mirror of
https://github.com/Luzifer/staticmap.git
synced 2024-12-20 12:51:18 +00:00
Vendor dependencies
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
4414239854
commit
759b968510
392 changed files with 161865 additions and 0 deletions
126
Godeps/Godeps.json
generated
Normal file
126
Godeps/Godeps.json
generated
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/Luzifer/staticmap",
|
||||||
|
"GoVersion": "go1.8",
|
||||||
|
"GodepVersion": "v79",
|
||||||
|
"Deps": [
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/Luzifer/go_helpers/accessLogger",
|
||||||
|
"Comment": "v2.2.0",
|
||||||
|
"Rev": "e31c3a2659d3f4901f696692cfe98bd0eb5168f9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/Luzifer/go_helpers/http",
|
||||||
|
"Comment": "v2.2.0",
|
||||||
|
"Rev": "e31c3a2659d3f4901f696692cfe98bd0eb5168f9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/Luzifer/rconfig",
|
||||||
|
"Comment": "v1.2.0",
|
||||||
|
"Rev": "7aef1d393c1e2d0758901853b59981c7adc67c7e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/Sirupsen/logrus",
|
||||||
|
"Comment": "v0.10.0-38-g3ec0642",
|
||||||
|
"Rev": "3ec0642a7fb6488f65b06f9040adc67e3990296a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/Wessie/appdirs",
|
||||||
|
"Rev": "6573e894f8e294cbae0c4e45c25ff9f2e2918a4e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/flopp/go-coordsparser",
|
||||||
|
"Rev": "845bca739e263e1cd38de25024a47b4d6acbfc1f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/flopp/go-staticmaps",
|
||||||
|
"Rev": "e8779c98399f6efad291d6504990daceeb9940a9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/fogleman/gg",
|
||||||
|
"Comment": "v1.0.0-10-gee8994f",
|
||||||
|
"Rev": "ee8994ff90057955c428a5a949da5d064bf3ce6b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/golang/freetype/raster",
|
||||||
|
"Comment": "release-131-g38b4c39",
|
||||||
|
"Rev": "38b4c392adc5eed94207994c4848fff99f4ac234"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/golang/freetype/truetype",
|
||||||
|
"Comment": "release-131-g38b4c39",
|
||||||
|
"Rev": "38b4c392adc5eed94207994c4848fff99f4ac234"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/golang/geo/r1",
|
||||||
|
"Rev": "f65fe014169924880aa2c95d7707c2da435534b9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/golang/geo/r2",
|
||||||
|
"Rev": "f65fe014169924880aa2c95d7707c2da435534b9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/golang/geo/r3",
|
||||||
|
"Rev": "f65fe014169924880aa2c95d7707c2da435534b9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/golang/geo/s1",
|
||||||
|
"Rev": "f65fe014169924880aa2c95d7707c2da435534b9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/golang/geo/s2",
|
||||||
|
"Rev": "f65fe014169924880aa2c95d7707c2da435534b9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gorilla/context",
|
||||||
|
"Rev": "1c83b3eabd45b6d76072b66b746c20815fb2872d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gorilla/mux",
|
||||||
|
"Rev": "49c024275504f0341e5a9971eb7ba7fa3dc7af40"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/lucasb-eyer/go-colorful",
|
||||||
|
"Rev": "c900de9dbbc73129068f5af6a823068fc5f2308c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/spf13/pflag",
|
||||||
|
"Rev": "c7e63cf4530bcd3ba943729cee0efeff2ebea63f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/tkrajina/gpxgo/gpx",
|
||||||
|
"Rev": "7848cf26f5a58b4a4e23b89a4b67cfc3d52dd042"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/image/draw",
|
||||||
|
"Rev": "97680175a5267bb8b31f1923e7a66df98013b11a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/image/font",
|
||||||
|
"Rev": "97680175a5267bb8b31f1923e7a66df98013b11a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/image/font/basicfont",
|
||||||
|
"Rev": "97680175a5267bb8b31f1923e7a66df98013b11a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/image/math/f64",
|
||||||
|
"Rev": "97680175a5267bb8b31f1923e7a66df98013b11a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/image/math/fixed",
|
||||||
|
"Rev": "97680175a5267bb8b31f1923e7a66df98013b11a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/sys/unix",
|
||||||
|
"Rev": "8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "gopkg.in/validator.v2",
|
||||||
|
"Rev": "07ffaad256c8e957050ad83d6472eb97d785013d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "gopkg.in/yaml.v2",
|
||||||
|
"Rev": "31c299268d302dd0aa9a0dcf765a3d58971ac83f"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
5
Godeps/Readme
generated
Normal file
5
Godeps/Readme
generated
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
This directory tree is generated automatically by godep.
|
||||||
|
|
||||||
|
Please do not edit.
|
||||||
|
|
||||||
|
See https://github.com/tools/godep for more information.
|
37
vendor/github.com/Luzifer/go_helpers/accessLogger/accessLogger.go
generated
vendored
Normal file
37
vendor/github.com/Luzifer/go_helpers/accessLogger/accessLogger.go
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package accessLogger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccessLogResponseWriter struct {
|
||||||
|
StatusCode int
|
||||||
|
Size int
|
||||||
|
|
||||||
|
http.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(res http.ResponseWriter) *AccessLogResponseWriter {
|
||||||
|
return &AccessLogResponseWriter{
|
||||||
|
StatusCode: 200,
|
||||||
|
Size: 0,
|
||||||
|
ResponseWriter: res,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AccessLogResponseWriter) Write(out []byte) (int, error) {
|
||||||
|
s, err := a.ResponseWriter.Write(out)
|
||||||
|
a.Size += s
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AccessLogResponseWriter) WriteHeader(code int) {
|
||||||
|
a.StatusCode = code
|
||||||
|
a.ResponseWriter.WriteHeader(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AccessLogResponseWriter) HTTPResponseType() string {
|
||||||
|
return fmt.Sprintf("%sxx", strconv.FormatInt(int64(a.StatusCode), 10)[0])
|
||||||
|
}
|
35
vendor/github.com/Luzifer/go_helpers/http/logHandler.go
generated
vendored
Normal file
35
vendor/github.com/Luzifer/go_helpers/http/logHandler.go
generated
vendored
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Luzifer/go_helpers/accessLogger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTPLogHandler struct {
|
||||||
|
Handler http.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPLogHandler(h http.Handler) http.Handler {
|
||||||
|
return HTTPLogHandler{Handler: h}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l HTTPLogHandler) ServeHTTP(res http.ResponseWriter, r *http.Request) {
|
||||||
|
start := time.Now()
|
||||||
|
ares := accessLogger.New(res)
|
||||||
|
|
||||||
|
l.Handler.ServeHTTP(ares, r)
|
||||||
|
|
||||||
|
log.Printf("%s - \"%s %s\" %d %d \"%s\" \"%s\" %s",
|
||||||
|
r.RemoteAddr,
|
||||||
|
r.Method,
|
||||||
|
r.URL.Path,
|
||||||
|
ares.StatusCode,
|
||||||
|
ares.Size,
|
||||||
|
r.Header.Get("Referer"),
|
||||||
|
r.Header.Get("User-Agent"),
|
||||||
|
time.Since(start),
|
||||||
|
)
|
||||||
|
}
|
8
vendor/github.com/Luzifer/rconfig/.travis.yml
generated
vendored
Normal file
8
vendor/github.com/Luzifer/rconfig/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.6
|
||||||
|
- 1.7
|
||||||
|
- tip
|
||||||
|
|
||||||
|
script: go test -v -race -cover ./...
|
9
vendor/github.com/Luzifer/rconfig/History.md
generated
vendored
Normal file
9
vendor/github.com/Luzifer/rconfig/History.md
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# 1.2.0 / 2017-06-19
|
||||||
|
|
||||||
|
* Add ParseAndValidate method
|
||||||
|
|
||||||
|
# 1.1.0 / 2016-06-28
|
||||||
|
|
||||||
|
* Support time.Duration config parameters
|
||||||
|
* Added goreportcard badge
|
||||||
|
* Added testcase for using bool with ENV and default
|
13
vendor/github.com/Luzifer/rconfig/LICENSE
generated
vendored
Normal file
13
vendor/github.com/Luzifer/rconfig/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
Copyright 2015 Knut Ahlers <knut@ahlers.me>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
87
vendor/github.com/Luzifer/rconfig/README.md
generated
vendored
Normal file
87
vendor/github.com/Luzifer/rconfig/README.md
generated
vendored
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
[![Build Status](https://travis-ci.org/Luzifer/rconfig.svg?branch=master)](https://travis-ci.org/Luzifer/rconfig)
|
||||||
|
[![License: Apache v2.0](https://badge.luzifer.io/v1/badge?color=5d79b5&title=license&text=Apache+v2.0)](http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
[![Documentation](https://badge.luzifer.io/v1/badge?title=godoc&text=reference)](https://godoc.org/github.com/Luzifer/rconfig)
|
||||||
|
[![Go Report](http://goreportcard.com/badge/Luzifer/rconfig)](http://goreportcard.com/report/Luzifer/rconfig)
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
> Package rconfig implements a CLI configuration reader with struct-embedded defaults, environment variables and posix compatible flag parsing using the [pflag](https://github.com/spf13/pflag) library.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Install by running:
|
||||||
|
|
||||||
|
```
|
||||||
|
go get -u github.com/Luzifer/rconfig
|
||||||
|
```
|
||||||
|
|
||||||
|
OR fetch a specific version:
|
||||||
|
|
||||||
|
```
|
||||||
|
go get -u gopkg.in/luzifer/rconfig.v1
|
||||||
|
```
|
||||||
|
|
||||||
|
Run tests by running:
|
||||||
|
|
||||||
|
```
|
||||||
|
go test -v -race -cover github.com/Luzifer/rconfig
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
A very simple usecase is to just configure a struct inside the vars section of your `main.go` and to parse the commandline flags from the `main()` function:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/Luzifer/rconfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cfg = struct {
|
||||||
|
Username string `default:"unknown" flag:"user" description:"Your name"`
|
||||||
|
Details struct {
|
||||||
|
Age int `default:"25" flag:"age" env:"age" description:"Your age"`
|
||||||
|
}
|
||||||
|
}{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
rconfig.Parse(&cfg)
|
||||||
|
|
||||||
|
fmt.Printf("Hello %s, happy birthday for your %dth birthday.",
|
||||||
|
cfg.Username,
|
||||||
|
cfg.Details.Age)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Provide variable defaults by using a file
|
||||||
|
|
||||||
|
Given you have a file `~/.myapp.yml` containing some secrets or usernames (for the example below username is assumed to be "luzifer") as a default configuration for your application you can use this source code to load the defaults from that file using the `vardefault` tag in your configuration struct.
|
||||||
|
|
||||||
|
The order of the directives (lower number = higher precedence):
|
||||||
|
|
||||||
|
1. Flags provided in command line
|
||||||
|
1. Environment variables
|
||||||
|
1. Variable defaults (`vardefault` tag in the struct)
|
||||||
|
1. `default` tag in the struct
|
||||||
|
|
||||||
|
```go
|
||||||
|
var cfg = struct {
|
||||||
|
Username string `vardefault:"username" flag:"username" description:"Your username"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
rconfig.SetVariableDefaults(rconfig.VarDefaultsFromYAMLFile("~/.myapp.yml"))
|
||||||
|
rconfig.Parse(&cfg)
|
||||||
|
|
||||||
|
fmt.Printf("Username = %s", cfg.Username)
|
||||||
|
// Output: Username = luzifer
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## More info
|
||||||
|
|
||||||
|
You can see the full reference documentation of the rconfig package [at godoc.org](https://godoc.org/github.com/Luzifer/rconfig), or through go's standard documentation system by running `godoc -http=:6060` and browsing to [http://localhost:6060/pkg/github.com/Luzifer/rconfig](http://localhost:6060/pkg/github.com/Luzifer/rconfig) after installation.
|
356
vendor/github.com/Luzifer/rconfig/config.go
generated
vendored
Normal file
356
vendor/github.com/Luzifer/rconfig/config.go
generated
vendored
Normal file
|
@ -0,0 +1,356 @@
|
||||||
|
// Package rconfig implements a CLI configuration reader with struct-embedded
|
||||||
|
// defaults, environment variables and posix compatible flag parsing using
|
||||||
|
// the pflag library.
|
||||||
|
package rconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
validator "gopkg.in/validator.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fs *pflag.FlagSet
|
||||||
|
variableDefaults map[string]string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
variableDefaults = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse takes the pointer to a struct filled with variables which should be read
|
||||||
|
// from ENV, default or flag. The precedence in this is flag > ENV > default. So
|
||||||
|
// if a flag is specified on the CLI it will overwrite the ENV and otherwise ENV
|
||||||
|
// overwrites the default specified.
|
||||||
|
//
|
||||||
|
// For your configuration struct you can use the following struct-tags to control
|
||||||
|
// the behavior of rconfig:
|
||||||
|
//
|
||||||
|
// default: Set a default value
|
||||||
|
// vardefault: Read the default value from the variable defaults
|
||||||
|
// env: Read the value from this environment variable
|
||||||
|
// flag: Flag to read in format "long,short" (for example "listen,l")
|
||||||
|
// description: A help text for Usage output to guide your users
|
||||||
|
//
|
||||||
|
// The format you need to specify those values you can see in the example to this
|
||||||
|
// function.
|
||||||
|
//
|
||||||
|
func Parse(config interface{}) error {
|
||||||
|
return parse(config, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseAndValidate works exactly like Parse but implements an additional run of
|
||||||
|
// the go-validator package on the configuration struct. Therefore additonal struct
|
||||||
|
// tags are supported like described in the readme file of the go-validator package:
|
||||||
|
//
|
||||||
|
// https://github.com/go-validator/validator/tree/v2#usage
|
||||||
|
func ParseAndValidate(config interface{}) error {
|
||||||
|
return parseAndValidate(config, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Args returns the non-flag command-line arguments.
|
||||||
|
func Args() []string {
|
||||||
|
return fs.Args()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage prints a basic usage with the corresponding defaults for the flags to
|
||||||
|
// os.Stdout. The defaults are derived from the `default` struct-tag and the ENV.
|
||||||
|
func Usage() {
|
||||||
|
if fs != nil && fs.Parsed() {
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
|
||||||
|
fs.PrintDefaults()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetVariableDefaults presets the parser with a map of default values to be used
|
||||||
|
// when specifying the vardefault tag
|
||||||
|
func SetVariableDefaults(defaults map[string]string) {
|
||||||
|
variableDefaults = defaults
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAndValidate(in interface{}, args []string) error {
|
||||||
|
if err := parse(in, args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return validator.Validate(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse(in interface{}, args []string) error {
|
||||||
|
if args == nil {
|
||||||
|
args = os.Args
|
||||||
|
}
|
||||||
|
|
||||||
|
fs = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError)
|
||||||
|
if err := execTags(in, fs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs.Parse(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func execTags(in interface{}, fs *pflag.FlagSet) error {
|
||||||
|
if reflect.TypeOf(in).Kind() != reflect.Ptr {
|
||||||
|
return errors.New("Calling parser with non-pointer")
|
||||||
|
}
|
||||||
|
|
||||||
|
if reflect.ValueOf(in).Elem().Kind() != reflect.Struct {
|
||||||
|
return errors.New("Calling parser with pointer to non-struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
st := reflect.ValueOf(in).Elem()
|
||||||
|
for i := 0; i < st.NumField(); i++ {
|
||||||
|
valField := st.Field(i)
|
||||||
|
typeField := st.Type().Field(i)
|
||||||
|
|
||||||
|
if typeField.Tag.Get("default") == "" && typeField.Tag.Get("env") == "" && typeField.Tag.Get("flag") == "" && typeField.Type.Kind() != reflect.Struct {
|
||||||
|
// None of our supported tags is present and it's not a sub-struct
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
value := varDefault(typeField.Tag.Get("vardefault"), typeField.Tag.Get("default"))
|
||||||
|
value = envDefault(typeField.Tag.Get("env"), value)
|
||||||
|
parts := strings.Split(typeField.Tag.Get("flag"), ",")
|
||||||
|
|
||||||
|
switch typeField.Type {
|
||||||
|
case reflect.TypeOf(time.Duration(0)):
|
||||||
|
v, err := time.ParseDuration(value)
|
||||||
|
if err != nil {
|
||||||
|
if value == "" {
|
||||||
|
v = time.Duration(0)
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if typeField.Tag.Get("flag") != "" {
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.DurationVar(valField.Addr().Interface().(*time.Duration), parts[0], v, typeField.Tag.Get("description"))
|
||||||
|
} else {
|
||||||
|
fs.DurationVarP(valField.Addr().Interface().(*time.Duration), parts[0], parts[1], v, typeField.Tag.Get("description"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
valField.Set(reflect.ValueOf(v))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch typeField.Type.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
if typeField.Tag.Get("flag") != "" {
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.StringVar(valField.Addr().Interface().(*string), parts[0], value, typeField.Tag.Get("description"))
|
||||||
|
} else {
|
||||||
|
fs.StringVarP(valField.Addr().Interface().(*string), parts[0], parts[1], value, typeField.Tag.Get("description"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
valField.SetString(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
v := value == "true"
|
||||||
|
if typeField.Tag.Get("flag") != "" {
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.BoolVar(valField.Addr().Interface().(*bool), parts[0], v, typeField.Tag.Get("description"))
|
||||||
|
} else {
|
||||||
|
fs.BoolVarP(valField.Addr().Interface().(*bool), parts[0], parts[1], v, typeField.Tag.Get("description"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
valField.SetBool(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int32, reflect.Int64:
|
||||||
|
vt, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
if value == "" {
|
||||||
|
vt = 0
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if typeField.Tag.Get("flag") != "" {
|
||||||
|
registerFlagInt(typeField.Type.Kind(), fs, valField.Addr().Interface(), parts, vt, typeField.Tag.Get("description"))
|
||||||
|
} else {
|
||||||
|
valField.SetInt(vt)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
vt, err := strconv.ParseUint(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
if value == "" {
|
||||||
|
vt = 0
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if typeField.Tag.Get("flag") != "" {
|
||||||
|
registerFlagUint(typeField.Type.Kind(), fs, valField.Addr().Interface(), parts, vt, typeField.Tag.Get("description"))
|
||||||
|
} else {
|
||||||
|
valField.SetUint(vt)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
vt, err := strconv.ParseFloat(value, 64)
|
||||||
|
if err != nil {
|
||||||
|
if value == "" {
|
||||||
|
vt = 0.0
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if typeField.Tag.Get("flag") != "" {
|
||||||
|
registerFlagFloat(typeField.Type.Kind(), fs, valField.Addr().Interface(), parts, vt, typeField.Tag.Get("description"))
|
||||||
|
} else {
|
||||||
|
valField.SetFloat(vt)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
if err := execTags(valField.Addr().Interface(), fs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
switch typeField.Type.Elem().Kind() {
|
||||||
|
case reflect.Int:
|
||||||
|
def := []int{}
|
||||||
|
for _, v := range strings.Split(value, ",") {
|
||||||
|
it, err := strconv.ParseInt(strings.TrimSpace(v), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
def = append(def, int(it))
|
||||||
|
}
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.IntSliceVar(valField.Addr().Interface().(*[]int), parts[0], def, typeField.Tag.Get("description"))
|
||||||
|
} else {
|
||||||
|
fs.IntSliceVarP(valField.Addr().Interface().(*[]int), parts[0], parts[1], def, typeField.Tag.Get("description"))
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
del := typeField.Tag.Get("delimiter")
|
||||||
|
if len(del) == 0 {
|
||||||
|
del = ","
|
||||||
|
}
|
||||||
|
def := strings.Split(value, del)
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.StringSliceVar(valField.Addr().Interface().(*[]string), parts[0], def, typeField.Tag.Get("description"))
|
||||||
|
} else {
|
||||||
|
fs.StringSliceVarP(valField.Addr().Interface().(*[]string), parts[0], parts[1], def, typeField.Tag.Get("description"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerFlagFloat(t reflect.Kind, fs *pflag.FlagSet, field interface{}, parts []string, vt float64, desc string) {
|
||||||
|
switch t {
|
||||||
|
case reflect.Float32:
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.Float32Var(field.(*float32), parts[0], float32(vt), desc)
|
||||||
|
} else {
|
||||||
|
fs.Float32VarP(field.(*float32), parts[0], parts[1], float32(vt), desc)
|
||||||
|
}
|
||||||
|
case reflect.Float64:
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.Float64Var(field.(*float64), parts[0], float64(vt), desc)
|
||||||
|
} else {
|
||||||
|
fs.Float64VarP(field.(*float64), parts[0], parts[1], float64(vt), desc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerFlagInt(t reflect.Kind, fs *pflag.FlagSet, field interface{}, parts []string, vt int64, desc string) {
|
||||||
|
switch t {
|
||||||
|
case reflect.Int:
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.IntVar(field.(*int), parts[0], int(vt), desc)
|
||||||
|
} else {
|
||||||
|
fs.IntVarP(field.(*int), parts[0], parts[1], int(vt), desc)
|
||||||
|
}
|
||||||
|
case reflect.Int8:
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.Int8Var(field.(*int8), parts[0], int8(vt), desc)
|
||||||
|
} else {
|
||||||
|
fs.Int8VarP(field.(*int8), parts[0], parts[1], int8(vt), desc)
|
||||||
|
}
|
||||||
|
case reflect.Int32:
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.Int32Var(field.(*int32), parts[0], int32(vt), desc)
|
||||||
|
} else {
|
||||||
|
fs.Int32VarP(field.(*int32), parts[0], parts[1], int32(vt), desc)
|
||||||
|
}
|
||||||
|
case reflect.Int64:
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.Int64Var(field.(*int64), parts[0], int64(vt), desc)
|
||||||
|
} else {
|
||||||
|
fs.Int64VarP(field.(*int64), parts[0], parts[1], int64(vt), desc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerFlagUint(t reflect.Kind, fs *pflag.FlagSet, field interface{}, parts []string, vt uint64, desc string) {
|
||||||
|
switch t {
|
||||||
|
case reflect.Uint:
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.UintVar(field.(*uint), parts[0], uint(vt), desc)
|
||||||
|
} else {
|
||||||
|
fs.UintVarP(field.(*uint), parts[0], parts[1], uint(vt), desc)
|
||||||
|
}
|
||||||
|
case reflect.Uint8:
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.Uint8Var(field.(*uint8), parts[0], uint8(vt), desc)
|
||||||
|
} else {
|
||||||
|
fs.Uint8VarP(field.(*uint8), parts[0], parts[1], uint8(vt), desc)
|
||||||
|
}
|
||||||
|
case reflect.Uint16:
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.Uint16Var(field.(*uint16), parts[0], uint16(vt), desc)
|
||||||
|
} else {
|
||||||
|
fs.Uint16VarP(field.(*uint16), parts[0], parts[1], uint16(vt), desc)
|
||||||
|
}
|
||||||
|
case reflect.Uint32:
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.Uint32Var(field.(*uint32), parts[0], uint32(vt), desc)
|
||||||
|
} else {
|
||||||
|
fs.Uint32VarP(field.(*uint32), parts[0], parts[1], uint32(vt), desc)
|
||||||
|
}
|
||||||
|
case reflect.Uint64:
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.Uint64Var(field.(*uint64), parts[0], uint64(vt), desc)
|
||||||
|
} else {
|
||||||
|
fs.Uint64VarP(field.(*uint64), parts[0], parts[1], uint64(vt), desc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func envDefault(env, def string) string {
|
||||||
|
value := def
|
||||||
|
|
||||||
|
if env != "" {
|
||||||
|
if e := os.Getenv(env); e != "" {
|
||||||
|
value = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func varDefault(name, def string) string {
|
||||||
|
value := def
|
||||||
|
|
||||||
|
if name != "" {
|
||||||
|
if v, ok := variableDefaults[name]; ok {
|
||||||
|
value = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
27
vendor/github.com/Luzifer/rconfig/vardefault_providers.go
generated
vendored
Normal file
27
vendor/github.com/Luzifer/rconfig/vardefault_providers.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package rconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VarDefaultsFromYAMLFile reads contents of a file and calls VarDefaultsFromYAML
|
||||||
|
func VarDefaultsFromYAMLFile(filename string) map[string]string {
|
||||||
|
data, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
return VarDefaultsFromYAML(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VarDefaultsFromYAML creates a vardefaults map from YAML raw data
|
||||||
|
func VarDefaultsFromYAML(in []byte) map[string]string {
|
||||||
|
out := make(map[string]string)
|
||||||
|
err := yaml.Unmarshal(in, &out)
|
||||||
|
if err != nil {
|
||||||
|
return make(map[string]string)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
1
vendor/github.com/Sirupsen/logrus/.gitignore
generated
vendored
Normal file
1
vendor/github.com/Sirupsen/logrus/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
logrus
|
10
vendor/github.com/Sirupsen/logrus/.travis.yml
generated
vendored
Normal file
10
vendor/github.com/Sirupsen/logrus/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.3
|
||||||
|
- 1.4
|
||||||
|
- 1.5
|
||||||
|
- 1.6
|
||||||
|
- tip
|
||||||
|
install:
|
||||||
|
- go get -t ./...
|
||||||
|
script: GOMAXPROCS=4 GORACE="halt_on_error=1" go test -race -v ./...
|
66
vendor/github.com/Sirupsen/logrus/CHANGELOG.md
generated
vendored
Normal file
66
vendor/github.com/Sirupsen/logrus/CHANGELOG.md
generated
vendored
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
# 0.10.0
|
||||||
|
|
||||||
|
* feature: Add a test hook (#180)
|
||||||
|
* feature: `ParseLevel` is now case-insensitive (#326)
|
||||||
|
* feature: `FieldLogger` interface that generalizes `Logger` and `Entry` (#308)
|
||||||
|
* performance: avoid re-allocations on `WithFields` (#335)
|
||||||
|
|
||||||
|
# 0.9.0
|
||||||
|
|
||||||
|
* logrus/text_formatter: don't emit empty msg
|
||||||
|
* logrus/hooks/airbrake: move out of main repository
|
||||||
|
* logrus/hooks/sentry: move out of main repository
|
||||||
|
* logrus/hooks/papertrail: move out of main repository
|
||||||
|
* logrus/hooks/bugsnag: move out of main repository
|
||||||
|
* logrus/core: run tests with `-race`
|
||||||
|
* logrus/core: detect TTY based on `stderr`
|
||||||
|
* logrus/core: support `WithError` on logger
|
||||||
|
* logrus/core: Solaris support
|
||||||
|
|
||||||
|
# 0.8.7
|
||||||
|
|
||||||
|
* logrus/core: fix possible race (#216)
|
||||||
|
* logrus/doc: small typo fixes and doc improvements
|
||||||
|
|
||||||
|
|
||||||
|
# 0.8.6
|
||||||
|
|
||||||
|
* hooks/raven: allow passing an initialized client
|
||||||
|
|
||||||
|
# 0.8.5
|
||||||
|
|
||||||
|
* logrus/core: revert #208
|
||||||
|
|
||||||
|
# 0.8.4
|
||||||
|
|
||||||
|
* formatter/text: fix data race (#218)
|
||||||
|
|
||||||
|
# 0.8.3
|
||||||
|
|
||||||
|
* logrus/core: fix entry log level (#208)
|
||||||
|
* logrus/core: improve performance of text formatter by 40%
|
||||||
|
* logrus/core: expose `LevelHooks` type
|
||||||
|
* logrus/core: add support for DragonflyBSD and NetBSD
|
||||||
|
* formatter/text: print structs more verbosely
|
||||||
|
|
||||||
|
# 0.8.2
|
||||||
|
|
||||||
|
* logrus: fix more Fatal family functions
|
||||||
|
|
||||||
|
# 0.8.1
|
||||||
|
|
||||||
|
* logrus: fix not exiting on `Fatalf` and `Fatalln`
|
||||||
|
|
||||||
|
# 0.8.0
|
||||||
|
|
||||||
|
* logrus: defaults to stderr instead of stdout
|
||||||
|
* hooks/sentry: add special field for `*http.Request`
|
||||||
|
* formatter/text: ignore Windows for colors
|
||||||
|
|
||||||
|
# 0.7.3
|
||||||
|
|
||||||
|
* formatter/\*: allow configuration of timestamp layout
|
||||||
|
|
||||||
|
# 0.7.2
|
||||||
|
|
||||||
|
* formatter/text: Add configuration option for time format (#158)
|
21
vendor/github.com/Sirupsen/logrus/LICENSE
generated
vendored
Normal file
21
vendor/github.com/Sirupsen/logrus/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Simon Eskildsen
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
421
vendor/github.com/Sirupsen/logrus/README.md
generated
vendored
Normal file
421
vendor/github.com/Sirupsen/logrus/README.md
generated
vendored
Normal file
|
@ -0,0 +1,421 @@
|
||||||
|
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [![Build Status](https://travis-ci.org/Sirupsen/logrus.svg?branch=master)](https://travis-ci.org/Sirupsen/logrus) [![GoDoc](https://godoc.org/github.com/Sirupsen/logrus?status.svg)](https://godoc.org/github.com/Sirupsen/logrus)
|
||||||
|
|
||||||
|
Logrus is a structured logger for Go (golang), completely API compatible with
|
||||||
|
the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not
|
||||||
|
yet stable (pre 1.0). Logrus itself is completely stable and has been used in
|
||||||
|
many large deployments. The core API is unlikely to change much but please
|
||||||
|
version control your Logrus to make sure you aren't fetching latest `master` on
|
||||||
|
every build.**
|
||||||
|
|
||||||
|
Nicely color-coded in development (when a TTY is attached, otherwise just
|
||||||
|
plain text):
|
||||||
|
|
||||||
|
![Colored](http://i.imgur.com/PY7qMwd.png)
|
||||||
|
|
||||||
|
With `log.SetFormatter(&log.JSONFormatter{})`, for easy parsing by logstash
|
||||||
|
or Splunk:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
|
||||||
|
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
|
||||||
|
|
||||||
|
{"level":"warning","msg":"The group's number increased tremendously!",
|
||||||
|
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
|
||||||
|
|
||||||
|
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
|
||||||
|
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
|
||||||
|
|
||||||
|
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
|
||||||
|
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
|
||||||
|
|
||||||
|
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
|
||||||
|
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
|
||||||
|
```
|
||||||
|
|
||||||
|
With the default `log.SetFormatter(&log.TextFormatter{})` when a TTY is not
|
||||||
|
attached, the output is compatible with the
|
||||||
|
[logfmt](http://godoc.org/github.com/kr/logfmt) format:
|
||||||
|
|
||||||
|
```text
|
||||||
|
time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
|
||||||
|
time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
|
||||||
|
time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
|
||||||
|
time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
|
||||||
|
time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
|
||||||
|
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
|
||||||
|
exit status 1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
The simplest way to use Logrus is simply the package-level exported logger:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
}).Info("A walrus appears")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that it's completely api-compatible with the stdlib logger, so you can
|
||||||
|
replace your `log` imports everywhere with `log "github.com/Sirupsen/logrus"`
|
||||||
|
and you'll now have the flexibility of Logrus. You can customize it all you
|
||||||
|
want:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Log as JSON instead of the default ASCII formatter.
|
||||||
|
log.SetFormatter(&log.JSONFormatter{})
|
||||||
|
|
||||||
|
// Output to stderr instead of stdout, could also be a file.
|
||||||
|
log.SetOutput(os.Stderr)
|
||||||
|
|
||||||
|
// Only log the warning severity or above.
|
||||||
|
log.SetLevel(log.WarnLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"size": 10,
|
||||||
|
}).Info("A group of walrus emerges from the ocean")
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"number": 122,
|
||||||
|
}).Warn("The group's number increased tremendously!")
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"number": 100,
|
||||||
|
}).Fatal("The ice breaks!")
|
||||||
|
|
||||||
|
// A common pattern is to re-use fields between logging statements by re-using
|
||||||
|
// the logrus.Entry returned from WithFields()
|
||||||
|
contextLogger := log.WithFields(log.Fields{
|
||||||
|
"common": "this is a common field",
|
||||||
|
"other": "I also should be logged always",
|
||||||
|
})
|
||||||
|
|
||||||
|
contextLogger.Info("I'll be logged with common and other field")
|
||||||
|
contextLogger.Info("Me too")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For more advanced usage such as logging to multiple locations from the same
|
||||||
|
application, you can also create an instance of the `logrus` Logger:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create a new instance of the logger. You can have any number of instances.
|
||||||
|
var log = logrus.New()
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// The API for setting attributes is a little different than the package level
|
||||||
|
// exported logger. See Godoc.
|
||||||
|
log.Out = os.Stderr
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"size": 10,
|
||||||
|
}).Info("A group of walrus emerges from the ocean")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Fields
|
||||||
|
|
||||||
|
Logrus encourages careful, structured logging though logging fields instead of
|
||||||
|
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
|
||||||
|
to send event %s to topic %s with key %d")`, you should log the much more
|
||||||
|
discoverable:
|
||||||
|
|
||||||
|
```go
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"event": event,
|
||||||
|
"topic": topic,
|
||||||
|
"key": key,
|
||||||
|
}).Fatal("Failed to send event")
|
||||||
|
```
|
||||||
|
|
||||||
|
We've found this API forces you to think about logging in a way that produces
|
||||||
|
much more useful logging messages. We've been in countless situations where just
|
||||||
|
a single added field to a log statement that was already there would've saved us
|
||||||
|
hours. The `WithFields` call is optional.
|
||||||
|
|
||||||
|
In general, with Logrus using any of the `printf`-family functions should be
|
||||||
|
seen as a hint you should add a field, however, you can still use the
|
||||||
|
`printf`-family functions with Logrus.
|
||||||
|
|
||||||
|
#### Hooks
|
||||||
|
|
||||||
|
You can add hooks for logging levels. For example to send errors to an exception
|
||||||
|
tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
|
||||||
|
multiple places simultaneously, e.g. syslog.
|
||||||
|
|
||||||
|
Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
|
||||||
|
`init`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "aibrake"
|
||||||
|
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
|
||||||
|
"log/syslog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
|
||||||
|
// Use the Airbrake hook to report errors that have Error severity or above to
|
||||||
|
// an exception tracker. You can create custom hooks, see the Hooks section.
|
||||||
|
log.AddHook(airbrake.NewHook(123, "xyz", "production"))
|
||||||
|
|
||||||
|
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Unable to connect to local syslog daemon")
|
||||||
|
} else {
|
||||||
|
log.AddHook(hook)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md).
|
||||||
|
|
||||||
|
| Hook | Description |
|
||||||
|
| ----- | ----------- |
|
||||||
|
| [Airbrake](https://github.com/gemnasium/logrus-airbrake-hook) | Send errors to the Airbrake API V3. Uses the official [`gobrake`](https://github.com/airbrake/gobrake) behind the scenes. |
|
||||||
|
| [Airbrake "legacy"](https://github.com/gemnasium/logrus-airbrake-legacy-hook) | Send errors to an exception tracking service compatible with the Airbrake API V2. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. |
|
||||||
|
| [Papertrail](https://github.com/polds/logrus-papertrail-hook) | Send errors to the [Papertrail](https://papertrailapp.com) hosted logging service via UDP. |
|
||||||
|
| [Syslog](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. |
|
||||||
|
| [Bugsnag](https://github.com/Shopify/logrus-bugsnag/blob/master/bugsnag.go) | Send errors to the Bugsnag exception tracking service. |
|
||||||
|
| [Sentry](https://github.com/evalphobia/logrus_sentry) | Send errors to the Sentry error logging and aggregation service. |
|
||||||
|
| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. |
|
||||||
|
| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
|
||||||
|
| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. |
|
||||||
|
| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` |
|
||||||
|
| [Graylog](https://github.com/gemnasium/logrus-graylog-hook) | Hook for logging to [Graylog](http://graylog2.org/) |
|
||||||
|
| [Raygun](https://github.com/squirkle/logrus-raygun-hook) | Hook for logging to [Raygun.io](http://raygun.io/) |
|
||||||
|
| [LFShook](https://github.com/rifflock/lfshook) | Hook for logging to the local filesystem |
|
||||||
|
| [Honeybadger](https://github.com/agonzalezro/logrus_honeybadger) | Hook for sending exceptions to Honeybadger |
|
||||||
|
| [Mail](https://github.com/zbindenren/logrus_mail) | Hook for sending exceptions via mail |
|
||||||
|
| [Rollrus](https://github.com/heroku/rollrus) | Hook for sending errors to rollbar |
|
||||||
|
| [Fluentd](https://github.com/evalphobia/logrus_fluent) | Hook for logging to fluentd |
|
||||||
|
| [Mongodb](https://github.com/weekface/mgorus) | Hook for logging to mongodb |
|
||||||
|
| [Influxus] (http://github.com/vlad-doru/influxus) | Hook for concurrently logging to [InfluxDB] (http://influxdata.com/) |
|
||||||
|
| [InfluxDB](https://github.com/Abramovic/logrus_influxdb) | Hook for logging to influxdb |
|
||||||
|
| [Octokit](https://github.com/dorajistyle/logrus-octokit-hook) | Hook for logging to github via octokit |
|
||||||
|
| [DeferPanic](https://github.com/deferpanic/dp-logrus) | Hook for logging to DeferPanic |
|
||||||
|
| [Redis-Hook](https://github.com/rogierlommers/logrus-redis-hook) | Hook for logging to a ELK stack (through Redis) |
|
||||||
|
| [Amqp-Hook](https://github.com/vladoatanasov/logrus_amqp) | Hook for logging to Amqp broker (Like RabbitMQ) |
|
||||||
|
| [KafkaLogrus](https://github.com/goibibo/KafkaLogrus) | Hook for logging to kafka |
|
||||||
|
| [Typetalk](https://github.com/dragon3/logrus-typetalk-hook) | Hook for logging to [Typetalk](https://www.typetalk.in/) |
|
||||||
|
| [ElasticSearch](https://github.com/sohlich/elogrus) | Hook for logging to ElasticSearch|
|
||||||
|
| [Sumorus](https://github.com/doublefree/sumorus) | Hook for logging to [SumoLogic](https://www.sumologic.com/)|
|
||||||
|
| [Logstash](https://github.com/bshuster-repo/logrus-logstash-hook) | Hook for logging to [Logstash](https://www.elastic.co/products/logstash) |
|
||||||
|
| [Logmatic.io](https://github.com/logmatic/logmatic-go) | Hook for logging to [Logmatic.io](http://logmatic.io/) |
|
||||||
|
|
||||||
|
|
||||||
|
#### Level logging
|
||||||
|
|
||||||
|
Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic.
|
||||||
|
|
||||||
|
```go
|
||||||
|
log.Debug("Useful debugging information.")
|
||||||
|
log.Info("Something noteworthy happened!")
|
||||||
|
log.Warn("You should probably take a look at this.")
|
||||||
|
log.Error("Something failed but I'm not quitting.")
|
||||||
|
// Calls os.Exit(1) after logging
|
||||||
|
log.Fatal("Bye.")
|
||||||
|
// Calls panic() after logging
|
||||||
|
log.Panic("I'm bailing.")
|
||||||
|
```
|
||||||
|
|
||||||
|
You can set the logging level on a `Logger`, then it will only log entries with
|
||||||
|
that severity or anything above it:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Will log anything that is info or above (warn, error, fatal, panic). Default.
|
||||||
|
log.SetLevel(log.InfoLevel)
|
||||||
|
```
|
||||||
|
|
||||||
|
It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
|
||||||
|
environment if your application has that.
|
||||||
|
|
||||||
|
#### Entries
|
||||||
|
|
||||||
|
Besides the fields added with `WithField` or `WithFields` some fields are
|
||||||
|
automatically added to all logging events:
|
||||||
|
|
||||||
|
1. `time`. The timestamp when the entry was created.
|
||||||
|
2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after
|
||||||
|
the `AddFields` call. E.g. `Failed to send event.`
|
||||||
|
3. `level`. The logging level. E.g. `info`.
|
||||||
|
|
||||||
|
#### Environments
|
||||||
|
|
||||||
|
Logrus has no notion of environment.
|
||||||
|
|
||||||
|
If you wish for hooks and formatters to only be used in specific environments,
|
||||||
|
you should handle that yourself. For example, if your application has a global
|
||||||
|
variable `Environment`, which is a string representation of the environment you
|
||||||
|
could do:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
init() {
|
||||||
|
// do something here to set environment depending on an environment variable
|
||||||
|
// or command-line flag
|
||||||
|
if Environment == "production" {
|
||||||
|
log.SetFormatter(&log.JSONFormatter{})
|
||||||
|
} else {
|
||||||
|
// The TextFormatter is default, you don't actually have to do this.
|
||||||
|
log.SetFormatter(&log.TextFormatter{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This configuration is how `logrus` was intended to be used, but JSON in
|
||||||
|
production is mostly only useful if you do log aggregation with tools like
|
||||||
|
Splunk or Logstash.
|
||||||
|
|
||||||
|
#### Formatters
|
||||||
|
|
||||||
|
The built-in logging formatters are:
|
||||||
|
|
||||||
|
* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
|
||||||
|
without colors.
|
||||||
|
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
|
||||||
|
field to `true`. To force no colored output even if there is a TTY set the
|
||||||
|
`DisableColors` field to `true`
|
||||||
|
* `logrus.JSONFormatter`. Logs fields as JSON.
|
||||||
|
|
||||||
|
Third party logging formatters:
|
||||||
|
|
||||||
|
* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events.
|
||||||
|
* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
|
||||||
|
* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
|
||||||
|
|
||||||
|
You can define your formatter by implementing the `Formatter` interface,
|
||||||
|
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
|
||||||
|
`Fields` type (`map[string]interface{}`) with all your fields as well as the
|
||||||
|
default ones (see Entries section above):
|
||||||
|
|
||||||
|
```go
|
||||||
|
type MyJSONFormatter struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
log.SetFormatter(new(MyJSONFormatter))
|
||||||
|
|
||||||
|
func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
|
// Note this doesn't include Time, Level and Message which are available on
|
||||||
|
// the Entry. Consult `godoc` on information about those fields or read the
|
||||||
|
// source of the official loggers.
|
||||||
|
serialized, err := json.Marshal(entry.Data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||||
|
}
|
||||||
|
return append(serialized, '\n'), nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Logger as an `io.Writer`
|
||||||
|
|
||||||
|
Logrus can be transformed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it.
|
||||||
|
|
||||||
|
```go
|
||||||
|
w := logger.Writer()
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
srv := http.Server{
|
||||||
|
// create a stdlib log.Logger that writes to
|
||||||
|
// logrus.Logger.
|
||||||
|
ErrorLog: log.New(w, "", 0),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Each line written to that writer will be printed the usual way, using formatters
|
||||||
|
and hooks. The level for those entries is `info`.
|
||||||
|
|
||||||
|
#### Rotation
|
||||||
|
|
||||||
|
Log rotation is not provided with Logrus. Log rotation should be done by an
|
||||||
|
external program (like `logrotate(8)`) that can compress and delete old log
|
||||||
|
entries. It should not be a feature of the application-level logger.
|
||||||
|
|
||||||
|
#### Tools
|
||||||
|
|
||||||
|
| Tool | Description |
|
||||||
|
| ---- | ----------- |
|
||||||
|
|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will generated with different config at different environment.|
|
||||||
|
|
||||||
|
#### Testing
|
||||||
|
|
||||||
|
Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides:
|
||||||
|
|
||||||
|
* decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just add the `test` hook
|
||||||
|
* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any):
|
||||||
|
|
||||||
|
```go
|
||||||
|
logger, hook := NewNullLogger()
|
||||||
|
logger.Error("Hello error")
|
||||||
|
|
||||||
|
assert.Equal(1, len(hook.Entries))
|
||||||
|
assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
|
||||||
|
assert.Equal("Hello error", hook.LastEntry().Message)
|
||||||
|
|
||||||
|
hook.Reset()
|
||||||
|
assert.Nil(hook.LastEntry())
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Fatal handlers
|
||||||
|
|
||||||
|
Logrus can register one or more functions that will be called when any `fatal`
|
||||||
|
level message is logged. The registered handlers will be executed before
|
||||||
|
logrus performs a `os.Exit(1)`. This behavior may be helpful if callers need
|
||||||
|
to gracefully shutdown. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted.
|
||||||
|
|
||||||
|
```
|
||||||
|
...
|
||||||
|
handler := func() {
|
||||||
|
// gracefully shutdown something...
|
||||||
|
}
|
||||||
|
logrus.RegisterExitHandler(handler)
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Thread safty
|
||||||
|
|
||||||
|
By default Logger is protected by mutex for concurrent writes, this mutex is invoked when calling hooks and writing logs.
|
||||||
|
If you are sure such locking is not needed, you can call logger.SetNoLock() to disable the locking.
|
||||||
|
|
||||||
|
Situation when locking is not needed includes:
|
||||||
|
|
||||||
|
* You have no hooks registered, or hooks calling is already thread-safe.
|
||||||
|
|
||||||
|
* Writing to logger.Out is already thread-safe, for example:
|
||||||
|
|
||||||
|
1) logger.Out is protected by locks.
|
||||||
|
|
||||||
|
2) logger.Out is a os.File handler opened with `O_APPEND` flag, and every write is smaller than 4k. (This allow multi-thread/multi-process writing)
|
||||||
|
|
||||||
|
(Refer to http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/)
|
64
vendor/github.com/Sirupsen/logrus/alt_exit.go
generated
vendored
Normal file
64
vendor/github.com/Sirupsen/logrus/alt_exit.go
generated
vendored
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
// The following code was sourced and modified from the
|
||||||
|
// https://bitbucket.org/tebeka/atexit package governed by the following license:
|
||||||
|
//
|
||||||
|
// Copyright (c) 2012 Miki Tebeka <miki.tebeka@gmail.com>.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
// this software and associated documentation files (the "Software"), to deal in
|
||||||
|
// the Software without restriction, including without limitation the rights to
|
||||||
|
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
// subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var handlers = []func(){}
|
||||||
|
|
||||||
|
func runHandler(handler func()) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "Error: Logrus exit handler error:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
handler()
|
||||||
|
}
|
||||||
|
|
||||||
|
func runHandlers() {
|
||||||
|
for _, handler := range handlers {
|
||||||
|
runHandler(handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit runs all the Logrus atexit handlers and then terminates the program using os.Exit(code)
|
||||||
|
func Exit(code int) {
|
||||||
|
runHandlers()
|
||||||
|
os.Exit(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterExitHandler adds a Logrus Exit handler, call logrus.Exit to invoke
|
||||||
|
// all handlers. The handlers will also be invoked when any Fatal log entry is
|
||||||
|
// made.
|
||||||
|
//
|
||||||
|
// This method is useful when a caller wishes to use logrus to log a fatal
|
||||||
|
// message but also needs to gracefully shutdown. An example usecase could be
|
||||||
|
// closing database connections, or sending a alert that the application is
|
||||||
|
// closing.
|
||||||
|
func RegisterExitHandler(handler func()) {
|
||||||
|
handlers = append(handlers, handler)
|
||||||
|
}
|
26
vendor/github.com/Sirupsen/logrus/doc.go
generated
vendored
Normal file
26
vendor/github.com/Sirupsen/logrus/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
Package logrus is a structured logger for Go, completely API compatible with the standard library logger.
|
||||||
|
|
||||||
|
|
||||||
|
The simplest way to use Logrus is simply the package-level exported logger:
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"number": 1,
|
||||||
|
"size": 10,
|
||||||
|
}).Info("A walrus appears")
|
||||||
|
}
|
||||||
|
|
||||||
|
Output:
|
||||||
|
time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
|
||||||
|
|
||||||
|
For a full guide visit https://github.com/Sirupsen/logrus
|
||||||
|
*/
|
||||||
|
package logrus
|
275
vendor/github.com/Sirupsen/logrus/entry.go
generated
vendored
Normal file
275
vendor/github.com/Sirupsen/logrus/entry.go
generated
vendored
Normal file
|
@ -0,0 +1,275 @@
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bufferPool *sync.Pool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
bufferPool = &sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return new(bytes.Buffer)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defines the key when adding errors using WithError.
|
||||||
|
var ErrorKey = "error"
|
||||||
|
|
||||||
|
// An entry is the final or intermediate Logrus logging entry. It contains all
|
||||||
|
// the fields passed with WithField{,s}. It's finally logged when Debug, Info,
|
||||||
|
// Warn, Error, Fatal or Panic is called on it. These objects can be reused and
|
||||||
|
// passed around as much as you wish to avoid field duplication.
|
||||||
|
type Entry struct {
|
||||||
|
Logger *Logger
|
||||||
|
|
||||||
|
// Contains all the fields set by the user.
|
||||||
|
Data Fields
|
||||||
|
|
||||||
|
// Time at which the log entry was created
|
||||||
|
Time time.Time
|
||||||
|
|
||||||
|
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
|
||||||
|
Level Level
|
||||||
|
|
||||||
|
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
|
||||||
|
Message string
|
||||||
|
|
||||||
|
// When formatter is called in entry.log(), an Buffer may be set to entry
|
||||||
|
Buffer *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEntry(logger *Logger) *Entry {
|
||||||
|
return &Entry{
|
||||||
|
Logger: logger,
|
||||||
|
// Default is three fields, give a little extra room
|
||||||
|
Data: make(Fields, 5),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the string representation from the reader and ultimately the
|
||||||
|
// formatter.
|
||||||
|
func (entry *Entry) String() (string, error) {
|
||||||
|
serialized, err := entry.Logger.Formatter.Format(entry)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
str := string(serialized)
|
||||||
|
return str, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an error as single field (using the key defined in ErrorKey) to the Entry.
|
||||||
|
func (entry *Entry) WithError(err error) *Entry {
|
||||||
|
return entry.WithField(ErrorKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a single field to the Entry.
|
||||||
|
func (entry *Entry) WithField(key string, value interface{}) *Entry {
|
||||||
|
return entry.WithFields(Fields{key: value})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a map of fields to the Entry.
|
||||||
|
func (entry *Entry) WithFields(fields Fields) *Entry {
|
||||||
|
data := make(Fields, len(entry.Data)+len(fields))
|
||||||
|
for k, v := range entry.Data {
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range fields {
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
return &Entry{Logger: entry.Logger, Data: data}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is not declared with a pointer value because otherwise
|
||||||
|
// race conditions will occur when using multiple goroutines
|
||||||
|
func (entry Entry) log(level Level, msg string) {
|
||||||
|
var buffer *bytes.Buffer
|
||||||
|
entry.Time = time.Now()
|
||||||
|
entry.Level = level
|
||||||
|
entry.Message = msg
|
||||||
|
|
||||||
|
if err := entry.Logger.Hooks.Fire(level, &entry); err != nil {
|
||||||
|
entry.Logger.mu.Lock()
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
||||||
|
entry.Logger.mu.Unlock()
|
||||||
|
}
|
||||||
|
buffer = bufferPool.Get().(*bytes.Buffer)
|
||||||
|
buffer.Reset()
|
||||||
|
defer bufferPool.Put(buffer)
|
||||||
|
entry.Buffer = buffer
|
||||||
|
serialized, err := entry.Logger.Formatter.Format(&entry)
|
||||||
|
entry.Buffer = nil
|
||||||
|
if err != nil {
|
||||||
|
entry.Logger.mu.Lock()
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
||||||
|
entry.Logger.mu.Unlock()
|
||||||
|
} else {
|
||||||
|
entry.Logger.mu.Lock()
|
||||||
|
_, err = entry.Logger.Out.Write(serialized)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
||||||
|
}
|
||||||
|
entry.Logger.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// To avoid Entry#log() returning a value that only would make sense for
|
||||||
|
// panic() to use in Entry#Panic(), we avoid the allocation by checking
|
||||||
|
// directly here.
|
||||||
|
if level <= PanicLevel {
|
||||||
|
panic(&entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Debug(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= DebugLevel {
|
||||||
|
entry.log(DebugLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Print(args ...interface{}) {
|
||||||
|
entry.Info(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Info(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= InfoLevel {
|
||||||
|
entry.log(InfoLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warn(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= WarnLevel {
|
||||||
|
entry.log(WarnLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warning(args ...interface{}) {
|
||||||
|
entry.Warn(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Error(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= ErrorLevel {
|
||||||
|
entry.log(ErrorLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Fatal(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= FatalLevel {
|
||||||
|
entry.log(FatalLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Panic(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= PanicLevel {
|
||||||
|
entry.log(PanicLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
panic(fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry Printf family functions
|
||||||
|
|
||||||
|
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= DebugLevel {
|
||||||
|
entry.Debug(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Infof(format string, args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= InfoLevel {
|
||||||
|
entry.Info(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Printf(format string, args ...interface{}) {
|
||||||
|
entry.Infof(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= WarnLevel {
|
||||||
|
entry.Warn(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warningf(format string, args ...interface{}) {
|
||||||
|
entry.Warnf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= ErrorLevel {
|
||||||
|
entry.Error(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= FatalLevel {
|
||||||
|
entry.Fatal(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= PanicLevel {
|
||||||
|
entry.Panic(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry Println family functions
|
||||||
|
|
||||||
|
func (entry *Entry) Debugln(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= DebugLevel {
|
||||||
|
entry.Debug(entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Infoln(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= InfoLevel {
|
||||||
|
entry.Info(entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Println(args ...interface{}) {
|
||||||
|
entry.Infoln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warnln(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= WarnLevel {
|
||||||
|
entry.Warn(entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warningln(args ...interface{}) {
|
||||||
|
entry.Warnln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Errorln(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= ErrorLevel {
|
||||||
|
entry.Error(entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Fatalln(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= FatalLevel {
|
||||||
|
entry.Fatal(entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Panicln(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= PanicLevel {
|
||||||
|
entry.Panic(entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintlnn => Sprint no newline. This is to get the behavior of how
|
||||||
|
// fmt.Sprintln where spaces are always added between operands, regardless of
|
||||||
|
// their type. Instead of vendoring the Sprintln implementation to spare a
|
||||||
|
// string allocation, we do the simplest thing.
|
||||||
|
func (entry *Entry) sprintlnn(args ...interface{}) string {
|
||||||
|
msg := fmt.Sprintln(args...)
|
||||||
|
return msg[:len(msg)-1]
|
||||||
|
}
|
193
vendor/github.com/Sirupsen/logrus/exported.go
generated
vendored
Normal file
193
vendor/github.com/Sirupsen/logrus/exported.go
generated
vendored
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// std is the name of the standard logger in stdlib `log`
|
||||||
|
std = New()
|
||||||
|
)
|
||||||
|
|
||||||
|
func StandardLogger() *Logger {
|
||||||
|
return std
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOutput sets the standard logger output.
|
||||||
|
func SetOutput(out io.Writer) {
|
||||||
|
std.mu.Lock()
|
||||||
|
defer std.mu.Unlock()
|
||||||
|
std.Out = out
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFormatter sets the standard logger formatter.
|
||||||
|
func SetFormatter(formatter Formatter) {
|
||||||
|
std.mu.Lock()
|
||||||
|
defer std.mu.Unlock()
|
||||||
|
std.Formatter = formatter
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLevel sets the standard logger level.
|
||||||
|
func SetLevel(level Level) {
|
||||||
|
std.mu.Lock()
|
||||||
|
defer std.mu.Unlock()
|
||||||
|
std.Level = level
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLevel returns the standard logger level.
|
||||||
|
func GetLevel() Level {
|
||||||
|
std.mu.Lock()
|
||||||
|
defer std.mu.Unlock()
|
||||||
|
return std.Level
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddHook adds a hook to the standard logger hooks.
|
||||||
|
func AddHook(hook Hook) {
|
||||||
|
std.mu.Lock()
|
||||||
|
defer std.mu.Unlock()
|
||||||
|
std.Hooks.Add(hook)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
|
||||||
|
func WithError(err error) *Entry {
|
||||||
|
return std.WithField(ErrorKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithField creates an entry from the standard logger and adds a field to
|
||||||
|
// it. If you want multiple fields, use `WithFields`.
|
||||||
|
//
|
||||||
|
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||||
|
// or Panic on the Entry it returns.
|
||||||
|
func WithField(key string, value interface{}) *Entry {
|
||||||
|
return std.WithField(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFields creates an entry from the standard logger and adds multiple
|
||||||
|
// fields to it. This is simply a helper for `WithField`, invoking it
|
||||||
|
// once for each field.
|
||||||
|
//
|
||||||
|
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||||
|
// or Panic on the Entry it returns.
|
||||||
|
func WithFields(fields Fields) *Entry {
|
||||||
|
return std.WithFields(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug logs a message at level Debug on the standard logger.
|
||||||
|
func Debug(args ...interface{}) {
|
||||||
|
std.Debug(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print logs a message at level Info on the standard logger.
|
||||||
|
func Print(args ...interface{}) {
|
||||||
|
std.Print(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info logs a message at level Info on the standard logger.
|
||||||
|
func Info(args ...interface{}) {
|
||||||
|
std.Info(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn logs a message at level Warn on the standard logger.
|
||||||
|
func Warn(args ...interface{}) {
|
||||||
|
std.Warn(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning logs a message at level Warn on the standard logger.
|
||||||
|
func Warning(args ...interface{}) {
|
||||||
|
std.Warning(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs a message at level Error on the standard logger.
|
||||||
|
func Error(args ...interface{}) {
|
||||||
|
std.Error(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panic logs a message at level Panic on the standard logger.
|
||||||
|
func Panic(args ...interface{}) {
|
||||||
|
std.Panic(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal logs a message at level Fatal on the standard logger.
|
||||||
|
func Fatal(args ...interface{}) {
|
||||||
|
std.Fatal(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugf logs a message at level Debug on the standard logger.
|
||||||
|
func Debugf(format string, args ...interface{}) {
|
||||||
|
std.Debugf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf logs a message at level Info on the standard logger.
|
||||||
|
func Printf(format string, args ...interface{}) {
|
||||||
|
std.Printf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof logs a message at level Info on the standard logger.
|
||||||
|
func Infof(format string, args ...interface{}) {
|
||||||
|
std.Infof(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnf logs a message at level Warn on the standard logger.
|
||||||
|
func Warnf(format string, args ...interface{}) {
|
||||||
|
std.Warnf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warningf logs a message at level Warn on the standard logger.
|
||||||
|
func Warningf(format string, args ...interface{}) {
|
||||||
|
std.Warningf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf logs a message at level Error on the standard logger.
|
||||||
|
func Errorf(format string, args ...interface{}) {
|
||||||
|
std.Errorf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panicf logs a message at level Panic on the standard logger.
|
||||||
|
func Panicf(format string, args ...interface{}) {
|
||||||
|
std.Panicf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalf logs a message at level Fatal on the standard logger.
|
||||||
|
func Fatalf(format string, args ...interface{}) {
|
||||||
|
std.Fatalf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugln logs a message at level Debug on the standard logger.
|
||||||
|
func Debugln(args ...interface{}) {
|
||||||
|
std.Debugln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println logs a message at level Info on the standard logger.
|
||||||
|
func Println(args ...interface{}) {
|
||||||
|
std.Println(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infoln logs a message at level Info on the standard logger.
|
||||||
|
func Infoln(args ...interface{}) {
|
||||||
|
std.Infoln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnln logs a message at level Warn on the standard logger.
|
||||||
|
func Warnln(args ...interface{}) {
|
||||||
|
std.Warnln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warningln logs a message at level Warn on the standard logger.
|
||||||
|
func Warningln(args ...interface{}) {
|
||||||
|
std.Warningln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorln logs a message at level Error on the standard logger.
|
||||||
|
func Errorln(args ...interface{}) {
|
||||||
|
std.Errorln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panicln logs a message at level Panic on the standard logger.
|
||||||
|
func Panicln(args ...interface{}) {
|
||||||
|
std.Panicln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalln logs a message at level Fatal on the standard logger.
|
||||||
|
func Fatalln(args ...interface{}) {
|
||||||
|
std.Fatalln(args...)
|
||||||
|
}
|
45
vendor/github.com/Sirupsen/logrus/formatter.go
generated
vendored
Normal file
45
vendor/github.com/Sirupsen/logrus/formatter.go
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const DefaultTimestampFormat = time.RFC3339
|
||||||
|
|
||||||
|
// The Formatter interface is used to implement a custom Formatter. It takes an
|
||||||
|
// `Entry`. It exposes all the fields, including the default ones:
|
||||||
|
//
|
||||||
|
// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
|
||||||
|
// * `entry.Data["time"]`. The timestamp.
|
||||||
|
// * `entry.Data["level"]. The level the entry was logged at.
|
||||||
|
//
|
||||||
|
// Any additional fields added with `WithField` or `WithFields` are also in
|
||||||
|
// `entry.Data`. Format is expected to return an array of bytes which are then
|
||||||
|
// logged to `logger.Out`.
|
||||||
|
type Formatter interface {
|
||||||
|
Format(*Entry) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is to not silently overwrite `time`, `msg` and `level` fields when
|
||||||
|
// dumping it. If this code wasn't there doing:
|
||||||
|
//
|
||||||
|
// logrus.WithField("level", 1).Info("hello")
|
||||||
|
//
|
||||||
|
// Would just silently drop the user provided level. Instead with this code
|
||||||
|
// it'll logged as:
|
||||||
|
//
|
||||||
|
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
|
||||||
|
//
|
||||||
|
// It's not exported because it's still using Data in an opinionated way. It's to
|
||||||
|
// avoid code duplication between the two default formatters.
|
||||||
|
func prefixFieldClashes(data Fields) {
|
||||||
|
if t, ok := data["time"]; ok {
|
||||||
|
data["fields.time"] = t
|
||||||
|
}
|
||||||
|
|
||||||
|
if m, ok := data["msg"]; ok {
|
||||||
|
data["fields.msg"] = m
|
||||||
|
}
|
||||||
|
|
||||||
|
if l, ok := data["level"]; ok {
|
||||||
|
data["fields.level"] = l
|
||||||
|
}
|
||||||
|
}
|
34
vendor/github.com/Sirupsen/logrus/hooks.go
generated
vendored
Normal file
34
vendor/github.com/Sirupsen/logrus/hooks.go
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
// A hook to be fired when logging on the logging levels returned from
|
||||||
|
// `Levels()` on your implementation of the interface. Note that this is not
|
||||||
|
// fired in a goroutine or a channel with workers, you should handle such
|
||||||
|
// functionality yourself if your call is non-blocking and you don't wish for
|
||||||
|
// the logging calls for levels returned from `Levels()` to block.
|
||||||
|
type Hook interface {
|
||||||
|
Levels() []Level
|
||||||
|
Fire(*Entry) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal type for storing the hooks on a logger instance.
|
||||||
|
type LevelHooks map[Level][]Hook
|
||||||
|
|
||||||
|
// Add a hook to an instance of logger. This is called with
|
||||||
|
// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface.
|
||||||
|
func (hooks LevelHooks) Add(hook Hook) {
|
||||||
|
for _, level := range hook.Levels() {
|
||||||
|
hooks[level] = append(hooks[level], hook)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fire all the hooks for the passed level. Used by `entry.log` to fire
|
||||||
|
// appropriate hooks for a log entry.
|
||||||
|
func (hooks LevelHooks) Fire(level Level, entry *Entry) error {
|
||||||
|
for _, hook := range hooks[level] {
|
||||||
|
if err := hook.Fire(entry); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
41
vendor/github.com/Sirupsen/logrus/json_formatter.go
generated
vendored
Normal file
41
vendor/github.com/Sirupsen/logrus/json_formatter.go
generated
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JSONFormatter struct {
|
||||||
|
// TimestampFormat sets the format used for marshaling timestamps.
|
||||||
|
TimestampFormat string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
|
data := make(Fields, len(entry.Data)+3)
|
||||||
|
for k, v := range entry.Data {
|
||||||
|
switch v := v.(type) {
|
||||||
|
case error:
|
||||||
|
// Otherwise errors are ignored by `encoding/json`
|
||||||
|
// https://github.com/Sirupsen/logrus/issues/137
|
||||||
|
data[k] = v.Error()
|
||||||
|
default:
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prefixFieldClashes(data)
|
||||||
|
|
||||||
|
timestampFormat := f.TimestampFormat
|
||||||
|
if timestampFormat == "" {
|
||||||
|
timestampFormat = DefaultTimestampFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
data["time"] = entry.Time.Format(timestampFormat)
|
||||||
|
data["msg"] = entry.Message
|
||||||
|
data["level"] = entry.Level.String()
|
||||||
|
|
||||||
|
serialized, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||||
|
}
|
||||||
|
return append(serialized, '\n'), nil
|
||||||
|
}
|
308
vendor/github.com/Sirupsen/logrus/logger.go
generated
vendored
Normal file
308
vendor/github.com/Sirupsen/logrus/logger.go
generated
vendored
Normal file
|
@ -0,0 +1,308 @@
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
|
||||||
|
// file, or leave it default which is `os.Stderr`. You can also set this to
|
||||||
|
// something more adventorous, such as logging to Kafka.
|
||||||
|
Out io.Writer
|
||||||
|
// Hooks for the logger instance. These allow firing events based on logging
|
||||||
|
// levels and log entries. For example, to send errors to an error tracking
|
||||||
|
// service, log to StatsD or dump the core on fatal errors.
|
||||||
|
Hooks LevelHooks
|
||||||
|
// All log entries pass through the formatter before logged to Out. The
|
||||||
|
// included formatters are `TextFormatter` and `JSONFormatter` for which
|
||||||
|
// TextFormatter is the default. In development (when a TTY is attached) it
|
||||||
|
// logs with colors, but to a file it wouldn't. You can easily implement your
|
||||||
|
// own that implements the `Formatter` interface, see the `README` or included
|
||||||
|
// formatters for examples.
|
||||||
|
Formatter Formatter
|
||||||
|
// The logging level the logger should log at. This is typically (and defaults
|
||||||
|
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
||||||
|
// logged. `logrus.Debug` is useful in
|
||||||
|
Level Level
|
||||||
|
// Used to sync writing to the log. Locking is enabled by Default
|
||||||
|
mu MutexWrap
|
||||||
|
// Reusable empty entry
|
||||||
|
entryPool sync.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
type MutexWrap struct {
|
||||||
|
lock sync.Mutex
|
||||||
|
disabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mw *MutexWrap) Lock() {
|
||||||
|
if !mw.disabled {
|
||||||
|
mw.lock.Lock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mw *MutexWrap) Unlock() {
|
||||||
|
if !mw.disabled {
|
||||||
|
mw.lock.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mw *MutexWrap) Disable() {
|
||||||
|
mw.disabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new logger. Configuration should be set by changing `Formatter`,
|
||||||
|
// `Out` and `Hooks` directly on the default logger instance. You can also just
|
||||||
|
// instantiate your own:
|
||||||
|
//
|
||||||
|
// var log = &Logger{
|
||||||
|
// Out: os.Stderr,
|
||||||
|
// Formatter: new(JSONFormatter),
|
||||||
|
// Hooks: make(LevelHooks),
|
||||||
|
// Level: logrus.DebugLevel,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// It's recommended to make this a global instance called `log`.
|
||||||
|
func New() *Logger {
|
||||||
|
return &Logger{
|
||||||
|
Out: os.Stderr,
|
||||||
|
Formatter: new(TextFormatter),
|
||||||
|
Hooks: make(LevelHooks),
|
||||||
|
Level: InfoLevel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) newEntry() *Entry {
|
||||||
|
entry, ok := logger.entryPool.Get().(*Entry)
|
||||||
|
if ok {
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
return NewEntry(logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) releaseEntry(entry *Entry) {
|
||||||
|
logger.entryPool.Put(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a field to the log entry, note that it doesn't log until you call
|
||||||
|
// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry.
|
||||||
|
// If you want multiple fields, use `WithFields`.
|
||||||
|
func (logger *Logger) WithField(key string, value interface{}) *Entry {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
defer logger.releaseEntry(entry)
|
||||||
|
return entry.WithField(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a struct of fields to the log entry. All it does is call `WithField` for
|
||||||
|
// each `Field`.
|
||||||
|
func (logger *Logger) WithFields(fields Fields) *Entry {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
defer logger.releaseEntry(entry)
|
||||||
|
return entry.WithFields(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an error as single field to the log entry. All it does is call
|
||||||
|
// `WithError` for the given `error`.
|
||||||
|
func (logger *Logger) WithError(err error) *Entry {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
defer logger.releaseEntry(entry)
|
||||||
|
return entry.WithError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
||||||
|
if logger.Level >= DebugLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Debugf(format, args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Infof(format string, args ...interface{}) {
|
||||||
|
if logger.Level >= InfoLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Infof(format, args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Printf(format string, args ...interface{}) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Printf(format, args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
||||||
|
if logger.Level >= WarnLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Warnf(format, args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
||||||
|
if logger.Level >= WarnLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Warnf(format, args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
||||||
|
if logger.Level >= ErrorLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Errorf(format, args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
||||||
|
if logger.Level >= FatalLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Fatalf(format, args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
||||||
|
if logger.Level >= PanicLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Panicf(format, args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Debug(args ...interface{}) {
|
||||||
|
if logger.Level >= DebugLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Debug(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Info(args ...interface{}) {
|
||||||
|
if logger.Level >= InfoLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Info(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Print(args ...interface{}) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Info(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warn(args ...interface{}) {
|
||||||
|
if logger.Level >= WarnLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Warn(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warning(args ...interface{}) {
|
||||||
|
if logger.Level >= WarnLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Warn(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Error(args ...interface{}) {
|
||||||
|
if logger.Level >= ErrorLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Error(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Fatal(args ...interface{}) {
|
||||||
|
if logger.Level >= FatalLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Fatal(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Panic(args ...interface{}) {
|
||||||
|
if logger.Level >= PanicLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Panic(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Debugln(args ...interface{}) {
|
||||||
|
if logger.Level >= DebugLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Debugln(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Infoln(args ...interface{}) {
|
||||||
|
if logger.Level >= InfoLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Infoln(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Println(args ...interface{}) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Println(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warnln(args ...interface{}) {
|
||||||
|
if logger.Level >= WarnLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Warnln(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warningln(args ...interface{}) {
|
||||||
|
if logger.Level >= WarnLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Warnln(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Errorln(args ...interface{}) {
|
||||||
|
if logger.Level >= ErrorLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Errorln(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Fatalln(args ...interface{}) {
|
||||||
|
if logger.Level >= FatalLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Fatalln(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Panicln(args ...interface{}) {
|
||||||
|
if logger.Level >= PanicLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Panicln(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//When file is opened with appending mode, it's safe to
|
||||||
|
//write concurrently to a file (within 4k message on Linux).
|
||||||
|
//In these cases user can choose to disable the lock.
|
||||||
|
func (logger *Logger) SetNoLock() {
|
||||||
|
logger.mu.Disable()
|
||||||
|
}
|
143
vendor/github.com/Sirupsen/logrus/logrus.go
generated
vendored
Normal file
143
vendor/github.com/Sirupsen/logrus/logrus.go
generated
vendored
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fields type, used to pass to `WithFields`.
|
||||||
|
type Fields map[string]interface{}
|
||||||
|
|
||||||
|
// Level type
|
||||||
|
type Level uint8
|
||||||
|
|
||||||
|
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
|
||||||
|
func (level Level) String() string {
|
||||||
|
switch level {
|
||||||
|
case DebugLevel:
|
||||||
|
return "debug"
|
||||||
|
case InfoLevel:
|
||||||
|
return "info"
|
||||||
|
case WarnLevel:
|
||||||
|
return "warning"
|
||||||
|
case ErrorLevel:
|
||||||
|
return "error"
|
||||||
|
case FatalLevel:
|
||||||
|
return "fatal"
|
||||||
|
case PanicLevel:
|
||||||
|
return "panic"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseLevel takes a string level and returns the Logrus log level constant.
|
||||||
|
func ParseLevel(lvl string) (Level, error) {
|
||||||
|
switch strings.ToLower(lvl) {
|
||||||
|
case "panic":
|
||||||
|
return PanicLevel, nil
|
||||||
|
case "fatal":
|
||||||
|
return FatalLevel, nil
|
||||||
|
case "error":
|
||||||
|
return ErrorLevel, nil
|
||||||
|
case "warn", "warning":
|
||||||
|
return WarnLevel, nil
|
||||||
|
case "info":
|
||||||
|
return InfoLevel, nil
|
||||||
|
case "debug":
|
||||||
|
return DebugLevel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var l Level
|
||||||
|
return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A constant exposing all logging levels
|
||||||
|
var AllLevels = []Level{
|
||||||
|
PanicLevel,
|
||||||
|
FatalLevel,
|
||||||
|
ErrorLevel,
|
||||||
|
WarnLevel,
|
||||||
|
InfoLevel,
|
||||||
|
DebugLevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are the different logging levels. You can set the logging level to log
|
||||||
|
// on your instance of logger, obtained with `logrus.New()`.
|
||||||
|
const (
|
||||||
|
// PanicLevel level, highest level of severity. Logs and then calls panic with the
|
||||||
|
// message passed to Debug, Info, ...
|
||||||
|
PanicLevel Level = iota
|
||||||
|
// FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the
|
||||||
|
// logging level is set to Panic.
|
||||||
|
FatalLevel
|
||||||
|
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
|
||||||
|
// Commonly used for hooks to send errors to an error tracking service.
|
||||||
|
ErrorLevel
|
||||||
|
// WarnLevel level. Non-critical entries that deserve eyes.
|
||||||
|
WarnLevel
|
||||||
|
// InfoLevel level. General operational entries about what's going on inside the
|
||||||
|
// application.
|
||||||
|
InfoLevel
|
||||||
|
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
|
||||||
|
DebugLevel
|
||||||
|
)
|
||||||
|
|
||||||
|
// Won't compile if StdLogger can't be realized by a log.Logger
|
||||||
|
var (
|
||||||
|
_ StdLogger = &log.Logger{}
|
||||||
|
_ StdLogger = &Entry{}
|
||||||
|
_ StdLogger = &Logger{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// StdLogger is what your logrus-enabled library should take, that way
|
||||||
|
// it'll accept a stdlib logger and a logrus logger. There's no standard
|
||||||
|
// interface, this is the closest we get, unfortunately.
|
||||||
|
type StdLogger interface {
|
||||||
|
Print(...interface{})
|
||||||
|
Printf(string, ...interface{})
|
||||||
|
Println(...interface{})
|
||||||
|
|
||||||
|
Fatal(...interface{})
|
||||||
|
Fatalf(string, ...interface{})
|
||||||
|
Fatalln(...interface{})
|
||||||
|
|
||||||
|
Panic(...interface{})
|
||||||
|
Panicf(string, ...interface{})
|
||||||
|
Panicln(...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// The FieldLogger interface generalizes the Entry and Logger types
|
||||||
|
type FieldLogger interface {
|
||||||
|
WithField(key string, value interface{}) *Entry
|
||||||
|
WithFields(fields Fields) *Entry
|
||||||
|
WithError(err error) *Entry
|
||||||
|
|
||||||
|
Debugf(format string, args ...interface{})
|
||||||
|
Infof(format string, args ...interface{})
|
||||||
|
Printf(format string, args ...interface{})
|
||||||
|
Warnf(format string, args ...interface{})
|
||||||
|
Warningf(format string, args ...interface{})
|
||||||
|
Errorf(format string, args ...interface{})
|
||||||
|
Fatalf(format string, args ...interface{})
|
||||||
|
Panicf(format string, args ...interface{})
|
||||||
|
|
||||||
|
Debug(args ...interface{})
|
||||||
|
Info(args ...interface{})
|
||||||
|
Print(args ...interface{})
|
||||||
|
Warn(args ...interface{})
|
||||||
|
Warning(args ...interface{})
|
||||||
|
Error(args ...interface{})
|
||||||
|
Fatal(args ...interface{})
|
||||||
|
Panic(args ...interface{})
|
||||||
|
|
||||||
|
Debugln(args ...interface{})
|
||||||
|
Infoln(args ...interface{})
|
||||||
|
Println(args ...interface{})
|
||||||
|
Warnln(args ...interface{})
|
||||||
|
Warningln(args ...interface{})
|
||||||
|
Errorln(args ...interface{})
|
||||||
|
Fatalln(args ...interface{})
|
||||||
|
Panicln(args ...interface{})
|
||||||
|
}
|
8
vendor/github.com/Sirupsen/logrus/terminal_appengine.go
generated
vendored
Normal file
8
vendor/github.com/Sirupsen/logrus/terminal_appengine.go
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// +build appengine
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
||||||
|
func IsTerminal() bool {
|
||||||
|
return true
|
||||||
|
}
|
10
vendor/github.com/Sirupsen/logrus/terminal_bsd.go
generated
vendored
Normal file
10
vendor/github.com/Sirupsen/logrus/terminal_bsd.go
generated
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
// +build darwin freebsd openbsd netbsd dragonfly
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const ioctlReadTermios = syscall.TIOCGETA
|
||||||
|
|
||||||
|
type Termios syscall.Termios
|
14
vendor/github.com/Sirupsen/logrus/terminal_linux.go
generated
vendored
Normal file
14
vendor/github.com/Sirupsen/logrus/terminal_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// Based on ssh/terminal:
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const ioctlReadTermios = syscall.TCGETS
|
||||||
|
|
||||||
|
type Termios syscall.Termios
|
22
vendor/github.com/Sirupsen/logrus/terminal_notwindows.go
generated
vendored
Normal file
22
vendor/github.com/Sirupsen/logrus/terminal_notwindows.go
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// Based on ssh/terminal:
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build linux darwin freebsd openbsd netbsd dragonfly
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
||||||
|
func IsTerminal() bool {
|
||||||
|
fd := syscall.Stderr
|
||||||
|
var termios Termios
|
||||||
|
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
||||||
|
return err == 0
|
||||||
|
}
|
15
vendor/github.com/Sirupsen/logrus/terminal_solaris.go
generated
vendored
Normal file
15
vendor/github.com/Sirupsen/logrus/terminal_solaris.go
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// +build solaris,!appengine
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal() bool {
|
||||||
|
_, err := unix.IoctlGetTermios(int(os.Stdout.Fd()), unix.TCGETA)
|
||||||
|
return err == nil
|
||||||
|
}
|
27
vendor/github.com/Sirupsen/logrus/terminal_windows.go
generated
vendored
Normal file
27
vendor/github.com/Sirupsen/logrus/terminal_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
// Based on ssh/terminal:
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build windows,!appengine
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
|
||||||
|
var (
|
||||||
|
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
||||||
|
func IsTerminal() bool {
|
||||||
|
fd := syscall.Stderr
|
||||||
|
var st uint32
|
||||||
|
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
||||||
|
return r != 0 && e == 0
|
||||||
|
}
|
165
vendor/github.com/Sirupsen/logrus/text_formatter.go
generated
vendored
Normal file
165
vendor/github.com/Sirupsen/logrus/text_formatter.go
generated
vendored
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
nocolor = 0
|
||||||
|
red = 31
|
||||||
|
green = 32
|
||||||
|
yellow = 33
|
||||||
|
blue = 34
|
||||||
|
gray = 37
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
baseTimestamp time.Time
|
||||||
|
isTerminal bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
baseTimestamp = time.Now()
|
||||||
|
isTerminal = IsTerminal()
|
||||||
|
}
|
||||||
|
|
||||||
|
func miniTS() int {
|
||||||
|
return int(time.Since(baseTimestamp) / time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TextFormatter struct {
|
||||||
|
// Set to true to bypass checking for a TTY before outputting colors.
|
||||||
|
ForceColors bool
|
||||||
|
|
||||||
|
// Force disabling colors.
|
||||||
|
DisableColors bool
|
||||||
|
|
||||||
|
// Disable timestamp logging. useful when output is redirected to logging
|
||||||
|
// system that already adds timestamps.
|
||||||
|
DisableTimestamp bool
|
||||||
|
|
||||||
|
// Enable logging the full timestamp when a TTY is attached instead of just
|
||||||
|
// the time passed since beginning of execution.
|
||||||
|
FullTimestamp bool
|
||||||
|
|
||||||
|
// TimestampFormat to use for display when a full timestamp is printed
|
||||||
|
TimestampFormat string
|
||||||
|
|
||||||
|
// The fields are sorted by default for a consistent output. For applications
|
||||||
|
// that log extremely frequently and don't use the JSON formatter this may not
|
||||||
|
// be desired.
|
||||||
|
DisableSorting bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
|
var b *bytes.Buffer
|
||||||
|
var keys []string = make([]string, 0, len(entry.Data))
|
||||||
|
for k := range entry.Data {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !f.DisableSorting {
|
||||||
|
sort.Strings(keys)
|
||||||
|
}
|
||||||
|
if entry.Buffer != nil {
|
||||||
|
b = entry.Buffer
|
||||||
|
} else {
|
||||||
|
b = &bytes.Buffer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
prefixFieldClashes(entry.Data)
|
||||||
|
|
||||||
|
isColorTerminal := isTerminal && (runtime.GOOS != "windows")
|
||||||
|
isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors
|
||||||
|
|
||||||
|
timestampFormat := f.TimestampFormat
|
||||||
|
if timestampFormat == "" {
|
||||||
|
timestampFormat = DefaultTimestampFormat
|
||||||
|
}
|
||||||
|
if isColored {
|
||||||
|
f.printColored(b, entry, keys, timestampFormat)
|
||||||
|
} else {
|
||||||
|
if !f.DisableTimestamp {
|
||||||
|
f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
|
||||||
|
}
|
||||||
|
f.appendKeyValue(b, "level", entry.Level.String())
|
||||||
|
if entry.Message != "" {
|
||||||
|
f.appendKeyValue(b, "msg", entry.Message)
|
||||||
|
}
|
||||||
|
for _, key := range keys {
|
||||||
|
f.appendKeyValue(b, key, entry.Data[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteByte('\n')
|
||||||
|
return b.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) {
|
||||||
|
var levelColor int
|
||||||
|
switch entry.Level {
|
||||||
|
case DebugLevel:
|
||||||
|
levelColor = gray
|
||||||
|
case WarnLevel:
|
||||||
|
levelColor = yellow
|
||||||
|
case ErrorLevel, FatalLevel, PanicLevel:
|
||||||
|
levelColor = red
|
||||||
|
default:
|
||||||
|
levelColor = blue
|
||||||
|
}
|
||||||
|
|
||||||
|
levelText := strings.ToUpper(entry.Level.String())[0:4]
|
||||||
|
|
||||||
|
if !f.FullTimestamp {
|
||||||
|
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message)
|
||||||
|
}
|
||||||
|
for _, k := range keys {
|
||||||
|
v := entry.Data[k]
|
||||||
|
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%+v", levelColor, k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func needsQuoting(text string) bool {
|
||||||
|
for _, ch := range text {
|
||||||
|
if !((ch >= 'a' && ch <= 'z') ||
|
||||||
|
(ch >= 'A' && ch <= 'Z') ||
|
||||||
|
(ch >= '0' && ch <= '9') ||
|
||||||
|
ch == '-' || ch == '.') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
|
||||||
|
|
||||||
|
b.WriteString(key)
|
||||||
|
b.WriteByte('=')
|
||||||
|
|
||||||
|
switch value := value.(type) {
|
||||||
|
case string:
|
||||||
|
if !needsQuoting(value) {
|
||||||
|
b.WriteString(value)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(b, "%q", value)
|
||||||
|
}
|
||||||
|
case error:
|
||||||
|
errmsg := value.Error()
|
||||||
|
if !needsQuoting(errmsg) {
|
||||||
|
b.WriteString(errmsg)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(b, "%q", value)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fmt.Fprint(b, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteByte(' ')
|
||||||
|
}
|
53
vendor/github.com/Sirupsen/logrus/writer.go
generated
vendored
Normal file
53
vendor/github.com/Sirupsen/logrus/writer.go
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (logger *Logger) Writer() *io.PipeWriter {
|
||||||
|
return logger.WriterLevel(InfoLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) WriterLevel(level Level) *io.PipeWriter {
|
||||||
|
reader, writer := io.Pipe()
|
||||||
|
|
||||||
|
var printFunc func(args ...interface{})
|
||||||
|
switch level {
|
||||||
|
case DebugLevel:
|
||||||
|
printFunc = logger.Debug
|
||||||
|
case InfoLevel:
|
||||||
|
printFunc = logger.Info
|
||||||
|
case WarnLevel:
|
||||||
|
printFunc = logger.Warn
|
||||||
|
case ErrorLevel:
|
||||||
|
printFunc = logger.Error
|
||||||
|
case FatalLevel:
|
||||||
|
printFunc = logger.Fatal
|
||||||
|
case PanicLevel:
|
||||||
|
printFunc = logger.Panic
|
||||||
|
default:
|
||||||
|
printFunc = logger.Print
|
||||||
|
}
|
||||||
|
|
||||||
|
go logger.writerScanner(reader, printFunc)
|
||||||
|
runtime.SetFinalizer(writer, writerFinalizer)
|
||||||
|
|
||||||
|
return writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) {
|
||||||
|
scanner := bufio.NewScanner(reader)
|
||||||
|
for scanner.Scan() {
|
||||||
|
printFunc(scanner.Text())
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
logger.Errorf("Error while reading from Writer: %s", err)
|
||||||
|
}
|
||||||
|
reader.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func writerFinalizer(writer *io.PipeWriter) {
|
||||||
|
writer.Close()
|
||||||
|
}
|
0
vendor/github.com/Wessie/appdirs/.gitignore
generated
vendored
Normal file
0
vendor/github.com/Wessie/appdirs/.gitignore
generated
vendored
Normal file
18
vendor/github.com/Wessie/appdirs/LICENSE
generated
vendored
Normal file
18
vendor/github.com/Wessie/appdirs/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
Copyright (c) 2013 Wesley Bitter
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
3
vendor/github.com/Wessie/appdirs/README.md
generated
vendored
Normal file
3
vendor/github.com/Wessie/appdirs/README.md
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
This is a port of the excellent python module named the same, which can be found here [appdirs](https://github.com/ActiveState/appdirs).
|
||||||
|
|
||||||
|
The README and documentation in the original should be a good starting point. Documentation is currently lacking on the port itself, but the API is very similar to the python version.
|
80
vendor/github.com/Wessie/appdirs/appdirs.go
generated
vendored
Normal file
80
vendor/github.com/Wessie/appdirs/appdirs.go
generated
vendored
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
// A port of the excellent python module `appdirs`.
|
||||||
|
// See https://github.com/ActiveState/appdirs for the python version.
|
||||||
|
package appdirs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/user"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// App is a helper type to create easy access across your program to the appdirs
|
||||||
|
// functions.
|
||||||
|
//
|
||||||
|
// The *App type has 6 methods that map to the 6 functions exported by `appdirs`.
|
||||||
|
// All methods take no arguments, and supply the function it wraps with arguments
|
||||||
|
// pre-set in the struct on creation.
|
||||||
|
type App struct {
|
||||||
|
Name string
|
||||||
|
Author string
|
||||||
|
Version string
|
||||||
|
Roaming bool
|
||||||
|
Opinion bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new App helper that has various methods for receiving
|
||||||
|
// relevant directories for your application.
|
||||||
|
//
|
||||||
|
// The following defaults are used for the two fields not settable by New:
|
||||||
|
// Roaming: false, Opinion: true
|
||||||
|
//
|
||||||
|
// If you want to set these, create your own App struct by the usual means.
|
||||||
|
func New(name, author, version string) *App {
|
||||||
|
return &App{
|
||||||
|
Name: name,
|
||||||
|
Author: author,
|
||||||
|
Version: version,
|
||||||
|
Roaming: false,
|
||||||
|
Opinion: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserData returns the full path to the user-specific data directory
|
||||||
|
func (app *App) UserData() string {
|
||||||
|
return UserDataDir(app.Name, app.Author, app.Version, app.Roaming)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SiteData returns the full path to the user-shared data directory
|
||||||
|
func (app *App) SiteData() string {
|
||||||
|
return SiteDataDir(app.Name, app.Author, app.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SiteConfig returns the full path to the user-shared configuration directory
|
||||||
|
func (app *App) SiteConfig() string {
|
||||||
|
return SiteConfigDir(app.Name, app.Author, app.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserConfig returns the full path to the user-specific configuration directory
|
||||||
|
func (app *App) UserConfig() string {
|
||||||
|
return UserConfigDir(app.Name, app.Author, app.Version, app.Roaming)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserCache returns the full path to the user-specific cache directory
|
||||||
|
func (app *App) UserCache() string {
|
||||||
|
return UserCacheDir(app.Name, app.Author, app.Version, app.Opinion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserLog returns the full path to the user-specific log directory
|
||||||
|
func (app *App) UserLog() string {
|
||||||
|
return UserLogDir(app.Name, app.Author, app.Version, app.Opinion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpandUser is a helper function that expands the first '~' it finds in the
|
||||||
|
// passed path with the home directory of the current user.
|
||||||
|
//
|
||||||
|
// Note: This only works on environments similar to bash.
|
||||||
|
func ExpandUser(path string) string {
|
||||||
|
if u, err := user.Current(); err == nil {
|
||||||
|
return strings.Replace(path, "~", u.HomeDir, -1)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
63
vendor/github.com/Wessie/appdirs/appdirs_darwin.go
generated
vendored
Normal file
63
vendor/github.com/Wessie/appdirs/appdirs_darwin.go
generated
vendored
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
package appdirs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func userDataDir(name, author, version string, roaming bool) (path string) {
|
||||||
|
path = ExpandUser("~/Library/Application Support")
|
||||||
|
|
||||||
|
if name != "" {
|
||||||
|
path = filepath.Join(path, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if name != "" && version != "" {
|
||||||
|
path = filepath.Join(path, version)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func siteDataDir(name, author, version string) (path string) {
|
||||||
|
path = ExpandUser("/Library/Application Support")
|
||||||
|
|
||||||
|
if name != "" {
|
||||||
|
path = filepath.Join(path, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if name != "" && version != "" {
|
||||||
|
path = filepath.Join(path, version)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func userConfigDir(name, author, version string, roaming bool) (path string) {
|
||||||
|
return UserDataDir(name, author, version, roaming)
|
||||||
|
}
|
||||||
|
|
||||||
|
func siteConfigDir(name, author, version string) (path string) {
|
||||||
|
return SiteDataDir(name, author, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func userCacheDir(name, author, version string, opinion bool) (path string) {
|
||||||
|
path = ExpandUser("~/Library/Caches")
|
||||||
|
|
||||||
|
if name != "" {
|
||||||
|
path = filepath.Join(path, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if name != "" && version != "" {
|
||||||
|
path = filepath.Join(path, version)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func userLogDir(name, author, version string, opinion bool) (path string) {
|
||||||
|
path = ExpandUser("~/Library/Logs")
|
||||||
|
|
||||||
|
path = filepath.Join(path, name)
|
||||||
|
|
||||||
|
if name != "" && version != "" {
|
||||||
|
path = filepath.Join(path, version)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
102
vendor/github.com/Wessie/appdirs/appdirs_unix.go
generated
vendored
Normal file
102
vendor/github.com/Wessie/appdirs/appdirs_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
// +build linux freebsd netbsd openbsd
|
||||||
|
|
||||||
|
package appdirs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func userDataDir(name, author, version string, roaming bool) (path string) {
|
||||||
|
if path = os.Getenv("XDG_DATA_HOME"); path == "" {
|
||||||
|
path = ExpandUser("~/.local/share")
|
||||||
|
}
|
||||||
|
|
||||||
|
if name != "" {
|
||||||
|
path = filepath.Join(path, name, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func SiteDataDirs(name, author, version string) (paths []string) {
|
||||||
|
var path string
|
||||||
|
|
||||||
|
if path = os.Getenv("XDG_DATA_DIRS"); path == "" {
|
||||||
|
paths = []string{"/usr/local/share", "/usr/share"}
|
||||||
|
} else {
|
||||||
|
paths = filepath.SplitList(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, path := range paths {
|
||||||
|
path = ExpandUser(path)
|
||||||
|
|
||||||
|
if name != "" {
|
||||||
|
path = filepath.Join(path, name, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
paths[i] = path
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
|
||||||
|
func siteDataDir(name, author, version string) (path string) {
|
||||||
|
return SiteDataDirs(name, author, version)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func userConfigDir(name, author, version string, roaming bool) (path string) {
|
||||||
|
if path = os.Getenv("XDG_CONFIG_HOME"); path == "" {
|
||||||
|
path = ExpandUser("~/.config")
|
||||||
|
}
|
||||||
|
|
||||||
|
if name != "" {
|
||||||
|
path = filepath.Join(path, name, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func SiteConfigDirs(name, author, version string) (paths []string) {
|
||||||
|
var path string
|
||||||
|
|
||||||
|
if path = os.Getenv("XDG_CONFIG_DIRS"); path == "" {
|
||||||
|
paths = []string{"/etc/xdg"}
|
||||||
|
} else {
|
||||||
|
paths = filepath.SplitList(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, path := range paths {
|
||||||
|
path = ExpandUser(path)
|
||||||
|
|
||||||
|
if name != "" {
|
||||||
|
path = filepath.Join(path, name, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
paths[i] = path
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
|
||||||
|
func siteConfigDir(name, author, version string) (path string) {
|
||||||
|
return SiteConfigDirs(name, author, version)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func userCacheDir(name, author, version string, opinion bool) (path string) {
|
||||||
|
if path = os.Getenv("XDG_CACHE_HOME"); path == "" {
|
||||||
|
path = ExpandUser("~/.cache")
|
||||||
|
}
|
||||||
|
|
||||||
|
if name != "" {
|
||||||
|
path = filepath.Join(path, name, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func userLogDir(name, author, version string, opinion bool) (path string) {
|
||||||
|
path = UserCacheDir(name, author, version, opinion)
|
||||||
|
|
||||||
|
return filepath.Join(path, "log")
|
||||||
|
}
|
171
vendor/github.com/Wessie/appdirs/appdirs_windows.go
generated
vendored
Normal file
171
vendor/github.com/Wessie/appdirs/appdirs_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
package appdirs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
shell32, _ = syscall.LoadLibrary("shell32.dll")
|
||||||
|
getKnownFolderPath, _ = syscall.GetProcAddress(shell32, "SHGetKnownFolderPath")
|
||||||
|
|
||||||
|
ole32, _ = syscall.LoadLibrary("Ole32.dll")
|
||||||
|
coTaskMemFree, _ = syscall.GetProcAddress(ole32, "CoTaskMemFree")
|
||||||
|
)
|
||||||
|
|
||||||
|
// These are KNOWNFOLDERID constants that are passed to GetKnownFolderPath
|
||||||
|
var (
|
||||||
|
rfidLocalAppData = syscall.GUID{
|
||||||
|
0xf1b32785,
|
||||||
|
0x6fba,
|
||||||
|
0x4fcf,
|
||||||
|
[8]byte{0x9d, 0x55, 0x7b, 0x8e, 0x7f, 0x15, 0x70, 0x91},
|
||||||
|
}
|
||||||
|
rfidRoamingAppData = syscall.GUID{
|
||||||
|
0x3eb685db,
|
||||||
|
0x65f9,
|
||||||
|
0x4cf6,
|
||||||
|
[8]byte{0xa0, 0x3a, 0xe3, 0xef, 0x65, 0x72, 0x9f, 0x3d},
|
||||||
|
}
|
||||||
|
rfidProgramData = syscall.GUID{
|
||||||
|
0x62ab5d82,
|
||||||
|
0xfdc1,
|
||||||
|
0x4dc3,
|
||||||
|
[8]byte{0xa9, 0xdd, 0x07, 0x0d, 0x1d, 0x49, 0x5d, 0x97},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func userDataDir(name, author, version string, roaming bool) (path string) {
|
||||||
|
if author == "" {
|
||||||
|
author = name
|
||||||
|
}
|
||||||
|
|
||||||
|
var rfid syscall.GUID
|
||||||
|
if roaming {
|
||||||
|
rfid = rfidRoamingAppData
|
||||||
|
} else {
|
||||||
|
rfid = rfidLocalAppData
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err := getFolderPath(rfid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if path, err = filepath.Abs(path); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if name != "" {
|
||||||
|
path = filepath.Join(path, author, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if name != "" && version != "" {
|
||||||
|
path = filepath.Join(path, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func siteDataDir(name, author, version string) (path string) {
|
||||||
|
path, err := getFolderPath(rfidProgramData)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if path, err = filepath.Abs(path); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if author == "" {
|
||||||
|
author = name
|
||||||
|
}
|
||||||
|
|
||||||
|
if name != "" {
|
||||||
|
path = filepath.Join(path, author, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if name != "" && version != "" {
|
||||||
|
path = filepath.Join(path, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func userConfigDir(name, author, version string, roaming bool) string {
|
||||||
|
return UserDataDir(name, author, version, roaming)
|
||||||
|
}
|
||||||
|
|
||||||
|
func siteConfigDir(name, author, version string) (path string) {
|
||||||
|
return SiteDataDir(name, author, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func userCacheDir(name, author, version string, opinion bool) (path string) {
|
||||||
|
if author == "" {
|
||||||
|
author = name
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err := getFolderPath(rfidLocalAppData)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if path, err = filepath.Abs(path); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if name != "" {
|
||||||
|
path = filepath.Join(path, author, name)
|
||||||
|
if opinion {
|
||||||
|
path = filepath.Join(path, "Cache")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if name != "" && version != "" {
|
||||||
|
path = filepath.Join(path, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func userLogDir(name, author, version string, opinion bool) (path string) {
|
||||||
|
path = UserDataDir(name, author, version, false)
|
||||||
|
|
||||||
|
if opinion {
|
||||||
|
path = filepath.Join(path, "Logs")
|
||||||
|
}
|
||||||
|
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFolderPath(rfid syscall.GUID) (string, error) {
|
||||||
|
var res uintptr
|
||||||
|
|
||||||
|
ret, _, callErr := syscall.Syscall6(
|
||||||
|
uintptr(getKnownFolderPath),
|
||||||
|
4,
|
||||||
|
uintptr(unsafe.Pointer(&rfid)),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
uintptr(unsafe.Pointer(&res)),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
|
||||||
|
if callErr != 0 && ret != 0 {
|
||||||
|
return "", callErr
|
||||||
|
}
|
||||||
|
|
||||||
|
defer syscall.Syscall(uintptr(coTaskMemFree), 1, res, 0, 0)
|
||||||
|
return ucs2PtrToString(res), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ucs2PtrToString(p uintptr) string {
|
||||||
|
ptr := (*[4096]uint16)(unsafe.Pointer(p))
|
||||||
|
|
||||||
|
return syscall.UTF16ToString((*ptr)[:])
|
||||||
|
}
|
108
vendor/github.com/Wessie/appdirs/doc.go
generated
vendored
Normal file
108
vendor/github.com/Wessie/appdirs/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
// appdirs project doc.go
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is a port of a python module used for finding what directory you 'should'
|
||||||
|
be using for saving your application data such as configuration, cache files or
|
||||||
|
other files.
|
||||||
|
|
||||||
|
The location of these directories is often hard to get right. The original python
|
||||||
|
module set out to change this into a simple API that returns you the exact
|
||||||
|
directory you need. This is a port of it to Go.
|
||||||
|
|
||||||
|
Depending on platform, this package exports you at the least 6 functions that
|
||||||
|
return various system directories. And one helper struct type that combines the
|
||||||
|
functions into methods for less arguments in your code.
|
||||||
|
|
||||||
|
Each function defined accepts a number of arguments, each argument is optional
|
||||||
|
and can be left to the types default value if omitted. Often the function will
|
||||||
|
ignore arguments if the name given is empty.
|
||||||
|
|
||||||
|
Passing in all default values into any of the functions will return you the base
|
||||||
|
directory without any of the arguments appended to it.
|
||||||
|
*/
|
||||||
|
package appdirs
|
||||||
|
|
||||||
|
// UserDataDir returns the full path to the user-specific data directory.
|
||||||
|
//
|
||||||
|
// This function uses XDG_DATA_HOME as defined by the XDG spec on *nix like systems.
|
||||||
|
//
|
||||||
|
// Examples of return values:
|
||||||
|
// Mac OS X: ~/Library/Application Support/<AppName>
|
||||||
|
// Unix: ~/.local/share/<AppName> # or in $XDG_DATA_HOME, if defined
|
||||||
|
// Win XP (not roaming): C:\Documents and Settings\<username>\Application Data\<AppAuthor>\<AppName>
|
||||||
|
// Win XP (roaming): C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>
|
||||||
|
// Win 7 (not roaming): C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>
|
||||||
|
// Win 7 (roaming): C:\Users\<username>\AppData\Roaming\<AppAuthor>\<AppName>
|
||||||
|
func UserDataDir(name, author, version string, roaming bool) string {
|
||||||
|
return userDataDir(name, author, version, roaming)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SiteDataDir returns the full path to the user-shared data directory.
|
||||||
|
//
|
||||||
|
// This function uses XDG_DATA_DIRS[0] as by the XDG spec on *nix like systems.
|
||||||
|
//
|
||||||
|
// Examples of return values:
|
||||||
|
// Mac OS X: /Library/Application Support/<AppName>
|
||||||
|
// Unix: /usr/local/share/<AppName> or /usr/share/<AppName>
|
||||||
|
// Win XP: C:\Documents and Settings\All Users\Application Data\<AppAuthor>\<AppName>
|
||||||
|
// Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
|
||||||
|
// Win 7: C:\ProgramData\<AppAuthor>\<AppName> # Hidden, but writeable on Win 7.
|
||||||
|
//
|
||||||
|
// WARNING: Do not use this on Windows Vista, See the note above.
|
||||||
|
func SiteDataDir(name, author, version string) string {
|
||||||
|
return siteDataDir(name, author, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserConfigDir returns the full path to the user-specific configuration directory
|
||||||
|
//
|
||||||
|
// This function uses XDG_CONFIG_HOME as by the XDG spec on *nix like systems.
|
||||||
|
//
|
||||||
|
// Examples of return values:
|
||||||
|
// Mac OS X: same as UserDataDir
|
||||||
|
// Unix: ~/.config/<AppName> # or in $XDG_CONFIG_HOME, if defined
|
||||||
|
// Win *: same as UserDataDir
|
||||||
|
func UserConfigDir(name, author, version string, roaming bool) string {
|
||||||
|
return userConfigDir(name, author, version, roaming)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SiteConfigDir returns the full path to the user-shared data directory.
|
||||||
|
//
|
||||||
|
// This function uses XDG_CONFIG_DIRS[0] as by the XDG spec on *nix like systems.
|
||||||
|
//
|
||||||
|
// Examples of return values:
|
||||||
|
// Mac OS X: same as SiteDataDir
|
||||||
|
// Unix: /etc/xdg/<AppName> or $XDG_CONFIG_DIRS[i]/<AppName> for each value in $XDG_CONFIG_DIRS
|
||||||
|
// Win *: same as SiteDataDir
|
||||||
|
// Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
|
||||||
|
//
|
||||||
|
// WARNING: Do not use this on Windows Vista, see the note above.
|
||||||
|
func SiteConfigDir(name, author, version string) string {
|
||||||
|
return siteConfigDir(name, author, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserCacheDir returns the full path to the user-specific cache directory.
|
||||||
|
//
|
||||||
|
// The opinion argument will append 'Cache' to the base directory if set to true.
|
||||||
|
//
|
||||||
|
// Examples of return values:
|
||||||
|
// Mac OS X: ~/Library/Caches/<AppName>
|
||||||
|
// Unix: ~/.cache/<AppName> (XDG default)
|
||||||
|
// Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Cache
|
||||||
|
// Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Cache
|
||||||
|
func UserCacheDir(name, author, version string, opinion bool) string {
|
||||||
|
return userCacheDir(name, author, version, opinion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserLogDir returns the full path to the user-specific log directory.
|
||||||
|
//
|
||||||
|
// The opinion argument will append either 'Logs' (windows) or 'log' (unix) to
|
||||||
|
// the base directory when set to true.
|
||||||
|
//
|
||||||
|
// Examples of return values:
|
||||||
|
// Mac OS X: ~/Library/Logs/<AppName>
|
||||||
|
// Unix: ~/.cache/<AppName>/log # or under $XDG_CACHE_HOME if defined
|
||||||
|
// Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Logs
|
||||||
|
// Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Logs
|
||||||
|
func UserLogDir(name, author, version string, opinion bool) string {
|
||||||
|
return userLogDir(name, author, version, opinion)
|
||||||
|
}
|
3
vendor/github.com/flopp/go-coordsparser/.travis.yml
generated
vendored
Normal file
3
vendor/github.com/flopp/go-coordsparser/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.5
|
21
vendor/github.com/flopp/go-coordsparser/LICENSE
generated
vendored
Normal file
21
vendor/github.com/flopp/go-coordsparser/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 Florian Pigorsch
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
57
vendor/github.com/flopp/go-coordsparser/README.md
generated
vendored
Normal file
57
vendor/github.com/flopp/go-coordsparser/README.md
generated
vendored
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
[![GoDoc](https://godoc.org/github.com/flopp/go-coordsparser?status.svg)](https://godoc.org/github.com/flopp/go-coordsparser)
|
||||||
|
[![Build Status](https://travis-ci.org/flopp/go-coordsparser.svg)](https://travis-ci.org/flopp/go-coordsparser)
|
||||||
|
[![Go Report Card](https://goreportcard.com/badge/flopp/go-coordsparser)](https://goreportcard.com/report/flopp/go-coordsparser)
|
||||||
|
[![License MIT](https://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat)](https://github.com/flopp/go-coordsparser)
|
||||||
|
|
||||||
|
# go-coordsparser
|
||||||
|
A library for parsing (geographic) coordinates in go (golang)
|
||||||
|
|
||||||
|
# What?
|
||||||
|
|
||||||
|
go-coordsparser allows you to parse lat/lng coordinates from strings in various popular formats. Currently supported formats are:
|
||||||
|
|
||||||
|
- **D** (decimal degrees), e.g. `40.76, -73.984`
|
||||||
|
- **HD** (hemisphere prefix, decimal degrees), e.g. `N 40.76 W 73.984`
|
||||||
|
- **HDM** (hemisphere prefix, integral degrees, decimal minutes), e.g. `N 40 45.600 W 73 59.040`
|
||||||
|
- **HDMS** (hemisphere prefix, integral degrees, integral minutes, decimal seconds), e.g. `N 40 45 36.0 W 73 59 02.4`
|
||||||
|
|
||||||
|
# How?
|
||||||
|
|
||||||
|
### Installing
|
||||||
|
Installing the library is as easy as
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ go get github.com/flopp/go-coordsparser
|
||||||
|
```
|
||||||
|
|
||||||
|
The package can then be used through an
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/flopp/go-coordsparser"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using
|
||||||
|
go-coordsparser provides several functions for parsing coordinate strings: a general parsing function `coordsparser.Parse`, which accepts all supported formats, as well as specialized functions `coordsparser.ParseD`, `coordsparser.ParseHD`, `coordsparser.ParseHDM`, `coordsparser.ParseHDMS` for the corresponding coordinate formats.
|
||||||
|
|
||||||
|
Each function takes a single string as a parameter and returns an idiomatic `lat, lng, error` triple, where `lat` and `lng` are decimal degrees (`float64`) with -90 ≤ `lat` ≤ 90 and -180 ≤ `lng` ≤ 180.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// parse any format
|
||||||
|
s1 := "..."
|
||||||
|
lat1, lng1, err := coordsparser.Parse(s1)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Errorf("Cannot parse coordinates string:", s1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse specific format, e.g. HDM
|
||||||
|
s2 := "..."
|
||||||
|
lat2, lng2, err = coordsparser.ParseHDM(s2)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Errorf("Cannot parse coordinates string:", s2)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# License
|
||||||
|
Copyright 2016 Florian Pigorsch. All rights reserved.
|
||||||
|
|
||||||
|
Use of this source code is governed by a MIT-style license that can be found in the LICENSE file.
|
172
vendor/github.com/flopp/go-coordsparser/coordsparser.go
generated
vendored
Normal file
172
vendor/github.com/flopp/go-coordsparser/coordsparser.go
generated
vendored
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
// Copyright 2016 Florian Pigorsch. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package coordsparser is a library for parsing (geographic) coordinates in various string formats
|
||||||
|
package coordsparser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parse parses a coordinate string and returns a lat/lng pair or an error
|
||||||
|
func Parse(s string) (float64, float64, error) {
|
||||||
|
lat, lng, err := ParseD(s)
|
||||||
|
if err == nil {
|
||||||
|
return lat, lng, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lat, lng, err = ParseHD(s)
|
||||||
|
if err == nil {
|
||||||
|
return lat, lng, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lat, lng, err = ParseHDM(s)
|
||||||
|
if err == nil {
|
||||||
|
return lat, lng, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lat, lng, err = ParseHDMS(s)
|
||||||
|
if err == nil {
|
||||||
|
return lat, lng, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, 0, fmt.Errorf("Cannot parse coordinates: %s", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseD parses a coordinate string of the form "D.DDDD D.DDDD" and returns a lat/lng pair or an error
|
||||||
|
func ParseD(s string) (float64, float64, error) {
|
||||||
|
re := regexp.MustCompile(`^\s*([+-]?[\d\.]+)\s*(,|;|:|\s)\s*([+-]?[\d\.]+)\s*$`)
|
||||||
|
|
||||||
|
matches := re.FindStringSubmatch(s)
|
||||||
|
if matches == nil {
|
||||||
|
return 0, 0, fmt.Errorf("Cannot parse 'D' string: %s", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
lat, err := strconv.ParseFloat(matches[1], 64)
|
||||||
|
if err != nil || lat < -90 || lat > 90 {
|
||||||
|
return 0, 0, fmt.Errorf("Cannot parse 'D' string: %s", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
lng, err := strconv.ParseFloat(matches[3], 64)
|
||||||
|
if err != nil || lng < -180 || lng > 180 {
|
||||||
|
return 0, 0, fmt.Errorf("Cannot parse 'D' string: %s", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return lat, lng, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseHD parses a coordinate string of the form "H D.DDDD H D.DDDD" and returns a lat/lng pair or an error
|
||||||
|
func ParseHD(s string) (float64, float64, error) {
|
||||||
|
re := regexp.MustCompile(`^\s*([NnSs])\s*([\d\.]+)\s+([EeWw])\s*([\d\.]+)\s*$`)
|
||||||
|
|
||||||
|
matches := re.FindStringSubmatch(s)
|
||||||
|
if matches == nil {
|
||||||
|
return 0, 0, fmt.Errorf("Cannot parse 'HD' string: %s", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
lat, err := strconv.ParseFloat(matches[2], 64)
|
||||||
|
if err != nil || lat > 90 {
|
||||||
|
return 0, 0, fmt.Errorf("Cannot parse 'HD' string: %s", s)
|
||||||
|
}
|
||||||
|
if matches[1] == "S" || matches[1] == "s" {
|
||||||
|
lat = -lat
|
||||||
|
}
|
||||||
|
|
||||||
|
lng, err := strconv.ParseFloat(matches[4], 64)
|
||||||
|
if err != nil || lng > 180 {
|
||||||
|
return 0, 0, fmt.Errorf("Cannot parse 'HD' string: %s", s)
|
||||||
|
}
|
||||||
|
if matches[3] == "W" || matches[3] == "w" {
|
||||||
|
lng = -lng
|
||||||
|
}
|
||||||
|
|
||||||
|
return lat, lng, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseHDM parses a coordinate string of the form "H D M.MMM H D M.MMM" and returns a lat/lng pair or an error
|
||||||
|
func ParseHDM(s string) (float64, float64, error) {
|
||||||
|
re := regexp.MustCompile(`^\s*([NnSs])\s*([\d]+)\s+([\d.]+)\s+([EeWw])\s*([\d]+)\s+([\d.]+)\s*$`)
|
||||||
|
|
||||||
|
matches := re.FindStringSubmatch(s)
|
||||||
|
if matches == nil {
|
||||||
|
return 0, 0, fmt.Errorf("Cannot parse 'HDM' string: %s", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
latDeg, err := strconv.ParseFloat(matches[2], 64)
|
||||||
|
if err != nil || latDeg > 90 {
|
||||||
|
return 0, 0, fmt.Errorf("Cannot parse 'HDM' string: %s", s)
|
||||||
|
}
|
||||||
|
latMin, err := strconv.ParseFloat(matches[3], 64)
|
||||||
|
if err != nil || latMin >= 60 {
|
||||||
|
return 0, 0, fmt.Errorf("Cannot parse 'HDM' string: %s", s)
|
||||||
|
}
|
||||||
|
lat := latDeg + latMin/60.0
|
||||||
|
if matches[1] == "S" || matches[1] == "s" {
|
||||||
|
lat = -lat
|
||||||
|
}
|
||||||
|
|
||||||
|
lngDeg, err := strconv.ParseFloat(matches[5], 64)
|
||||||
|
if err != nil || lngDeg > 180 {
|
||||||
|
return 0, 0, fmt.Errorf("Cannot parse 'HDM' string: %s", s)
|
||||||
|
}
|
||||||
|
lngMin, err := strconv.ParseFloat(matches[6], 64)
|
||||||
|
if err != nil || lngMin >= 60 {
|
||||||
|
return 0, 0, fmt.Errorf("Cannot parse 'HDM' string: %s", s)
|
||||||
|
}
|
||||||
|
lng := lngDeg + lngMin/60.0
|
||||||
|
if matches[4] == "W" || matches[4] == "w" {
|
||||||
|
lng = -lng
|
||||||
|
}
|
||||||
|
|
||||||
|
return lat, lng, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseHDMS parses a coordinate string of the form "H D M S.SSS H D M S.SSS" and returns a lat/lng pair or an error
|
||||||
|
func ParseHDMS(s string) (float64, float64, error) {
|
||||||
|
re := regexp.MustCompile(`^\s*([NnSs])\s*([\d]+)\s+([\d]+)\s+([\d.]+)\s+([EeWw])\s*([\d]+)\s+([\d]+)\s+([\d.]+)\s*$`)
|
||||||
|
|
||||||
|
matches := re.FindStringSubmatch(s)
|
||||||
|
if matches == nil {
|
||||||
|
return 0, 0, fmt.Errorf("Cannot parse 'HDMS' string: %s", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
latDeg, err := strconv.ParseFloat(matches[2], 64)
|
||||||
|
if err != nil || latDeg > 90 {
|
||||||
|
return 0, 0, fmt.Errorf("Cannot parse 'HDMS' string: %s", s)
|
||||||
|
}
|
||||||
|
latMin, err := strconv.ParseFloat(matches[3], 64)
|
||||||
|
if err != nil || latMin >= 60 {
|
||||||
|
return 0, 0, fmt.Errorf("Cannot parse 'HDMS' string: %s", s)
|
||||||
|
}
|
||||||
|
latSec, err := strconv.ParseFloat(matches[4], 64)
|
||||||
|
if err != nil || latSec >= 60 {
|
||||||
|
return 0, 0, fmt.Errorf("Cannot parse 'HDMS' string: %s", s)
|
||||||
|
}
|
||||||
|
lat := latDeg + latMin/60.0 + latSec/3600.0
|
||||||
|
if matches[1] == "S" || matches[1] == "s" {
|
||||||
|
lat = -lat
|
||||||
|
}
|
||||||
|
|
||||||
|
lngDeg, err := strconv.ParseFloat(matches[6], 64)
|
||||||
|
if err != nil || lngDeg > 180 {
|
||||||
|
return 0, 0, fmt.Errorf("Cannot parse 'HDMS' string: %s", s)
|
||||||
|
}
|
||||||
|
lngMin, err := strconv.ParseFloat(matches[7], 64)
|
||||||
|
if err != nil || lngMin >= 60 {
|
||||||
|
return 0, 0, fmt.Errorf("Cannot parse 'HDMS' string: %s", s)
|
||||||
|
}
|
||||||
|
lngSec, err := strconv.ParseFloat(matches[8], 64)
|
||||||
|
if err != nil || lngSec >= 60 {
|
||||||
|
return 0, 0, fmt.Errorf("Cannot parse 'HDMS' string: %s", s)
|
||||||
|
}
|
||||||
|
lng := lngDeg + lngMin/60.0 + lngSec/3600.0
|
||||||
|
if matches[5] == "W" || matches[5] == "w" {
|
||||||
|
lng = -lng
|
||||||
|
}
|
||||||
|
|
||||||
|
return lat, lng, nil
|
||||||
|
}
|
1
vendor/github.com/flopp/go-staticmaps/.gitignore
generated
vendored
Normal file
1
vendor/github.com/flopp/go-staticmaps/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
*.png
|
21
vendor/github.com/flopp/go-staticmaps/LICENSE
generated
vendored
Normal file
21
vendor/github.com/flopp/go-staticmaps/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 Florian Pigorsch
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
225
vendor/github.com/flopp/go-staticmaps/README.md
generated
vendored
Normal file
225
vendor/github.com/flopp/go-staticmaps/README.md
generated
vendored
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
[![GoDoc](https://godoc.org/github.com/flopp/go-staticmaps?status.svg)](https://godoc.org/github.com/flopp/go-staticmaps)
|
||||||
|
[![Go Report Card](https://goreportcard.com/badge/github.com/flopp/go-staticmaps)](https://goreportcard.com/report/flopp/go-staticmaps)
|
||||||
|
[![License MIT](https://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat)](https://github.com/flopp/go-staticmaps/)
|
||||||
|
|
||||||
|
# go-staticmaps
|
||||||
|
A go (golang) library and command line tool to render static map images using OpenStreetMap tiles.
|
||||||
|
|
||||||
|
## What?
|
||||||
|
go-staticmaps is a golang library that allows you to create nice static map images from OpenStreetMap tiles, along with markers of different size and color, as well as paths and colored areas.
|
||||||
|
|
||||||
|
go-staticmaps comes with a command line tool called `create-static-map` for use in shell scripts, etc.
|
||||||
|
|
||||||
|
![Static map of the Berlin Marathon](https://raw.githubusercontent.com/flopp/flopp.github.io/master/go-staticmaps/berlin-marathon.png)
|
||||||
|
|
||||||
|
## How?
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
Installing go-staticmaps is as easy as
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get -u github.com/flopp/go-staticmaps
|
||||||
|
```
|
||||||
|
|
||||||
|
For the command line tool, use
|
||||||
|
```bash
|
||||||
|
go get -u github.com/flopp/go-staticmaps/create-static-map
|
||||||
|
```
|
||||||
|
|
||||||
|
Of course, your local Go installation must be setup up properly.
|
||||||
|
|
||||||
|
### Library Usage
|
||||||
|
|
||||||
|
Create a 400x300 pixel map with a red marker:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"github.com/flopp/go-staticmaps"
|
||||||
|
"github.com/fogleman/gg"
|
||||||
|
"github.com/golang/geo/s2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctx := sm.NewContext()
|
||||||
|
ctx.SetSize(400, 300)
|
||||||
|
ctx.AddMarker(sm.NewMarker(s2.LatLngFromDegrees(52.514536, 13.350151), color.RGBA{0xff, 0, 0, 0xff}, 16.0))
|
||||||
|
|
||||||
|
img, err := ctx.Render()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gg.SavePNG("my-map.png", img); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
See [GoDoc](https://godoc.org/github.com/flopp/go-staticmaps) for a complete documentation and the source code of the [command line tool](https://github.com/flopp/go-staticmaps/blob/master/create-static-map/create-static-map.go) for an example how to use the package.
|
||||||
|
|
||||||
|
|
||||||
|
### Command Line Usage
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
create-static-map [OPTIONS]
|
||||||
|
|
||||||
|
Creates a static map
|
||||||
|
|
||||||
|
Application Options:
|
||||||
|
--width=PIXELS Width of the generated static map image (default: 512)
|
||||||
|
--height=PIXELS Height of the generated static map image (default: 512)
|
||||||
|
-o, --output=FILENAME Output file name (default: map.png)
|
||||||
|
-t, --type=MAPTYPE Select the map type; list possible map types with '--type list'
|
||||||
|
-c, --center=LATLNG Center coordinates (lat,lng) of the static map
|
||||||
|
-z, --zoom=ZOOMLEVEL Zoom factor
|
||||||
|
-b, --bbox=NW_LATLNG|SE_LATLNG
|
||||||
|
Set the bounding box (NW_LATLNG = north-western point of the
|
||||||
|
bounding box, SW_LATLNG = southe-western point of the bounding
|
||||||
|
box)
|
||||||
|
-m, --marker=MARKER Add a marker to the static map
|
||||||
|
-p, --path=PATH Add a path to the static map
|
||||||
|
-a, --area=AREA Add an area to the static map
|
||||||
|
|
||||||
|
Help Options:
|
||||||
|
-h, --help Show this help message
|
||||||
|
|
||||||
|
### General
|
||||||
|
The command line interface tries to resemble [Google's Static Maps API](https://developers.google.com/maps/documentation/static-maps/intro).
|
||||||
|
If neither `--bbox`, `--center`, nor `--zoom` are given, the map extent is determined from the specified markers, paths and areas.
|
||||||
|
|
||||||
|
### Markers
|
||||||
|
The `--marker` option defines one or more map markers of the same style. Use multiple `--marker` options to add markers of different styles.
|
||||||
|
|
||||||
|
--marker MARKER_STYLES|LATLNG|LATLNG|...
|
||||||
|
|
||||||
|
`LATLNG` is a comma separated pair of latitude and longitude, e.g. `52.5153,13.3564`.
|
||||||
|
|
||||||
|
`MARKER_STYLES` consists of a set of style descriptors separated by the pipe character `|`:
|
||||||
|
|
||||||
|
- `color:COLOR` - where `COLOR` is either of the form `0xRRGGBB`, `0xRRGGBBAA`, or one of `black`, `blue`, `brown`, `green`, `orange`, `purple`, `red`, `yellow`, `white` (default: `red`)
|
||||||
|
- `size:SIZE` - where `SIZE` is one of `mid`, `small`, `tiny`, or some number > 0 (default: `mid`)
|
||||||
|
- `label:LABEL` - where `LABEL` is an alpha numeric character, i.e. `A`-`Z`, `a`-`z`, `0`-`9`; (default: no label)
|
||||||
|
- `labelcolor:COLOR` - where `COLOR` is either of the form `0xRRGGBB`, `0xRRGGBBAA`, or one of `black`, `blue`, `brown`, `green`, `orange`, `purple`, `red`, `yellow`, `white` (default: `black` or `white`, depending on the marker color)
|
||||||
|
|
||||||
|
|
||||||
|
### Paths
|
||||||
|
The `--path` option defines a path on the map. Use multiple `--path` options to add multiple paths to the map.
|
||||||
|
|
||||||
|
--path PATH_STYLES|LATLNG|LATLNG|...
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
--path PATH_STYLES|gpx:my_gpx_file.gpx
|
||||||
|
|
||||||
|
`PATH_STYLES` consists of a set of style descriptors separated by the pipe character `|`:
|
||||||
|
|
||||||
|
- `color:COLOR` - where `COLOR` is either of the form `0xRRGGBB`, `0xRRGGBBAA`, or one of `black`, `blue`, `brown`, `green`, `orange`, `purple`, `red`, `yellow`, `white` (default: `red`)
|
||||||
|
- `weight:WEIGHT` - where `WEIGHT` is the line width in pixels (defaut: `5`)
|
||||||
|
|
||||||
|
### Areas
|
||||||
|
The `--area` option defines a closed area on the map. Use multiple `--area` options to add multiple areas to the map.
|
||||||
|
|
||||||
|
--area AREA_STYLES|LATLNG|LATLNG|...
|
||||||
|
|
||||||
|
`AREA_STYLES` consists of a set of style descriptors separated by the pipe character `|`:
|
||||||
|
|
||||||
|
- `color:COLOR` - where `COLOR` is either of the form `0xRRGGBB`, `0xRRGGBBAA`, or one of `black`, `blue`, `brown`, `green`, `orange`, `purple`, `red`, `yellow`, `white` (default: `red`)
|
||||||
|
- `weight:WEIGHT` - where `WEIGHT` is the line width in pixels (defaut: `5`)
|
||||||
|
- `fill:COLOR` - where `COLOR` is either of the form `0xRRGGBB`, `0xRRGGBBAA`, or one of `black`, `blue`, `brown`, `green`, `orange`, `purple`, `red`, `yellow`, `white` (default: none)
|
||||||
|
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Basic Maps
|
||||||
|
|
||||||
|
Centered at "N 52.514536 E 13.350151" with zoom level 10:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ create-static-map --width 600 --height 400 -o map1.png -c "52.514536,13.350151" -z 10
|
||||||
|
```
|
||||||
|
![Example 1](https://raw.githubusercontent.com/flopp/flopp.github.io/master/go-staticmaps/map1.png)
|
||||||
|
|
||||||
|
A map with a marker at "N 52.514536 E 13.350151" with zoom level 14 (no need to specify the map's center - it is automatically computed from the marker(s)):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ create-static-map --width 600 --height 400 -o map2.png -z 14 -m "52.514536,13.350151"
|
||||||
|
```
|
||||||
|
|
||||||
|
![Example 2](https://raw.githubusercontent.com/flopp/flopp.github.io/master/go-staticmaps/map2.png)
|
||||||
|
|
||||||
|
A map with two markers (red and green). If there are more than two markers in the map, a *good* zoom level can be determined automatically:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ create-static-map --width 600 --height 400 -o map3.png -m "color:red|52.514536,13.350151" -m "color:green|52.516285,13.377746"
|
||||||
|
```
|
||||||
|
|
||||||
|
![Example 3](https://raw.githubusercontent.com/flopp/flopp.github.io/master/go-staticmaps/map3.png)
|
||||||
|
|
||||||
|
|
||||||
|
### Create a map of the Berlin Marathon
|
||||||
|
|
||||||
|
create-static-map --width 800 --height 600 \
|
||||||
|
--marker "color:green|52.5153,13.3564" \
|
||||||
|
--marker "color:red|52.5160,13.3711" \
|
||||||
|
--output "berlin-marathon.png" \
|
||||||
|
--path "color:blue|weight:2|gpx:berlin-marathon.gpx"
|
||||||
|
|
||||||
|
![Static map of the Berlin Marathon](https://raw.githubusercontent.com/flopp/flopp.github.io/master/go-staticmaps/berlin-marathon.png)
|
||||||
|
|
||||||
|
### Create a map of the US capitals
|
||||||
|
|
||||||
|
create-static-map --width 800 --height 400 \
|
||||||
|
--output "us-capitals.png" \
|
||||||
|
--marker "color:blue|size:tiny|32.3754,-86.2996|58.3637,-134.5721|33.4483,-112.0738|34.7244,-92.2789|\
|
||||||
|
38.5737,-121.4871|39.7551,-104.9881|41.7665,-72.6732|39.1615,-75.5136|30.4382,-84.2806|33.7545,-84.3897|\
|
||||||
|
21.2920,-157.8219|43.6021,-116.2125|39.8018,-89.6533|39.7670,-86.1563|41.5888,-93.6203|39.0474,-95.6815|\
|
||||||
|
38.1894,-84.8715|30.4493,-91.1882|44.3294,-69.7323|38.9693,-76.5197|42.3589,-71.0568|42.7336,-84.5466|\
|
||||||
|
44.9446,-93.1027|32.3122,-90.1780|38.5698,-92.1941|46.5911,-112.0205|40.8136,-96.7026|39.1501,-119.7519|\
|
||||||
|
43.2314,-71.5597|40.2202,-74.7642|35.6816,-105.9381|42.6517,-73.7551|35.7797,-78.6434|46.8084,-100.7694|\
|
||||||
|
39.9622,-83.0007|35.4931,-97.4591|44.9370,-123.0272|40.2740,-76.8849|41.8270,-71.4087|34.0007,-81.0353|\
|
||||||
|
44.3776,-100.3177|36.1589,-86.7821|30.2687,-97.7452|40.7716,-111.8882|44.2627,-72.5716|37.5408,-77.4339|\
|
||||||
|
47.0449,-122.9016|38.3533,-81.6354|43.0632,-89.4007|41.1389,-104.8165"
|
||||||
|
|
||||||
|
![Static map of the US capitals](https://raw.githubusercontent.com/flopp/flopp.github.io/master/go-staticmaps/us-capitals.png)
|
||||||
|
|
||||||
|
### Create a map of Australia
|
||||||
|
...where the Northern Territory is highlighted and the capital Canberra is marked.
|
||||||
|
|
||||||
|
create-static-map --width 800 --height 600 \
|
||||||
|
--center="-26.284973,134.303764" \
|
||||||
|
--output "australia.png" \
|
||||||
|
--marker "color:blue|-35.305200,149.121574" \
|
||||||
|
--area "color:0x00FF00|fill:0x00FF007F|weight:2|-25.994024,129.013847|-25.994024,137.989677|-16.537670,138.011649|\
|
||||||
|
-14.834820,135.385917|-12.293236,137.033866|-11.174554,130.398124|-12.925791,130.167411|-14.866678,129.002860"
|
||||||
|
|
||||||
|
![Static map of Australia](https://raw.githubusercontent.com/flopp/flopp.github.io/master/go-staticmaps/australia.png)
|
||||||
|
|
||||||
|
## Acknowledgements
|
||||||
|
Besides the go standard library, go-staticmaps uses
|
||||||
|
|
||||||
|
- [OpenStreetMap](http://openstreetmap.org/), [Thunderforest](http://www.thunderforest.com/), [OpenTopoMap](http://www.opentopomap.org/), [Stamen](http://maps.stamen.com/) and [Carto](http://carto.com) as map tile providers
|
||||||
|
- [Go Graphics](https://github.com/fogleman/gg) for 2D drawing
|
||||||
|
- [S2 geometry library](https://github.com/golang/geo) for spherical geometry calculations
|
||||||
|
- [appdirs](https://github.com/Wessie/appdirs) for platform specific system directories
|
||||||
|
- [gpxgo](github.com/tkrajina/gpxgo) for loading GPX files
|
||||||
|
- [go-coordsparser](https://github.com/flopp/go-coordsparser) for parsing geo coordinates
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
- [Kooper](https://github.com/Kooper): fixed *library usage examples*
|
||||||
|
- [felix](https://github.com/felix): added *more tile servers*
|
||||||
|
- [wiless](https://github.com/wiless): suggested to add user definable *marker label colors*
|
||||||
|
- [noki](https://github.com/Noki): suggested to add a user definable *bounding box*
|
||||||
|
- [digitocero](https://github.com/digitocero): reported and fixed *type mismatch error*
|
||||||
|
- [bcicen](https://github.com/bcicen): reported and fixed *syntax error in examples*
|
||||||
|
- [pshevtsov](https://github.com/pshevtsov): fixed *drawing of empty attribution strings*
|
||||||
|
- [Luzifer](https://github.com/Luzifer): added *overwritable user agent strings* to comply with the OSM tile usage policy
|
||||||
|
|
||||||
|
|
||||||
|
## License
|
||||||
|
Copyright 2016, 2017 Florian Pigorsch & Contributors. All rights reserved.
|
||||||
|
|
||||||
|
Use of this source code is governed by a MIT-style license that can be found in the LICENSE file.
|
94
vendor/github.com/flopp/go-staticmaps/area.go
generated
vendored
Normal file
94
vendor/github.com/flopp/go-staticmaps/area.go
generated
vendored
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
// Copyright 2016, 2017 Florian Pigorsch. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package sm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/flopp/go-coordsparser"
|
||||||
|
"github.com/fogleman/gg"
|
||||||
|
"github.com/golang/geo/s2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Area represents a area or area on the map
|
||||||
|
type Area struct {
|
||||||
|
MapObject
|
||||||
|
Positions []s2.LatLng
|
||||||
|
Color color.Color
|
||||||
|
Fill color.Color
|
||||||
|
Weight float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseAreaString parses a string and returns an area
|
||||||
|
func ParseAreaString(s string) (*Area, error) {
|
||||||
|
area := new(Area)
|
||||||
|
area.Color = color.RGBA{0xff, 0, 0, 0xff}
|
||||||
|
area.Fill = color.Transparent
|
||||||
|
area.Weight = 5.0
|
||||||
|
|
||||||
|
for _, ss := range strings.Split(s, "|") {
|
||||||
|
if ok, suffix := hasPrefix(ss, "color:"); ok {
|
||||||
|
var err error
|
||||||
|
area.Color, err = ParseColorString(suffix)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if ok, suffix := hasPrefix(ss, "fill:"); ok {
|
||||||
|
var err error
|
||||||
|
area.Fill, err = ParseColorString(suffix)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if ok, suffix := hasPrefix(ss, "weight:"); ok {
|
||||||
|
var err error
|
||||||
|
area.Weight, err = strconv.ParseFloat(suffix, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lat, lng, err := coordsparser.Parse(ss)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
area.Positions = append(area.Positions, s2.LatLngFromDegrees(lat, lng))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return area, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Area) extraMarginPixels() float64 {
|
||||||
|
return 0.5 * p.Weight
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Area) bounds() s2.Rect {
|
||||||
|
r := s2.EmptyRect()
|
||||||
|
for _, ll := range p.Positions {
|
||||||
|
r = r.AddPoint(ll)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Area) draw(gc *gg.Context, trans *transformer) {
|
||||||
|
if len(p.Positions) <= 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gc.ClearPath()
|
||||||
|
gc.SetLineWidth(p.Weight)
|
||||||
|
gc.SetLineCap(gg.LineCapRound)
|
||||||
|
gc.SetLineJoin(gg.LineJoinRound)
|
||||||
|
for _, ll := range p.Positions {
|
||||||
|
gc.LineTo(trans.ll2p(ll))
|
||||||
|
}
|
||||||
|
gc.ClosePath()
|
||||||
|
gc.SetColor(p.Fill)
|
||||||
|
gc.FillPreserve()
|
||||||
|
gc.SetColor(p.Color)
|
||||||
|
gc.Stroke()
|
||||||
|
}
|
53
vendor/github.com/flopp/go-staticmaps/bbox.go
generated
vendored
Normal file
53
vendor/github.com/flopp/go-staticmaps/bbox.go
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
// Copyright 2016, 2017 Florian Pigorsch. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package sm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/golang/geo/s1"
|
||||||
|
"github.com/golang/geo/s2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateBBox creates a bounding box from a north-western point
|
||||||
|
// (lat/lng in degrees) and a south-eastern point (lat/lng in degrees).
|
||||||
|
// Note that you can create a bounding box wrapping over the antimeridian at
|
||||||
|
// lng=+-/180° by nwlng > selng.
|
||||||
|
func CreateBBox(nwlat float64, nwlng float64, selat float64, selng float64) (*s2.Rect, error) {
|
||||||
|
if nwlat < -90 || nwlat > 90 {
|
||||||
|
return nil, fmt.Errorf("Out of range nwlat (%f) must be in [-90, 90]", nwlat)
|
||||||
|
}
|
||||||
|
if nwlng < -180 || nwlng > 180 {
|
||||||
|
return nil, fmt.Errorf("Out of range nwlng (%f) must be in [-180, 180]", nwlng)
|
||||||
|
}
|
||||||
|
|
||||||
|
if selat < -90 || selat > 90 {
|
||||||
|
return nil, fmt.Errorf("Out of range selat (%f) must be in [-90, 90]", selat)
|
||||||
|
}
|
||||||
|
if selng < -180 || selng > 180 {
|
||||||
|
return nil, fmt.Errorf("Out of range selng (%f) must be in [-180, 180]", selng)
|
||||||
|
}
|
||||||
|
|
||||||
|
if nwlat == selat {
|
||||||
|
return nil, fmt.Errorf("nwlat and selat must not be equal")
|
||||||
|
}
|
||||||
|
if nwlng == selng {
|
||||||
|
return nil, fmt.Errorf("nwlng and selng must not be equal")
|
||||||
|
}
|
||||||
|
|
||||||
|
bbox := new(s2.Rect)
|
||||||
|
if selat < nwlat {
|
||||||
|
bbox.Lat.Lo = selat * math.Pi / 180.0
|
||||||
|
bbox.Lat.Hi = nwlat * math.Pi / 180.0
|
||||||
|
} else {
|
||||||
|
bbox.Lat.Lo = nwlat * math.Pi / 180.0
|
||||||
|
bbox.Lat.Hi = selat * math.Pi / 180.0
|
||||||
|
}
|
||||||
|
bbox.Lng = s1.IntervalFromEndpoints(nwlng*math.Pi/180.0, selng*math.Pi/180.0)
|
||||||
|
|
||||||
|
return bbox, nil
|
||||||
|
}
|
65
vendor/github.com/flopp/go-staticmaps/color.go
generated
vendored
Normal file
65
vendor/github.com/flopp/go-staticmaps/color.go
generated
vendored
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright 2016, 2017 Florian Pigorsch. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package sm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image/color"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseColorString parses hex color strings (i.e. `0xRRGGBB`, `#RRGGBB`, `0xRRGGBBAA`, `#RRGGBBAA`), and names colors (e.g. 'black', 'blue', ...)
|
||||||
|
func ParseColorString(s string) (color.Color, error) {
|
||||||
|
s = strings.ToLower(strings.TrimSpace(s))
|
||||||
|
|
||||||
|
re := regexp.MustCompile(`^(0x|#)([A-Fa-f0-9]{6})$`)
|
||||||
|
matches := re.FindStringSubmatch(s)
|
||||||
|
if matches != nil {
|
||||||
|
var r, g, b int
|
||||||
|
fmt.Sscanf(matches[2], "%2x%2x%2x", &r, &g, &b)
|
||||||
|
return color.RGBA{uint8(r), uint8(g), uint8(b), 0xff}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
re = regexp.MustCompile(`^(0x|#)([A-Fa-f0-9]{8})$`)
|
||||||
|
matches = re.FindStringSubmatch(s)
|
||||||
|
if matches != nil {
|
||||||
|
var r, g, b, a int
|
||||||
|
fmt.Sscanf(matches[2], "%2x%2x%2x%2x", &r, &g, &b, &a)
|
||||||
|
rr := float64(r) * float64(a) / 256.0
|
||||||
|
gg := float64(g) * float64(a) / 256.0
|
||||||
|
bb := float64(b) * float64(a) / 256.0
|
||||||
|
return color.RGBA{uint8(rr), uint8(gg), uint8(bb), uint8(a)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch s {
|
||||||
|
case "black":
|
||||||
|
return color.RGBA{0x00, 0x00, 0x00, 0xff}, nil
|
||||||
|
case "blue":
|
||||||
|
return color.RGBA{0x00, 0x00, 0xff, 0xff}, nil
|
||||||
|
case "brown":
|
||||||
|
return color.RGBA{0x96, 0x4b, 0x00, 0xff}, nil
|
||||||
|
case "green":
|
||||||
|
return color.RGBA{0x00, 0xff, 0x00, 0xff}, nil
|
||||||
|
case "orange":
|
||||||
|
return color.RGBA{0xff, 0x7f, 0x00, 0xff}, nil
|
||||||
|
case "purple":
|
||||||
|
return color.RGBA{0x7f, 0x00, 0x7f, 0xff}, nil
|
||||||
|
case "red":
|
||||||
|
return color.RGBA{0xff, 0x00, 0x00, 0xff}, nil
|
||||||
|
case "yellow":
|
||||||
|
return color.RGBA{0xff, 0xff, 0x00, 0xff}, nil
|
||||||
|
case "white":
|
||||||
|
return color.RGBA{0xff, 0xff, 0xff, 0xff}, nil
|
||||||
|
}
|
||||||
|
return color.Transparent, fmt.Errorf("Cannot parse color string: %s", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Luminance computes the luminance (~ brightness) of the given color. Range: 0.0 for black to 1.0 for white.
|
||||||
|
func Luminance(col color.Color) float64 {
|
||||||
|
r, g, b, _ := col.RGBA()
|
||||||
|
return (float64(r)*0.299 + float64(g)*0.587 + float64(b)*0.114) / float64(0xffff)
|
||||||
|
}
|
321
vendor/github.com/flopp/go-staticmaps/context.go
generated
vendored
Normal file
321
vendor/github.com/flopp/go-staticmaps/context.go
generated
vendored
Normal file
|
@ -0,0 +1,321 @@
|
||||||
|
// Copyright 2016, 2017 Florian Pigorsch. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package sm (~ static maps) renders static map images from OSM tiles with markers, paths, and filled areas.
|
||||||
|
package sm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"image"
|
||||||
|
"image/draw"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/fogleman/gg"
|
||||||
|
"github.com/golang/geo/s2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Context holds all information about the map image that is to be rendered
|
||||||
|
type Context struct {
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
|
||||||
|
hasZoom bool
|
||||||
|
zoom int
|
||||||
|
|
||||||
|
hasCenter bool
|
||||||
|
center s2.LatLng
|
||||||
|
|
||||||
|
hasBoundingBox bool
|
||||||
|
boundingBox s2.Rect
|
||||||
|
|
||||||
|
markers []*Marker
|
||||||
|
paths []*Path
|
||||||
|
areas []*Area
|
||||||
|
|
||||||
|
tileProvider *TileProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContext creates a new instance of Context
|
||||||
|
func NewContext() *Context {
|
||||||
|
t := new(Context)
|
||||||
|
t.width = 512
|
||||||
|
t.height = 512
|
||||||
|
t.hasZoom = false
|
||||||
|
t.hasCenter = false
|
||||||
|
t.hasBoundingBox = false
|
||||||
|
t.tileProvider = NewTileProviderOpenStreetMaps()
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTileProvider sets the TileProvider to be used
|
||||||
|
func (m *Context) SetTileProvider(t *TileProvider) {
|
||||||
|
m.tileProvider = t
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSize sets the size of the generated image
|
||||||
|
func (m *Context) SetSize(width, height int) {
|
||||||
|
m.width = width
|
||||||
|
m.height = height
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetZoom sets the zoom level
|
||||||
|
func (m *Context) SetZoom(zoom int) {
|
||||||
|
m.zoom = zoom
|
||||||
|
m.hasZoom = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCenter sets the center coordinates
|
||||||
|
func (m *Context) SetCenter(center s2.LatLng) {
|
||||||
|
m.center = center
|
||||||
|
m.hasCenter = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBoundingBox sets the bounding box
|
||||||
|
func (m *Context) SetBoundingBox(bbox s2.Rect) {
|
||||||
|
m.boundingBox = bbox
|
||||||
|
m.hasBoundingBox = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddMarker adds a marker to the Context
|
||||||
|
func (m *Context) AddMarker(marker *Marker) {
|
||||||
|
m.markers = append(m.markers, marker)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearMarkers removes all markers from the Context
|
||||||
|
func (m *Context) ClearMarkers() {
|
||||||
|
m.markers = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPath adds a path to the Context
|
||||||
|
func (m *Context) AddPath(path *Path) {
|
||||||
|
m.paths = append(m.paths, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearPaths removes all paths from the Context
|
||||||
|
func (m *Context) ClearPaths() {
|
||||||
|
m.paths = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddArea adds an area to the Context
|
||||||
|
func (m *Context) AddArea(area *Area) {
|
||||||
|
m.areas = append(m.areas, area)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAreas removes all areas from the Context
|
||||||
|
func (m *Context) ClearAreas() {
|
||||||
|
m.areas = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Context) determineBounds() s2.Rect {
|
||||||
|
r := s2.EmptyRect()
|
||||||
|
for _, marker := range m.markers {
|
||||||
|
r = r.Union(marker.bounds())
|
||||||
|
}
|
||||||
|
for _, path := range m.paths {
|
||||||
|
r = r.Union(path.bounds())
|
||||||
|
}
|
||||||
|
for _, area := range m.areas {
|
||||||
|
r = r.Union(area.bounds())
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Context) determineExtraMarginPixels() float64 {
|
||||||
|
p := 0.0
|
||||||
|
for _, marker := range m.markers {
|
||||||
|
if pp := marker.extraMarginPixels(); pp > p {
|
||||||
|
p = pp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, path := range m.paths {
|
||||||
|
if pp := path.extraMarginPixels(); pp > p {
|
||||||
|
p = pp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, area := range m.areas {
|
||||||
|
if pp := area.extraMarginPixels(); pp > p {
|
||||||
|
p = pp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Context) determineZoom(bounds s2.Rect, center s2.LatLng) int {
|
||||||
|
b := bounds.AddPoint(center)
|
||||||
|
if b.IsEmpty() || b.IsPoint() {
|
||||||
|
return 15
|
||||||
|
}
|
||||||
|
|
||||||
|
tileSize := m.tileProvider.TileSize
|
||||||
|
margin := 4.0 + m.determineExtraMarginPixels()
|
||||||
|
w := (float64(m.width) - 2.0*margin) / float64(tileSize)
|
||||||
|
h := (float64(m.height) - 2.0*margin) / float64(tileSize)
|
||||||
|
minX := (b.Lo().Lng.Degrees() + 180.0) / 360.0
|
||||||
|
maxX := (b.Hi().Lng.Degrees() + 180.0) / 360.0
|
||||||
|
minY := (1.0 - math.Log(math.Tan(b.Lo().Lat.Radians())+(1.0/math.Cos(b.Lo().Lat.Radians())))/math.Pi) / 2.0
|
||||||
|
maxY := (1.0 - math.Log(math.Tan(b.Hi().Lat.Radians())+(1.0/math.Cos(b.Hi().Lat.Radians())))/math.Pi) / 2.0
|
||||||
|
|
||||||
|
dx := maxX - minX
|
||||||
|
for dx < 0 {
|
||||||
|
dx = dx + 1
|
||||||
|
}
|
||||||
|
for dx > 1 {
|
||||||
|
dx = dx - 1
|
||||||
|
}
|
||||||
|
dy := math.Abs(maxY - minY)
|
||||||
|
|
||||||
|
zoom := 1
|
||||||
|
for zoom < 30 {
|
||||||
|
tiles := float64(uint(1) << uint(zoom))
|
||||||
|
if dx*tiles > w || dy*tiles > h {
|
||||||
|
return zoom - 1
|
||||||
|
}
|
||||||
|
zoom = zoom + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return 15
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Context) determineZoomCenter() (int, s2.LatLng, error) {
|
||||||
|
bounds := m.determineBounds()
|
||||||
|
if m.hasBoundingBox && !m.boundingBox.IsEmpty() {
|
||||||
|
center := m.boundingBox.Center()
|
||||||
|
return m.determineZoom(m.boundingBox, center), center, nil
|
||||||
|
} else if m.hasCenter {
|
||||||
|
if m.hasZoom {
|
||||||
|
return m.zoom, m.center, nil
|
||||||
|
}
|
||||||
|
return m.determineZoom(bounds, m.center), m.center, nil
|
||||||
|
} else if !bounds.IsEmpty() {
|
||||||
|
center := bounds.Center()
|
||||||
|
if m.hasZoom {
|
||||||
|
return m.zoom, center, nil
|
||||||
|
}
|
||||||
|
return m.determineZoom(bounds, center), center, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, s2.LatLngFromDegrees(0, 0), errors.New("Cannot determine map extent: no center coordinates given, no bounding box given, no content (markers, paths, areas) given")
|
||||||
|
}
|
||||||
|
|
||||||
|
type transformer struct {
|
||||||
|
zoom int
|
||||||
|
tileSize int
|
||||||
|
pWidth, pHeight int
|
||||||
|
pCenterX, pCenterY int
|
||||||
|
tCountX, tCountY int
|
||||||
|
tCenterX, tCenterY float64
|
||||||
|
tOriginX, tOriginY int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTransformer(width int, height int, zoom int, llCenter s2.LatLng, tileSize int) *transformer {
|
||||||
|
t := new(transformer)
|
||||||
|
t.zoom = zoom
|
||||||
|
t.tileSize = tileSize
|
||||||
|
t.tCenterX, t.tCenterY = t.ll2t(llCenter)
|
||||||
|
|
||||||
|
ww := float64(width) / float64(tileSize)
|
||||||
|
hh := float64(height) / float64(tileSize)
|
||||||
|
|
||||||
|
t.tOriginX = int(math.Floor(t.tCenterX - 0.5*ww))
|
||||||
|
t.tOriginY = int(math.Floor(t.tCenterY - 0.5*hh))
|
||||||
|
|
||||||
|
t.tCountX = 1 + int(math.Floor(t.tCenterX+0.5*ww)) - t.tOriginX
|
||||||
|
t.tCountY = 1 + int(math.Floor(t.tCenterY+0.5*hh)) - t.tOriginY
|
||||||
|
|
||||||
|
t.pWidth = t.tCountX * tileSize
|
||||||
|
t.pHeight = t.tCountY * tileSize
|
||||||
|
|
||||||
|
t.pCenterX = int((t.tCenterX - float64(t.tOriginX)) * float64(tileSize))
|
||||||
|
t.pCenterY = int((t.tCenterY - float64(t.tOriginY)) * float64(tileSize))
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *transformer) ll2t(ll s2.LatLng) (float64, float64) {
|
||||||
|
tiles := math.Exp2(float64(t.zoom))
|
||||||
|
x := tiles * (ll.Lng.Degrees() + 180.0) / 360.0
|
||||||
|
y := tiles * (1 - math.Log(math.Tan(ll.Lat.Radians())+(1.0/math.Cos(ll.Lat.Radians())))/math.Pi) / 2.0
|
||||||
|
return x, y
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *transformer) ll2p(ll s2.LatLng) (float64, float64) {
|
||||||
|
x, y := t.ll2t(ll)
|
||||||
|
if x < float64(t.tOriginX) {
|
||||||
|
x = x + math.Exp2(float64(t.zoom))
|
||||||
|
}
|
||||||
|
x = float64(t.pCenterX) + (x-t.tCenterX)*float64(t.tileSize)
|
||||||
|
y = float64(t.pCenterY) + (y-t.tCenterY)*float64(t.tileSize)
|
||||||
|
return x, y
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render actually renders the map image including all map objects (markers, paths, areas)
|
||||||
|
func (m *Context) Render() (image.Image, error) {
|
||||||
|
zoom, center, err := m.determineZoomCenter()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tileSize := m.tileProvider.TileSize
|
||||||
|
trans := newTransformer(m.width, m.height, zoom, center, tileSize)
|
||||||
|
img := image.NewRGBA(image.Rect(0, 0, trans.pWidth, trans.pHeight))
|
||||||
|
gc := gg.NewContextForRGBA(img)
|
||||||
|
|
||||||
|
// fetch and draw tiles to img
|
||||||
|
t := NewTileFetcher(m.tileProvider)
|
||||||
|
tiles := (1 << uint(zoom))
|
||||||
|
for xx := 0; xx < trans.tCountX; xx++ {
|
||||||
|
x := trans.tOriginX + xx
|
||||||
|
if x < 0 {
|
||||||
|
x = x + tiles
|
||||||
|
} else if x >= tiles {
|
||||||
|
x = x - tiles
|
||||||
|
}
|
||||||
|
for yy := 0; yy < trans.tCountY; yy++ {
|
||||||
|
y := trans.tOriginY + yy
|
||||||
|
if y < 0 || y >= tiles {
|
||||||
|
log.Printf("Skipping out of bounds tile %d/%d", x, y)
|
||||||
|
} else {
|
||||||
|
if tileImg, err := t.Fetch(zoom, x, y); err == nil {
|
||||||
|
gc.DrawImage(tileImg, xx*tileSize, yy*tileSize)
|
||||||
|
} else {
|
||||||
|
log.Printf("Error downloading tile file: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw map objects
|
||||||
|
for _, area := range m.areas {
|
||||||
|
area.draw(gc, trans)
|
||||||
|
}
|
||||||
|
for _, path := range m.paths {
|
||||||
|
path.draw(gc, trans)
|
||||||
|
}
|
||||||
|
for _, marker := range m.markers {
|
||||||
|
marker.draw(gc, trans)
|
||||||
|
}
|
||||||
|
|
||||||
|
// crop image
|
||||||
|
croppedImg := image.NewRGBA(image.Rect(0, 0, int(m.width), int(m.height)))
|
||||||
|
draw.Draw(croppedImg, image.Rect(0, 0, int(m.width), int(m.height)),
|
||||||
|
img, image.Point{trans.pCenterX - int(m.width)/2, trans.pCenterY - int(m.height)/2},
|
||||||
|
draw.Src)
|
||||||
|
|
||||||
|
// draw attribution
|
||||||
|
if m.tileProvider.Attribution == "" {
|
||||||
|
return croppedImg, nil
|
||||||
|
}
|
||||||
|
_, textHeight := gc.MeasureString(m.tileProvider.Attribution)
|
||||||
|
boxHeight := textHeight + 4.0
|
||||||
|
gc = gg.NewContextForRGBA(croppedImg)
|
||||||
|
gc.SetRGBA(0.0, 0.0, 0.0, 0.5)
|
||||||
|
gc.DrawRectangle(0.0, float64(m.height)-boxHeight, float64(m.width), boxHeight)
|
||||||
|
gc.Fill()
|
||||||
|
gc.SetRGBA(1.0, 1.0, 1.0, 0.75)
|
||||||
|
gc.DrawString(m.tileProvider.Attribution, 4.0, float64(m.height)-4.0)
|
||||||
|
|
||||||
|
return croppedImg, nil
|
||||||
|
}
|
18
vendor/github.com/flopp/go-staticmaps/map_object.go
generated
vendored
Normal file
18
vendor/github.com/flopp/go-staticmaps/map_object.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright 2016, 2017 Florian Pigorsch. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package sm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/fogleman/gg"
|
||||||
|
"github.com/golang/geo/s2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MapObject is the interface for all objects on the map
|
||||||
|
type MapObject interface {
|
||||||
|
bounds() s2.Rect
|
||||||
|
extraMarginPixels() float64
|
||||||
|
draw(dc *gg.Context, trans *transformer)
|
||||||
|
}
|
142
vendor/github.com/flopp/go-staticmaps/marker.go
generated
vendored
Normal file
142
vendor/github.com/flopp/go-staticmaps/marker.go
generated
vendored
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
// Copyright 2016, 2017 Florian Pigorsch. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package sm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image/color"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/flopp/go-coordsparser"
|
||||||
|
"github.com/fogleman/gg"
|
||||||
|
"github.com/golang/geo/s2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Marker represents a marker on the map
|
||||||
|
type Marker struct {
|
||||||
|
MapObject
|
||||||
|
Position s2.LatLng
|
||||||
|
Color color.Color
|
||||||
|
Size float64
|
||||||
|
Label string
|
||||||
|
LabelColor color.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMarker creates a new Marker
|
||||||
|
func NewMarker(pos s2.LatLng, col color.Color, size float64) *Marker {
|
||||||
|
m := new(Marker)
|
||||||
|
m.Position = pos
|
||||||
|
m.Color = col
|
||||||
|
m.Size = size
|
||||||
|
m.Label = ""
|
||||||
|
if Luminance(m.Color) >= 0.5 {
|
||||||
|
m.LabelColor = color.RGBA{0x00, 0x00, 0x00, 0xff}
|
||||||
|
} else {
|
||||||
|
m.LabelColor = color.RGBA{0xff, 0xff, 0xff, 0xff}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSizeString(s string) (float64, error) {
|
||||||
|
switch {
|
||||||
|
case s == "mid":
|
||||||
|
return 16.0, nil
|
||||||
|
case s == "small":
|
||||||
|
return 12.0, nil
|
||||||
|
case s == "tiny":
|
||||||
|
return 8.0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ss, err := strconv.ParseFloat(s, 64); err != nil && ss > 0 {
|
||||||
|
return ss, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0.0, fmt.Errorf("Cannot parse size string: %s", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseMarkerString parses a string and returns an array of markers
|
||||||
|
func ParseMarkerString(s string) ([]*Marker, error) {
|
||||||
|
markers := make([]*Marker, 0, 0)
|
||||||
|
|
||||||
|
var markerColor color.Color = color.RGBA{0xff, 0, 0, 0xff}
|
||||||
|
size := 16.0
|
||||||
|
label := ""
|
||||||
|
var labelColor color.Color
|
||||||
|
|
||||||
|
for _, ss := range strings.Split(s, "|") {
|
||||||
|
if ok, suffix := hasPrefix(ss, "color:"); ok {
|
||||||
|
var err error
|
||||||
|
markerColor, err = ParseColorString(suffix)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if ok, suffix := hasPrefix(ss, "label:"); ok {
|
||||||
|
label = suffix
|
||||||
|
} else if ok, suffix := hasPrefix(ss, "size:"); ok {
|
||||||
|
var err error
|
||||||
|
size, err = parseSizeString(suffix)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if ok, suffix := hasPrefix(ss, "labelcolor:"); ok {
|
||||||
|
var err error
|
||||||
|
labelColor, err = ParseColorString(suffix)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lat, lng, err := coordsparser.Parse(ss)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m := NewMarker(s2.LatLngFromDegrees(lat, lng), markerColor, size)
|
||||||
|
m.Label = label
|
||||||
|
if labelColor != nil {
|
||||||
|
m.SetLabelColor(labelColor)
|
||||||
|
}
|
||||||
|
markers = append(markers, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return markers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLabelColor sets the color of the marker's text label
|
||||||
|
func (m *Marker) SetLabelColor(col color.Color) {
|
||||||
|
m.LabelColor = col
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Marker) extraMarginPixels() float64 {
|
||||||
|
return 1.0 + 1.5*m.Size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Marker) bounds() s2.Rect {
|
||||||
|
r := s2.EmptyRect()
|
||||||
|
r = r.AddPoint(m.Position)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Marker) draw(gc *gg.Context, trans *transformer) {
|
||||||
|
gc.ClearPath()
|
||||||
|
gc.SetLineJoin(gg.LineJoinRound)
|
||||||
|
gc.SetLineWidth(1.0)
|
||||||
|
|
||||||
|
radius := 0.5 * m.Size
|
||||||
|
x, y := trans.ll2p(m.Position)
|
||||||
|
gc.DrawArc(x, y-m.Size, radius, (90.0+60.0)*math.Pi/180.0, (360.0+90.0-60.0)*math.Pi/180.0)
|
||||||
|
gc.LineTo(x, y)
|
||||||
|
gc.ClosePath()
|
||||||
|
gc.SetColor(m.Color)
|
||||||
|
gc.FillPreserve()
|
||||||
|
gc.SetRGB(0, 0, 0)
|
||||||
|
gc.Stroke()
|
||||||
|
|
||||||
|
if m.Label != "" {
|
||||||
|
gc.SetColor(m.LabelColor)
|
||||||
|
gc.DrawStringAnchored(m.Label, x, y-m.Size, 0.5, 0.5)
|
||||||
|
}
|
||||||
|
}
|
103
vendor/github.com/flopp/go-staticmaps/path.go
generated
vendored
Normal file
103
vendor/github.com/flopp/go-staticmaps/path.go
generated
vendored
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
// Copyright 2016, 2017 Florian Pigorsch. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package sm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/flopp/go-coordsparser"
|
||||||
|
"github.com/fogleman/gg"
|
||||||
|
"github.com/golang/geo/s2"
|
||||||
|
"github.com/tkrajina/gpxgo/gpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Path represents a path or area on the map
|
||||||
|
type Path struct {
|
||||||
|
MapObject
|
||||||
|
Positions []s2.LatLng
|
||||||
|
Color color.Color
|
||||||
|
Weight float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePathString parses a string and returns a path
|
||||||
|
func ParsePathString(s string) ([]*Path, error) {
|
||||||
|
paths := make([]*Path, 0, 0)
|
||||||
|
currentPath := new(Path)
|
||||||
|
currentPath.Color = color.RGBA{0xff, 0, 0, 0xff}
|
||||||
|
currentPath.Weight = 5.0
|
||||||
|
|
||||||
|
for _, ss := range strings.Split(s, "|") {
|
||||||
|
if ok, suffix := hasPrefix(ss, "color:"); ok {
|
||||||
|
var err error
|
||||||
|
if currentPath.Color, err = ParseColorString(suffix); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if ok, suffix := hasPrefix(ss, "weight:"); ok {
|
||||||
|
var err error
|
||||||
|
if currentPath.Weight, err = strconv.ParseFloat(suffix, 64); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if ok, suffix := hasPrefix(ss, "gpx:"); ok {
|
||||||
|
gpxData, err := gpx.ParseFile(suffix)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, trk := range gpxData.Tracks {
|
||||||
|
for _, seg := range trk.Segments {
|
||||||
|
p := new(Path)
|
||||||
|
p.Color = currentPath.Color
|
||||||
|
p.Weight = currentPath.Weight
|
||||||
|
for _, pt := range seg.Points {
|
||||||
|
p.Positions = append(p.Positions, s2.LatLngFromDegrees(pt.GetLatitude(), pt.GetLongitude()))
|
||||||
|
}
|
||||||
|
if len(p.Positions) > 0 {
|
||||||
|
paths = append(paths, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lat, lng, err := coordsparser.Parse(ss)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
currentPath.Positions = append(currentPath.Positions, s2.LatLngFromDegrees(lat, lng))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(currentPath.Positions) > 0 {
|
||||||
|
paths = append(paths, currentPath)
|
||||||
|
}
|
||||||
|
return paths, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path) extraMarginPixels() float64 {
|
||||||
|
return 0.5 * p.Weight
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path) bounds() s2.Rect {
|
||||||
|
r := s2.EmptyRect()
|
||||||
|
for _, ll := range p.Positions {
|
||||||
|
r = r.AddPoint(ll)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path) draw(gc *gg.Context, trans *transformer) {
|
||||||
|
if len(p.Positions) <= 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gc.ClearPath()
|
||||||
|
gc.SetLineWidth(p.Weight)
|
||||||
|
gc.SetLineCap(gg.LineCapRound)
|
||||||
|
gc.SetLineJoin(gg.LineJoinRound)
|
||||||
|
for _, ll := range p.Positions {
|
||||||
|
gc.LineTo(trans.ll2p(ll))
|
||||||
|
}
|
||||||
|
gc.SetColor(p.Color)
|
||||||
|
gc.Stroke()
|
||||||
|
}
|
162
vendor/github.com/flopp/go-staticmaps/tile_fetcher.go
generated
vendored
Normal file
162
vendor/github.com/flopp/go-staticmaps/tile_fetcher.go
generated
vendored
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
// Copyright 2016, 2017 Florian Pigorsch. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package sm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
_ "image/jpeg" // to be able to decode jpegs
|
||||||
|
_ "image/png" // to be able to decode pngs
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/Wessie/appdirs"
|
||||||
|
)
|
||||||
|
|
||||||
|
var TileFetcherUserAgent = "Mozilla/5.0+(compatible; go-staticmaps/0.1; https://github.com/flopp/go-staticmaps)"
|
||||||
|
|
||||||
|
// TileFetcher downloads map tile images from a TileProvider
|
||||||
|
type TileFetcher struct {
|
||||||
|
tileProvider *TileProvider
|
||||||
|
cacheDir string
|
||||||
|
useCaching bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTileFetcher creates a new Tilefetcher struct
|
||||||
|
func NewTileFetcher(tileProvider *TileProvider) *TileFetcher {
|
||||||
|
t := new(TileFetcher)
|
||||||
|
t.tileProvider = tileProvider
|
||||||
|
app := appdirs.New("go-staticmaps", "flopp.net", "0.1")
|
||||||
|
t.cacheDir = fmt.Sprintf("%s/%s", app.UserCache(), tileProvider.Name)
|
||||||
|
t.useCaching = true
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TileFetcher) url(zoom, x, y int) string {
|
||||||
|
shard := ""
|
||||||
|
ss := len(t.tileProvider.Shards)
|
||||||
|
if len(t.tileProvider.Shards) > 0 {
|
||||||
|
shard = t.tileProvider.Shards[(x+y)%ss]
|
||||||
|
}
|
||||||
|
return t.tileProvider.getURL(shard, zoom, x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TileFetcher) cacheFileName(zoom int, x, y int) string {
|
||||||
|
return fmt.Sprintf("%s/%d/%d/%d", t.cacheDir, zoom, x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToggleCaching enables/disables caching
|
||||||
|
func (t *TileFetcher) ToggleCaching(enabled bool) {
|
||||||
|
t.useCaching = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch download (or retrieves from the cache) a tile image for the specified zoom level and tile coordinates
|
||||||
|
func (t *TileFetcher) Fetch(zoom, x, y int) (image.Image, error) {
|
||||||
|
if t.useCaching {
|
||||||
|
fileName := t.cacheFileName(zoom, x, y)
|
||||||
|
cachedImg, err := t.loadCache(fileName)
|
||||||
|
if err == nil {
|
||||||
|
return cachedImg, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
url := t.url(zoom, x, y)
|
||||||
|
data, err := t.download(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
img, _, err := image.Decode(bytes.NewBuffer(data))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.useCaching {
|
||||||
|
fileName := t.cacheFileName(zoom, x, y)
|
||||||
|
if err := t.storeCache(fileName, data); err != nil {
|
||||||
|
log.Printf("Failed to store map tile as '%s': %s", fileName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return img, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TileFetcher) download(url string) ([]byte, error) {
|
||||||
|
req, _ := http.NewRequest("GET", url, nil)
|
||||||
|
req.Header.Set("User-Agent", TileFetcherUserAgent)
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return nil, fmt.Errorf("GET %s: %s", url, resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
contents, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return contents, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TileFetcher) loadCache(fileName string) (image.Image, error) {
|
||||||
|
file, err := os.Open(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
img, _, err := image.Decode(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return img, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TileFetcher) createCacheDir(path string) error {
|
||||||
|
src, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return os.MkdirAll(path, 0777)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if src.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("File exists but is not a directory: %s", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TileFetcher) storeCache(fileName string, data []byte) error {
|
||||||
|
dir, _ := filepath.Split(fileName)
|
||||||
|
|
||||||
|
if err := t.createCacheDir(dir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
if _, err = io.Copy(file, bytes.NewBuffer(data)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
158
vendor/github.com/flopp/go-staticmaps/tile_provider.go
generated
vendored
Normal file
158
vendor/github.com/flopp/go-staticmaps/tile_provider.go
generated
vendored
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
// Copyright 2016, 2017 Florian Pigorsch. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package sm
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// TileProvider encapsulates all infos about a map tile provider service (name, url scheme, attribution, etc.)
|
||||||
|
type TileProvider struct {
|
||||||
|
Name string
|
||||||
|
Attribution string
|
||||||
|
TileSize int
|
||||||
|
URLPattern string // "%[1]s" => shard, "%[2]d" => zoom, "%[3]d" => x, "%[4]d" => y
|
||||||
|
Shards []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TileProvider) getURL(shard string, zoom, x, y int) string {
|
||||||
|
return fmt.Sprintf(t.URLPattern, shard, zoom, x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTileProviderOpenStreetMaps creates a TileProvider struct for OSM's tile service
|
||||||
|
func NewTileProviderOpenStreetMaps() *TileProvider {
|
||||||
|
t := new(TileProvider)
|
||||||
|
t.Name = "osm"
|
||||||
|
t.Attribution = "Maps and Data (c) openstreetmap.org and contributors, ODbL"
|
||||||
|
t.TileSize = 256
|
||||||
|
t.URLPattern = "http://%[1]s.tile.openstreetmap.org/%[2]d/%[3]d/%[4]d.png"
|
||||||
|
t.Shards = []string{"a", "b", "c"}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTileProviderThunderforest(name string) *TileProvider {
|
||||||
|
t := new(TileProvider)
|
||||||
|
t.Name = fmt.Sprintf("thunderforest-%s", name)
|
||||||
|
t.Attribution = "Maps (c) Thundeforest; Data (c) OSM and contributors, ODbL"
|
||||||
|
t.TileSize = 256
|
||||||
|
t.URLPattern = "https://%[1]s.tile.thunderforest.com/" + name + "/%[2]d/%[3]d/%[4]d.png"
|
||||||
|
t.Shards = []string{"a", "b", "c"}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTileProviderThunderforestLandscape creates a TileProvider struct for thundeforests's 'landscape' tile service
|
||||||
|
func NewTileProviderThunderforestLandscape() *TileProvider {
|
||||||
|
return newTileProviderThunderforest("landscape")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTileProviderThunderforestOutdoors creates a TileProvider struct for thundeforests's 'outdoors' tile service
|
||||||
|
func NewTileProviderThunderforestOutdoors() *TileProvider {
|
||||||
|
return newTileProviderThunderforest("outdoors")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTileProviderThunderforestTransport creates a TileProvider struct for thundeforests's 'transport' tile service
|
||||||
|
func NewTileProviderThunderforestTransport() *TileProvider {
|
||||||
|
return newTileProviderThunderforest("transport")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTileProviderStamenToner creates a TileProvider struct for stamens' 'toner' tile service
|
||||||
|
func NewTileProviderStamenToner() *TileProvider {
|
||||||
|
t := new(TileProvider)
|
||||||
|
t.Name = "stamen-toner"
|
||||||
|
t.Attribution = "Maps (c) Stamen; Data (c) OSM and contributors, ODbL"
|
||||||
|
t.TileSize = 256
|
||||||
|
t.URLPattern = "http://%[1]s.tile.stamen.com/toner/%[2]d/%[3]d/%[4]d.png"
|
||||||
|
t.Shards = []string{"a", "b", "c", "d"}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTileProviderStamenTerrain creates a TileProvider struct for stamens' 'terrain' tile service
|
||||||
|
func NewTileProviderStamenTerrain() *TileProvider {
|
||||||
|
t := new(TileProvider)
|
||||||
|
t.Name = "stamen-terrain"
|
||||||
|
t.Attribution = "Maps (c) Stamen; Data (c) OSM and contributors, ODbL"
|
||||||
|
t.TileSize = 256
|
||||||
|
t.URLPattern = "http://%[1]s.tile.stamen.com/terrain/%[2]d/%[3]d/%[4]d.png"
|
||||||
|
t.Shards = []string{"a", "b", "c", "d"}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTileProviderOpenTopoMap creates a TileProvider struct for opentopomap's tile service
|
||||||
|
func NewTileProviderOpenTopoMap() *TileProvider {
|
||||||
|
t := new(TileProvider)
|
||||||
|
t.Name = "opentopomap"
|
||||||
|
t.Attribution = "Maps (c) OpenTopoMap [CC-BY-SA]; Data (c) OSM and contributors [ODbL]; Data (c) SRTM"
|
||||||
|
t.TileSize = 256
|
||||||
|
t.URLPattern = "http://%[1]s.tile.opentopomap.org/%[2]d/%[3]d/%[4]d.png"
|
||||||
|
t.Shards = []string{"a", "b", "c"}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTileProviderWikimedia creates a TileProvider struct for Wikimedia's tile service
|
||||||
|
func NewTileProviderWikimedia() *TileProvider {
|
||||||
|
t := new(TileProvider)
|
||||||
|
t.Name = "wikimedia"
|
||||||
|
t.Attribution = "Map (c) Wikimedia; Data (c) OSM and contributors, ODbL."
|
||||||
|
t.TileSize = 256
|
||||||
|
t.URLPattern = "https://maps.wikimedia.org/osm-intl/%[2]d/%[3]d/%[4]d.png"
|
||||||
|
t.Shards = []string{}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTileProviderOpenCycleMap creates a TileProvider struct for OpenCycleMap's tile service
|
||||||
|
func NewTileProviderOpenCycleMap() *TileProvider {
|
||||||
|
t := new(TileProvider)
|
||||||
|
t.Name = "cycle"
|
||||||
|
t.Attribution = "Maps and Data (c) openstreetmaps.org and contributors, ODbL"
|
||||||
|
t.TileSize = 256
|
||||||
|
t.URLPattern = "http://%[1]s.tile.opencyclemap.org/cycle/%[2]d/%[3]d/%[4]d.png"
|
||||||
|
t.Shards = []string{"a", "b"}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTileProviderCarto(name string) *TileProvider {
|
||||||
|
t := new(TileProvider)
|
||||||
|
t.Name = fmt.Sprintf("carto-%s", name)
|
||||||
|
t.Attribution = "Map (c) Carto [CC BY 3.0] Data (c) OSM and contributors, ODbL."
|
||||||
|
t.TileSize = 256
|
||||||
|
t.URLPattern = "https://cartodb-basemaps-%[1]s.global.ssl.fastly.net/" + name + "_all/%[2]d/%[3]d/%[4]d.png"
|
||||||
|
t.Shards = []string{"a", "b", "c", "d"}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTileProviderCartoLight creates a TileProvider struct for Carto's tile service (light variant)
|
||||||
|
func NewTileProviderCartoLight() *TileProvider {
|
||||||
|
return newTileProviderCarto("light")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTileProviderCartoDark creates a TileProvider struct for Carto's tile service (dark variant)
|
||||||
|
func NewTileProviderCartoDark() *TileProvider {
|
||||||
|
return newTileProviderCarto("dark")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTileProviders returns a map of all available TileProviders
|
||||||
|
func GetTileProviders() map[string]*TileProvider {
|
||||||
|
m := make(map[string]*TileProvider)
|
||||||
|
|
||||||
|
list := []*TileProvider{
|
||||||
|
NewTileProviderOpenStreetMaps(),
|
||||||
|
NewTileProviderOpenCycleMap(),
|
||||||
|
NewTileProviderThunderforestLandscape(),
|
||||||
|
NewTileProviderThunderforestOutdoors(),
|
||||||
|
NewTileProviderThunderforestTransport(),
|
||||||
|
NewTileProviderStamenToner(),
|
||||||
|
NewTileProviderStamenTerrain(),
|
||||||
|
NewTileProviderOpenTopoMap(),
|
||||||
|
NewTileProviderOpenStreetMaps(),
|
||||||
|
NewTileProviderOpenCycleMap(),
|
||||||
|
NewTileProviderCartoLight(),
|
||||||
|
NewTileProviderCartoDark(),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tp := range list {
|
||||||
|
m[tp.Name] = tp
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
18
vendor/github.com/flopp/go-staticmaps/util.go
generated
vendored
Normal file
18
vendor/github.com/flopp/go-staticmaps/util.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright 2016, 2017 Florian Pigorsch. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package sm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// hasPrefix checks if 's' has prefix 'prefix'; returns 'true' and the remainder on success, and 'false', 's' otherwise.
|
||||||
|
func hasPrefix(s string, prefix string) (bool, string) {
|
||||||
|
if strings.HasPrefix(s, prefix) {
|
||||||
|
return true, strings.TrimPrefix(s, prefix)
|
||||||
|
}
|
||||||
|
return false, s
|
||||||
|
}
|
2
vendor/github.com/fogleman/gg/.gitignore
generated
vendored
Normal file
2
vendor/github.com/fogleman/gg/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
*.png
|
||||||
|
|
19
vendor/github.com/fogleman/gg/LICENSE.md
generated
vendored
Normal file
19
vendor/github.com/fogleman/gg/LICENSE.md
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (C) 2016 Michael Fogleman
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
216
vendor/github.com/fogleman/gg/README.md
generated
vendored
Normal file
216
vendor/github.com/fogleman/gg/README.md
generated
vendored
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
# Go Graphics
|
||||||
|
|
||||||
|
`gg` is a library for rendering 2D graphics in pure Go.
|
||||||
|
|
||||||
|
![Stars](http://i.imgur.com/CylQIJt.png)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
go get -u github.com/fogleman/gg
|
||||||
|
|
||||||
|
Alternatively, you may use gopkg.in to grab a specific major-version:
|
||||||
|
|
||||||
|
go get -u gopkg.in/fogleman/gg.v1
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
https://godoc.org/github.com/fogleman/gg
|
||||||
|
|
||||||
|
## Hello, Circle!
|
||||||
|
|
||||||
|
Look how easy!
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/fogleman/gg"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
dc := gg.NewContext(1000, 1000)
|
||||||
|
dc.DrawCircle(500, 500, 400)
|
||||||
|
dc.SetRGB(0, 0, 0)
|
||||||
|
dc.Fill()
|
||||||
|
dc.SavePNG("out.png")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
There are [lots of examples](https://github.com/fogleman/gg/tree/master/examples) included. They're mostly for testing the code, but they're good for learning, too.
|
||||||
|
|
||||||
|
![Examples](http://i.imgur.com/tMFoyzu.png)
|
||||||
|
|
||||||
|
## Creating Contexts
|
||||||
|
|
||||||
|
There are a few ways of creating a context.
|
||||||
|
|
||||||
|
```go
|
||||||
|
NewContext(width, height int) *Context
|
||||||
|
NewContextForImage(im image.Image) *Context
|
||||||
|
NewContextForRGBA(im *image.RGBA) *Context
|
||||||
|
```
|
||||||
|
|
||||||
|
## Drawing Functions
|
||||||
|
|
||||||
|
Ever used a graphics library that didn't have functions for drawing rectangles
|
||||||
|
or circles? What a pain!
|
||||||
|
|
||||||
|
```go
|
||||||
|
DrawPoint(x, y, r float64)
|
||||||
|
DrawLine(x1, y1, x2, y2 float64)
|
||||||
|
DrawRectangle(x, y, w, h float64)
|
||||||
|
DrawRoundedRectangle(x, y, w, h, r float64)
|
||||||
|
DrawCircle(x, y, r float64)
|
||||||
|
DrawArc(x, y, r, angle1, angle2 float64)
|
||||||
|
DrawEllipse(x, y, rx, ry float64)
|
||||||
|
DrawEllipticalArc(x, y, rx, ry, angle1, angle2 float64)
|
||||||
|
DrawRegularPolygon(n int, x, y, r, rotation float64)
|
||||||
|
DrawImage(im image.Image, x, y int)
|
||||||
|
DrawImageAnchored(im image.Image, x, y int, ax, ay float64)
|
||||||
|
SetPixel(x, y int)
|
||||||
|
|
||||||
|
MoveTo(x, y float64)
|
||||||
|
LineTo(x, y float64)
|
||||||
|
QuadraticTo(x1, y1, x2, y2 float64)
|
||||||
|
CubicTo(x1, y1, x2, y2, x3, y3 float64)
|
||||||
|
ClosePath()
|
||||||
|
ClearPath()
|
||||||
|
NewSubPath()
|
||||||
|
|
||||||
|
Clear()
|
||||||
|
Stroke()
|
||||||
|
Fill()
|
||||||
|
StrokePreserve()
|
||||||
|
FillPreserve()
|
||||||
|
```
|
||||||
|
|
||||||
|
It is often desired to center an image at a point. Use `DrawImageAnchored` with `ax` and `ay` set to 0.5 to do this. Use 0 to left or top align. Use 1 to right or bottom align. `DrawStringAnchored` does the same for text, so you don't need to call `MeasureString` yourself.
|
||||||
|
|
||||||
|
## Text Functions
|
||||||
|
|
||||||
|
It will even do word wrap for you!
|
||||||
|
|
||||||
|
```go
|
||||||
|
DrawString(s string, x, y float64)
|
||||||
|
DrawStringAnchored(s string, x, y, ax, ay float64)
|
||||||
|
DrawStringWrapped(s string, x, y, ax, ay, width, lineSpacing float64, align Align)
|
||||||
|
MeasureString(s string) (w, h float64)
|
||||||
|
WordWrap(s string, w float64) []string
|
||||||
|
SetFontFace(fontFace font.Face)
|
||||||
|
LoadFontFace(path string, points float64) error
|
||||||
|
```
|
||||||
|
|
||||||
|
## Color Functions
|
||||||
|
|
||||||
|
Colors can be set in several different ways for your convenience.
|
||||||
|
|
||||||
|
```go
|
||||||
|
SetRGB(r, g, b float64)
|
||||||
|
SetRGBA(r, g, b, a float64)
|
||||||
|
SetRGB255(r, g, b int)
|
||||||
|
SetRGBA255(r, g, b, a int)
|
||||||
|
SetColor(c color.Color)
|
||||||
|
SetHexColor(x string)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Stroke & Fill Options
|
||||||
|
|
||||||
|
```go
|
||||||
|
SetLineWidth(lineWidth float64)
|
||||||
|
SetLineCap(lineCap LineCap)
|
||||||
|
SetLineJoin(lineJoin LineJoin)
|
||||||
|
SetDash(dashes ...float64)
|
||||||
|
SetFillRule(fillRule FillRule)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gradients & Patterns
|
||||||
|
|
||||||
|
`gg` supports linear and radial gradients and surface patterns. You can also implement your own patterns.
|
||||||
|
|
||||||
|
```go
|
||||||
|
SetFillStyle(pattern Pattern)
|
||||||
|
SetStrokeStyle(pattern Pattern)
|
||||||
|
NewSolidPattern(color color.Color)
|
||||||
|
NewLinearGradient(x0, y0, x1, y1 float64)
|
||||||
|
NewRadialGradient(x0, y0, r0, x1, y1, r1 float64)
|
||||||
|
NewSurfacePattern(im image.Image, op RepeatOp)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Transformation Functions
|
||||||
|
|
||||||
|
```go
|
||||||
|
Identity()
|
||||||
|
Translate(x, y float64)
|
||||||
|
Scale(x, y float64)
|
||||||
|
Rotate(angle float64)
|
||||||
|
Shear(x, y float64)
|
||||||
|
ScaleAbout(sx, sy, x, y float64)
|
||||||
|
RotateAbout(angle, x, y float64)
|
||||||
|
ShearAbout(sx, sy, x, y float64)
|
||||||
|
TransformPoint(x, y float64) (tx, ty float64)
|
||||||
|
InvertY()
|
||||||
|
```
|
||||||
|
|
||||||
|
It is often desired to rotate or scale about a point that is not the origin. The functions `RotateAbout`, `ScaleAbout`, `ShearAbout` are provided as a convenience.
|
||||||
|
|
||||||
|
`InvertY` is provided in case Y should increase from bottom to top vs. the default top to bottom.
|
||||||
|
|
||||||
|
## Stack Functions
|
||||||
|
|
||||||
|
Save and restore the state of the context. These can be nested.
|
||||||
|
|
||||||
|
```go
|
||||||
|
Push()
|
||||||
|
Pop()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Clipping Functions
|
||||||
|
|
||||||
|
Use clipping regions to restrict drawing operations to an area that you
|
||||||
|
defined using paths.
|
||||||
|
|
||||||
|
```go
|
||||||
|
Clip()
|
||||||
|
ClipPreserve()
|
||||||
|
ResetClip()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Helper Functions
|
||||||
|
|
||||||
|
Sometimes you just don't want to write these yourself.
|
||||||
|
|
||||||
|
```go
|
||||||
|
Radians(degrees float64) float64
|
||||||
|
Degrees(radians float64) float64
|
||||||
|
LoadImage(path string) (image.Image, error)
|
||||||
|
LoadPNG(path string) (image.Image, error)
|
||||||
|
SavePNG(path string, im image.Image) error
|
||||||
|
```
|
||||||
|
|
||||||
|
![Separator](http://i.imgur.com/fsUvnPB.png)
|
||||||
|
|
||||||
|
## Another Example
|
||||||
|
|
||||||
|
See the output of this example below.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/fogleman/gg"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
const S = 1024
|
||||||
|
dc := gg.NewContext(S, S)
|
||||||
|
dc.SetRGBA(0, 0, 0, 0.1)
|
||||||
|
for i := 0; i < 360; i += 15 {
|
||||||
|
dc.Push()
|
||||||
|
dc.RotateAbout(gg.Radians(float64(i)), S/2, S/2)
|
||||||
|
dc.DrawEllipse(S/2, S/2, S*7/16, S/8)
|
||||||
|
dc.Fill()
|
||||||
|
dc.Pop()
|
||||||
|
}
|
||||||
|
dc.SavePNG("out.png")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
![Ellipses](http://i.imgur.com/J9CBZef.png)
|
59
vendor/github.com/fogleman/gg/bezier.go
generated
vendored
Normal file
59
vendor/github.com/fogleman/gg/bezier.go
generated
vendored
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package gg
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
func quadratic(x0, y0, x1, y1, x2, y2, t float64) (x, y float64) {
|
||||||
|
u := 1 - t
|
||||||
|
a := u * u
|
||||||
|
b := 2 * u * t
|
||||||
|
c := t * t
|
||||||
|
x = a*x0 + b*x1 + c*x2
|
||||||
|
y = a*y0 + b*y1 + c*y2
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func QuadraticBezier(x0, y0, x1, y1, x2, y2 float64) []Point {
|
||||||
|
l := (math.Hypot(x1-x0, y1-y0) +
|
||||||
|
math.Hypot(x2-x1, y2-y1))
|
||||||
|
n := int(l + 0.5)
|
||||||
|
if n < 4 {
|
||||||
|
n = 4
|
||||||
|
}
|
||||||
|
d := float64(n) - 1
|
||||||
|
result := make([]Point, n)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
t := float64(i) / d
|
||||||
|
x, y := quadratic(x0, y0, x1, y1, x2, y2, t)
|
||||||
|
result[i] = Point{x, y}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func cubic(x0, y0, x1, y1, x2, y2, x3, y3, t float64) (x, y float64) {
|
||||||
|
u := 1 - t
|
||||||
|
a := u * u * u
|
||||||
|
b := 3 * u * u * t
|
||||||
|
c := 3 * u * t * t
|
||||||
|
d := t * t * t
|
||||||
|
x = a*x0 + b*x1 + c*x2 + d*x3
|
||||||
|
y = a*y0 + b*y1 + c*y2 + d*y3
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func CubicBezier(x0, y0, x1, y1, x2, y2, x3, y3 float64) []Point {
|
||||||
|
l := (math.Hypot(x1-x0, y1-y0) +
|
||||||
|
math.Hypot(x2-x1, y2-y1) +
|
||||||
|
math.Hypot(x3-x2, y3-y2))
|
||||||
|
n := int(l + 0.5)
|
||||||
|
if n < 4 {
|
||||||
|
n = 4
|
||||||
|
}
|
||||||
|
d := float64(n) - 1
|
||||||
|
result := make([]Point, n)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
t := float64(i) / d
|
||||||
|
x, y := cubic(x0, y0, x1, y1, x2, y2, x3, y3, t)
|
||||||
|
result[i] = Point{x, y}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
795
vendor/github.com/fogleman/gg/context.go
generated
vendored
Normal file
795
vendor/github.com/fogleman/gg/context.go
generated
vendored
Normal file
|
@ -0,0 +1,795 @@
|
||||||
|
// Package gg provides a simple API for rendering 2D graphics in pure Go.
|
||||||
|
package gg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/png"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/golang/freetype/raster"
|
||||||
|
"golang.org/x/image/draw"
|
||||||
|
"golang.org/x/image/font"
|
||||||
|
"golang.org/x/image/font/basicfont"
|
||||||
|
"golang.org/x/image/math/f64"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LineCap int
|
||||||
|
|
||||||
|
const (
|
||||||
|
LineCapRound LineCap = iota
|
||||||
|
LineCapButt
|
||||||
|
LineCapSquare
|
||||||
|
)
|
||||||
|
|
||||||
|
type LineJoin int
|
||||||
|
|
||||||
|
const (
|
||||||
|
LineJoinRound LineJoin = iota
|
||||||
|
LineJoinBevel
|
||||||
|
)
|
||||||
|
|
||||||
|
type FillRule int
|
||||||
|
|
||||||
|
const (
|
||||||
|
FillRuleWinding FillRule = iota
|
||||||
|
FillRuleEvenOdd
|
||||||
|
)
|
||||||
|
|
||||||
|
type Align int
|
||||||
|
|
||||||
|
const (
|
||||||
|
AlignLeft Align = iota
|
||||||
|
AlignCenter
|
||||||
|
AlignRight
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultFillStyle = NewSolidPattern(color.White)
|
||||||
|
defaultStrokeStyle = NewSolidPattern(color.Black)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Context struct {
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
im *image.RGBA
|
||||||
|
mask *image.Alpha
|
||||||
|
color color.Color
|
||||||
|
fillPattern Pattern
|
||||||
|
strokePattern Pattern
|
||||||
|
strokePath raster.Path
|
||||||
|
fillPath raster.Path
|
||||||
|
start Point
|
||||||
|
current Point
|
||||||
|
hasCurrent bool
|
||||||
|
dashes []float64
|
||||||
|
lineWidth float64
|
||||||
|
lineCap LineCap
|
||||||
|
lineJoin LineJoin
|
||||||
|
fillRule FillRule
|
||||||
|
fontFace font.Face
|
||||||
|
fontHeight float64
|
||||||
|
matrix Matrix
|
||||||
|
stack []*Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContext creates a new image.RGBA with the specified width and height
|
||||||
|
// and prepares a context for rendering onto that image.
|
||||||
|
func NewContext(width, height int) *Context {
|
||||||
|
return NewContextForRGBA(image.NewRGBA(image.Rect(0, 0, width, height)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContextForImage copies the specified image into a new image.RGBA
|
||||||
|
// and prepares a context for rendering onto that image.
|
||||||
|
func NewContextForImage(im image.Image) *Context {
|
||||||
|
return NewContextForRGBA(imageToRGBA(im))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContextForRGBA prepares a context for rendering onto the specified image.
|
||||||
|
// No copy is made.
|
||||||
|
func NewContextForRGBA(im *image.RGBA) *Context {
|
||||||
|
return &Context{
|
||||||
|
width: im.Bounds().Size().X,
|
||||||
|
height: im.Bounds().Size().Y,
|
||||||
|
im: im,
|
||||||
|
color: color.Transparent,
|
||||||
|
fillPattern: defaultFillStyle,
|
||||||
|
strokePattern: defaultStrokeStyle,
|
||||||
|
lineWidth: 1,
|
||||||
|
fillRule: FillRuleWinding,
|
||||||
|
fontFace: basicfont.Face7x13,
|
||||||
|
fontHeight: 13,
|
||||||
|
matrix: Identity(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image returns the image that has been drawn by this context.
|
||||||
|
func (dc *Context) Image() image.Image {
|
||||||
|
return dc.im
|
||||||
|
}
|
||||||
|
|
||||||
|
// Width returns the width of the image in pixels.
|
||||||
|
func (dc *Context) Width() int {
|
||||||
|
return dc.width
|
||||||
|
}
|
||||||
|
|
||||||
|
// Height returns the height of the image in pixels.
|
||||||
|
func (dc *Context) Height() int {
|
||||||
|
return dc.height
|
||||||
|
}
|
||||||
|
|
||||||
|
// SavePNG encodes the image as a PNG and writes it to disk.
|
||||||
|
func (dc *Context) SavePNG(path string) error {
|
||||||
|
return SavePNG(path, dc.im)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodePNG encodes the image as a PNG and writes it to the provided io.Writer.
|
||||||
|
func (dc *Context) EncodePNG(w io.Writer) error {
|
||||||
|
return png.Encode(w, dc.im)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDash sets the current dash pattern to use. Call with zero arguments to
|
||||||
|
// disable dashes. The values specify the lengths of each dash, with
|
||||||
|
// alternating on and off lengths.
|
||||||
|
func (dc *Context) SetDash(dashes ...float64) {
|
||||||
|
dc.dashes = dashes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *Context) SetLineWidth(lineWidth float64) {
|
||||||
|
dc.lineWidth = lineWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *Context) SetLineCap(lineCap LineCap) {
|
||||||
|
dc.lineCap = lineCap
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *Context) SetLineCapRound() {
|
||||||
|
dc.lineCap = LineCapRound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *Context) SetLineCapButt() {
|
||||||
|
dc.lineCap = LineCapButt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *Context) SetLineCapSquare() {
|
||||||
|
dc.lineCap = LineCapSquare
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *Context) SetLineJoin(lineJoin LineJoin) {
|
||||||
|
dc.lineJoin = lineJoin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *Context) SetLineJoinRound() {
|
||||||
|
dc.lineJoin = LineJoinRound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *Context) SetLineJoinBevel() {
|
||||||
|
dc.lineJoin = LineJoinBevel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *Context) SetFillRule(fillRule FillRule) {
|
||||||
|
dc.fillRule = fillRule
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *Context) SetFillRuleWinding() {
|
||||||
|
dc.fillRule = FillRuleWinding
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *Context) SetFillRuleEvenOdd() {
|
||||||
|
dc.fillRule = FillRuleEvenOdd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Color Setters
|
||||||
|
|
||||||
|
func (dc *Context) setFillAndStrokeColor(c color.Color) {
|
||||||
|
dc.color = c
|
||||||
|
dc.fillPattern = NewSolidPattern(c)
|
||||||
|
dc.strokePattern = NewSolidPattern(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFillStyle sets current fill style
|
||||||
|
func (dc *Context) SetFillStyle(pattern Pattern) {
|
||||||
|
// if pattern is SolidPattern, also change dc.color(for dc.Clear, dc.drawString)
|
||||||
|
if fillStyle, ok := pattern.(*solidPattern); ok {
|
||||||
|
dc.color = fillStyle.color
|
||||||
|
}
|
||||||
|
dc.fillPattern = pattern
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStrokeStyle sets current stroke style
|
||||||
|
func (dc *Context) SetStrokeStyle(pattern Pattern) {
|
||||||
|
dc.strokePattern = pattern
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetColor sets the current color(for both fill and stroke).
|
||||||
|
func (dc *Context) SetColor(c color.Color) {
|
||||||
|
dc.setFillAndStrokeColor(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHexColor sets the current color using a hex string. The leading pound
|
||||||
|
// sign (#) is optional. Both 3- and 6-digit variations are supported. 8 digits
|
||||||
|
// may be provided to set the alpha value as well.
|
||||||
|
func (dc *Context) SetHexColor(x string) {
|
||||||
|
r, g, b, a := parseHexColor(x)
|
||||||
|
dc.SetRGBA255(r, g, b, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRGBA255 sets the current color. r, g, b, a values should be between 0 and
|
||||||
|
// 255, inclusive.
|
||||||
|
func (dc *Context) SetRGBA255(r, g, b, a int) {
|
||||||
|
dc.color = color.NRGBA{uint8(r), uint8(g), uint8(b), uint8(a)}
|
||||||
|
dc.setFillAndStrokeColor(dc.color)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRGB255 sets the current color. r, g, b values should be between 0 and 255,
|
||||||
|
// inclusive. Alpha will be set to 255 (fully opaque).
|
||||||
|
func (dc *Context) SetRGB255(r, g, b int) {
|
||||||
|
dc.SetRGBA255(r, g, b, 255)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRGBA sets the current color. r, g, b, a values should be between 0 and 1,
|
||||||
|
// inclusive.
|
||||||
|
func (dc *Context) SetRGBA(r, g, b, a float64) {
|
||||||
|
dc.color = color.NRGBA{
|
||||||
|
uint8(r * 255),
|
||||||
|
uint8(g * 255),
|
||||||
|
uint8(b * 255),
|
||||||
|
uint8(a * 255),
|
||||||
|
}
|
||||||
|
dc.setFillAndStrokeColor(dc.color)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRGB sets the current color. r, g, b values should be between 0 and 1,
|
||||||
|
// inclusive. Alpha will be set to 1 (fully opaque).
|
||||||
|
func (dc *Context) SetRGB(r, g, b float64) {
|
||||||
|
dc.SetRGBA(r, g, b, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path Manipulation
|
||||||
|
|
||||||
|
// MoveTo starts a new subpath within the current path starting at the
|
||||||
|
// specified point.
|
||||||
|
func (dc *Context) MoveTo(x, y float64) {
|
||||||
|
if dc.hasCurrent {
|
||||||
|
dc.fillPath.Add1(dc.start.Fixed())
|
||||||
|
}
|
||||||
|
x, y = dc.TransformPoint(x, y)
|
||||||
|
p := Point{x, y}
|
||||||
|
dc.strokePath.Start(p.Fixed())
|
||||||
|
dc.fillPath.Start(p.Fixed())
|
||||||
|
dc.start = p
|
||||||
|
dc.current = p
|
||||||
|
dc.hasCurrent = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// LineTo adds a line segment to the current path starting at the current
|
||||||
|
// point. If there is no current point, it is equivalent to MoveTo(x, y)
|
||||||
|
func (dc *Context) LineTo(x, y float64) {
|
||||||
|
if !dc.hasCurrent {
|
||||||
|
dc.MoveTo(x, y)
|
||||||
|
} else {
|
||||||
|
x, y = dc.TransformPoint(x, y)
|
||||||
|
p := Point{x, y}
|
||||||
|
dc.strokePath.Add1(p.Fixed())
|
||||||
|
dc.fillPath.Add1(p.Fixed())
|
||||||
|
dc.current = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuadraticTo adds a quadratic bezier curve to the current path starting at
|
||||||
|
// the current point. If there is no current point, it first performs
|
||||||
|
// MoveTo(x1, y1)
|
||||||
|
func (dc *Context) QuadraticTo(x1, y1, x2, y2 float64) {
|
||||||
|
if !dc.hasCurrent {
|
||||||
|
dc.MoveTo(x1, y1)
|
||||||
|
}
|
||||||
|
x1, y1 = dc.TransformPoint(x1, y1)
|
||||||
|
x2, y2 = dc.TransformPoint(x2, y2)
|
||||||
|
p1 := Point{x1, y1}
|
||||||
|
p2 := Point{x2, y2}
|
||||||
|
dc.strokePath.Add2(p1.Fixed(), p2.Fixed())
|
||||||
|
dc.fillPath.Add2(p1.Fixed(), p2.Fixed())
|
||||||
|
dc.current = p2
|
||||||
|
}
|
||||||
|
|
||||||
|
// CubicTo adds a cubic bezier curve to the current path starting at the
|
||||||
|
// current point. If there is no current point, it first performs
|
||||||
|
// MoveTo(x1, y1). Because freetype/raster does not support cubic beziers,
|
||||||
|
// this is emulated with many small line segments.
|
||||||
|
func (dc *Context) CubicTo(x1, y1, x2, y2, x3, y3 float64) {
|
||||||
|
if !dc.hasCurrent {
|
||||||
|
dc.MoveTo(x1, y1)
|
||||||
|
}
|
||||||
|
x0, y0 := dc.current.X, dc.current.Y
|
||||||
|
x1, y1 = dc.TransformPoint(x1, y1)
|
||||||
|
x2, y2 = dc.TransformPoint(x2, y2)
|
||||||
|
x3, y3 = dc.TransformPoint(x3, y3)
|
||||||
|
points := CubicBezier(x0, y0, x1, y1, x2, y2, x3, y3)
|
||||||
|
previous := dc.current.Fixed()
|
||||||
|
for _, p := range points[1:] {
|
||||||
|
f := p.Fixed()
|
||||||
|
if f == previous {
|
||||||
|
// TODO: this fixes some rendering issues but not all
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
previous = f
|
||||||
|
dc.strokePath.Add1(f)
|
||||||
|
dc.fillPath.Add1(f)
|
||||||
|
dc.current = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClosePath adds a line segment from the current point to the beginning
|
||||||
|
// of the current subpath. If there is no current point, this is a no-op.
|
||||||
|
func (dc *Context) ClosePath() {
|
||||||
|
if dc.hasCurrent {
|
||||||
|
dc.strokePath.Add1(dc.start.Fixed())
|
||||||
|
dc.fillPath.Add1(dc.start.Fixed())
|
||||||
|
dc.current = dc.start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearPath clears the current path. There is no current point after this
|
||||||
|
// operation.
|
||||||
|
func (dc *Context) ClearPath() {
|
||||||
|
dc.strokePath.Clear()
|
||||||
|
dc.fillPath.Clear()
|
||||||
|
dc.hasCurrent = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSubPath starts a new subpath within the current path. There is no current
|
||||||
|
// point after this operation.
|
||||||
|
func (dc *Context) NewSubPath() {
|
||||||
|
if dc.hasCurrent {
|
||||||
|
dc.fillPath.Add1(dc.start.Fixed())
|
||||||
|
}
|
||||||
|
dc.hasCurrent = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path Drawing
|
||||||
|
|
||||||
|
func (dc *Context) capper() raster.Capper {
|
||||||
|
switch dc.lineCap {
|
||||||
|
case LineCapButt:
|
||||||
|
return raster.ButtCapper
|
||||||
|
case LineCapRound:
|
||||||
|
return raster.RoundCapper
|
||||||
|
case LineCapSquare:
|
||||||
|
return raster.SquareCapper
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *Context) joiner() raster.Joiner {
|
||||||
|
switch dc.lineJoin {
|
||||||
|
case LineJoinBevel:
|
||||||
|
return raster.BevelJoiner
|
||||||
|
case LineJoinRound:
|
||||||
|
return raster.RoundJoiner
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *Context) stroke(painter raster.Painter) {
|
||||||
|
path := dc.strokePath
|
||||||
|
if len(dc.dashes) > 0 {
|
||||||
|
path = dashed(path, dc.dashes)
|
||||||
|
} else {
|
||||||
|
// TODO: this is a temporary workaround to remove tiny segments
|
||||||
|
// that result in rendering issues
|
||||||
|
path = rasterPath(flattenPath(path))
|
||||||
|
}
|
||||||
|
r := raster.NewRasterizer(dc.width, dc.height)
|
||||||
|
r.UseNonZeroWinding = true
|
||||||
|
r.AddStroke(path, fix(dc.lineWidth), dc.capper(), dc.joiner())
|
||||||
|
r.Rasterize(painter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *Context) fill(painter raster.Painter) {
|
||||||
|
path := dc.fillPath
|
||||||
|
if dc.hasCurrent {
|
||||||
|
path = make(raster.Path, len(dc.fillPath))
|
||||||
|
copy(path, dc.fillPath)
|
||||||
|
path.Add1(dc.start.Fixed())
|
||||||
|
}
|
||||||
|
r := raster.NewRasterizer(dc.width, dc.height)
|
||||||
|
r.UseNonZeroWinding = dc.fillRule == FillRuleWinding
|
||||||
|
r.AddPath(path)
|
||||||
|
r.Rasterize(painter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrokePreserve strokes the current path with the current color, line width,
|
||||||
|
// line cap, line join and dash settings. The path is preserved after this
|
||||||
|
// operation.
|
||||||
|
func (dc *Context) StrokePreserve() {
|
||||||
|
painter := newPatternPainter(dc.im, dc.mask, dc.strokePattern)
|
||||||
|
dc.stroke(painter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stroke strokes the current path with the current color, line width,
|
||||||
|
// line cap, line join and dash settings. The path is cleared after this
|
||||||
|
// operation.
|
||||||
|
func (dc *Context) Stroke() {
|
||||||
|
dc.StrokePreserve()
|
||||||
|
dc.ClearPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FillPreserve fills the current path with the current color. Open subpaths
|
||||||
|
// are implicity closed. The path is preserved after this operation.
|
||||||
|
func (dc *Context) FillPreserve() {
|
||||||
|
painter := newPatternPainter(dc.im, dc.mask, dc.fillPattern)
|
||||||
|
dc.fill(painter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill fills the current path with the current color. Open subpaths
|
||||||
|
// are implicity closed. The path is cleared after this operation.
|
||||||
|
func (dc *Context) Fill() {
|
||||||
|
dc.FillPreserve()
|
||||||
|
dc.ClearPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClipPreserve updates the clipping region by intersecting the current
|
||||||
|
// clipping region with the current path as it would be filled by dc.Fill().
|
||||||
|
// The path is preserved after this operation.
|
||||||
|
func (dc *Context) ClipPreserve() {
|
||||||
|
clip := image.NewAlpha(image.Rect(0, 0, dc.width, dc.height))
|
||||||
|
painter := raster.NewAlphaOverPainter(clip)
|
||||||
|
dc.fill(painter)
|
||||||
|
if dc.mask == nil {
|
||||||
|
dc.mask = clip
|
||||||
|
} else {
|
||||||
|
mask := image.NewAlpha(image.Rect(0, 0, dc.width, dc.height))
|
||||||
|
draw.DrawMask(mask, mask.Bounds(), clip, image.ZP, dc.mask, image.ZP, draw.Over)
|
||||||
|
dc.mask = mask
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clip updates the clipping region by intersecting the current
|
||||||
|
// clipping region with the current path as it would be filled by dc.Fill().
|
||||||
|
// The path is cleared after this operation.
|
||||||
|
func (dc *Context) Clip() {
|
||||||
|
dc.ClipPreserve()
|
||||||
|
dc.ClearPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetClip clears the clipping region.
|
||||||
|
func (dc *Context) ResetClip() {
|
||||||
|
dc.mask = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenient Drawing Functions
|
||||||
|
|
||||||
|
// Clear fills the entire image with the current color.
|
||||||
|
func (dc *Context) Clear() {
|
||||||
|
src := image.NewUniform(dc.color)
|
||||||
|
draw.Draw(dc.im, dc.im.Bounds(), src, image.ZP, draw.Src)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPixel sets the color of the specified pixel using the current color.
|
||||||
|
func (dc *Context) SetPixel(x, y int) {
|
||||||
|
dc.im.Set(x, y, dc.color)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrawPoint is like DrawCircle but ensures that a circle of the specified
|
||||||
|
// size is drawn regardless of the current transformation matrix. The position
|
||||||
|
// is still transformed, but not the shape of the point.
|
||||||
|
func (dc *Context) DrawPoint(x, y, r float64) {
|
||||||
|
dc.Push()
|
||||||
|
tx, ty := dc.TransformPoint(x, y)
|
||||||
|
dc.Identity()
|
||||||
|
dc.DrawCircle(tx, ty, r)
|
||||||
|
dc.Pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *Context) DrawLine(x1, y1, x2, y2 float64) {
|
||||||
|
dc.MoveTo(x1, y1)
|
||||||
|
dc.LineTo(x2, y2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *Context) DrawRectangle(x, y, w, h float64) {
|
||||||
|
dc.NewSubPath()
|
||||||
|
dc.MoveTo(x, y)
|
||||||
|
dc.LineTo(x+w, y)
|
||||||
|
dc.LineTo(x+w, y+h)
|
||||||
|
dc.LineTo(x, y+h)
|
||||||
|
dc.ClosePath()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *Context) DrawRoundedRectangle(x, y, w, h, r float64) {
|
||||||
|
x0, x1, x2, x3 := x, x+r, x+w-r, x+w
|
||||||
|
y0, y1, y2, y3 := y, y+r, y+h-r, y+h
|
||||||
|
dc.NewSubPath()
|
||||||
|
dc.MoveTo(x1, y0)
|
||||||
|
dc.LineTo(x2, y0)
|
||||||
|
dc.DrawArc(x2, y1, r, Radians(270), Radians(360))
|
||||||
|
dc.LineTo(x3, y2)
|
||||||
|
dc.DrawArc(x2, y2, r, Radians(0), Radians(90))
|
||||||
|
dc.LineTo(x1, y3)
|
||||||
|
dc.DrawArc(x1, y2, r, Radians(90), Radians(180))
|
||||||
|
dc.LineTo(x0, y1)
|
||||||
|
dc.DrawArc(x1, y1, r, Radians(180), Radians(270))
|
||||||
|
dc.ClosePath()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *Context) DrawEllipticalArc(x, y, rx, ry, angle1, angle2 float64) {
|
||||||
|
const n = 16
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
p1 := float64(i+0) / n
|
||||||
|
p2 := float64(i+1) / n
|
||||||
|
a1 := angle1 + (angle2-angle1)*p1
|
||||||
|
a2 := angle1 + (angle2-angle1)*p2
|
||||||
|
x0 := x + rx*math.Cos(a1)
|
||||||
|
y0 := y + ry*math.Sin(a1)
|
||||||
|
x1 := x + rx*math.Cos(a1+(a2-a1)/2)
|
||||||
|
y1 := y + ry*math.Sin(a1+(a2-a1)/2)
|
||||||
|
x2 := x + rx*math.Cos(a2)
|
||||||
|
y2 := y + ry*math.Sin(a2)
|
||||||
|
cx := 2*x1 - x0/2 - x2/2
|
||||||
|
cy := 2*y1 - y0/2 - y2/2
|
||||||
|
if i == 0 && !dc.hasCurrent {
|
||||||
|
dc.MoveTo(x0, y0)
|
||||||
|
}
|
||||||
|
dc.QuadraticTo(cx, cy, x2, y2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *Context) DrawEllipse(x, y, rx, ry float64) {
|
||||||
|
dc.NewSubPath()
|
||||||
|
dc.DrawEllipticalArc(x, y, rx, ry, 0, 2*math.Pi)
|
||||||
|
dc.ClosePath()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *Context) DrawArc(x, y, r, angle1, angle2 float64) {
|
||||||
|
dc.DrawEllipticalArc(x, y, r, r, angle1, angle2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *Context) DrawCircle(x, y, r float64) {
|
||||||
|
dc.NewSubPath()
|
||||||
|
dc.DrawEllipticalArc(x, y, r, r, 0, 2*math.Pi)
|
||||||
|
dc.ClosePath()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *Context) DrawRegularPolygon(n int, x, y, r, rotation float64) {
|
||||||
|
angle := 2 * math.Pi / float64(n)
|
||||||
|
rotation -= math.Pi / 2
|
||||||
|
if n%2 == 0 {
|
||||||
|
rotation += angle / 2
|
||||||
|
}
|
||||||
|
dc.NewSubPath()
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
a := rotation + angle*float64(i)
|
||||||
|
dc.LineTo(x+r*math.Cos(a), y+r*math.Sin(a))
|
||||||
|
}
|
||||||
|
dc.ClosePath()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrawImage draws the specified image at the specified point.
|
||||||
|
func (dc *Context) DrawImage(im image.Image, x, y int) {
|
||||||
|
dc.DrawImageAnchored(im, x, y, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrawImageAnchored draws the specified image at the specified anchor point.
|
||||||
|
// The anchor point is x - w * ax, y - h * ay, where w, h is the size of the
|
||||||
|
// image. Use ax=0.5, ay=0.5 to center the image at the specified point.
|
||||||
|
func (dc *Context) DrawImageAnchored(im image.Image, x, y int, ax, ay float64) {
|
||||||
|
s := im.Bounds().Size()
|
||||||
|
x -= int(ax * float64(s.X))
|
||||||
|
y -= int(ay * float64(s.Y))
|
||||||
|
transformer := draw.BiLinear
|
||||||
|
fx, fy := float64(x), float64(y)
|
||||||
|
m := dc.matrix.Translate(fx, fy)
|
||||||
|
s2d := f64.Aff3{m.XX, m.XY, m.X0, m.YX, m.YY, m.Y0}
|
||||||
|
if dc.mask == nil {
|
||||||
|
transformer.Transform(dc.im, s2d, im, im.Bounds(), draw.Over, nil)
|
||||||
|
} else {
|
||||||
|
transformer.Transform(dc.im, s2d, im, im.Bounds(), draw.Over, &draw.Options{
|
||||||
|
DstMask: dc.mask,
|
||||||
|
DstMaskP: image.ZP,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text Functions
|
||||||
|
|
||||||
|
func (dc *Context) SetFontFace(fontFace font.Face) {
|
||||||
|
dc.fontFace = fontFace
|
||||||
|
dc.fontHeight = float64(fontFace.Metrics().Height) / 64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *Context) LoadFontFace(path string, points float64) error {
|
||||||
|
face, err := LoadFontFace(path, points)
|
||||||
|
if err == nil {
|
||||||
|
dc.fontFace = face
|
||||||
|
dc.fontHeight = points * 72 / 96
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *Context) drawString(im *image.RGBA, s string, x, y float64) {
|
||||||
|
d := &font.Drawer{
|
||||||
|
Dst: im,
|
||||||
|
Src: image.NewUniform(dc.color),
|
||||||
|
Face: dc.fontFace,
|
||||||
|
Dot: fixp(x, y),
|
||||||
|
}
|
||||||
|
// based on Drawer.DrawString() in golang.org/x/image/font/font.go
|
||||||
|
prevC := rune(-1)
|
||||||
|
for _, c := range s {
|
||||||
|
if prevC >= 0 {
|
||||||
|
d.Dot.X += d.Face.Kern(prevC, c)
|
||||||
|
}
|
||||||
|
dr, mask, maskp, advance, ok := d.Face.Glyph(d.Dot, c)
|
||||||
|
if !ok {
|
||||||
|
// TODO: is falling back on the U+FFFD glyph the responsibility of
|
||||||
|
// the Drawer or the Face?
|
||||||
|
// TODO: set prevC = '\ufffd'?
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sr := dr.Sub(dr.Min)
|
||||||
|
transformer := draw.BiLinear
|
||||||
|
fx, fy := float64(dr.Min.X), float64(dr.Min.Y)
|
||||||
|
m := dc.matrix.Translate(fx, fy)
|
||||||
|
s2d := f64.Aff3{m.XX, m.XY, m.X0, m.YX, m.YY, m.Y0}
|
||||||
|
transformer.Transform(d.Dst, s2d, d.Src, sr, draw.Over, &draw.Options{
|
||||||
|
SrcMask: mask,
|
||||||
|
SrcMaskP: maskp,
|
||||||
|
})
|
||||||
|
d.Dot.X += advance
|
||||||
|
prevC = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrawString draws the specified text at the specified point.
|
||||||
|
func (dc *Context) DrawString(s string, x, y float64) {
|
||||||
|
dc.DrawStringAnchored(s, x, y, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrawStringAnchored draws the specified text at the specified anchor point.
|
||||||
|
// The anchor point is x - w * ax, y - h * ay, where w, h is the size of the
|
||||||
|
// text. Use ax=0.5, ay=0.5 to center the text at the specified point.
|
||||||
|
func (dc *Context) DrawStringAnchored(s string, x, y, ax, ay float64) {
|
||||||
|
w, h := dc.MeasureString(s)
|
||||||
|
x -= ax * w
|
||||||
|
y += ay * h
|
||||||
|
if dc.mask == nil {
|
||||||
|
dc.drawString(dc.im, s, x, y)
|
||||||
|
} else {
|
||||||
|
im := image.NewRGBA(image.Rect(0, 0, dc.width, dc.height))
|
||||||
|
dc.drawString(im, s, x, y)
|
||||||
|
draw.DrawMask(dc.im, dc.im.Bounds(), im, image.ZP, dc.mask, image.ZP, draw.Over)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrawStringWrapped word-wraps the specified string to the given max width
|
||||||
|
// and then draws it at the specified anchor point using the given line
|
||||||
|
// spacing and text alignment.
|
||||||
|
func (dc *Context) DrawStringWrapped(s string, x, y, ax, ay, width, lineSpacing float64, align Align) {
|
||||||
|
lines := dc.WordWrap(s, width)
|
||||||
|
h := float64(len(lines)) * dc.fontHeight * lineSpacing
|
||||||
|
h -= (lineSpacing - 1) * dc.fontHeight
|
||||||
|
x -= ax * width
|
||||||
|
y -= ay * h
|
||||||
|
switch align {
|
||||||
|
case AlignLeft:
|
||||||
|
ax = 0
|
||||||
|
case AlignCenter:
|
||||||
|
ax = 0.5
|
||||||
|
x += width / 2
|
||||||
|
case AlignRight:
|
||||||
|
ax = 1
|
||||||
|
x += width
|
||||||
|
}
|
||||||
|
ay = 1
|
||||||
|
for _, line := range lines {
|
||||||
|
dc.DrawStringAnchored(line, x, y, ax, ay)
|
||||||
|
y += dc.fontHeight * lineSpacing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MeasureString returns the rendered width and height of the specified text
|
||||||
|
// given the current font face.
|
||||||
|
func (dc *Context) MeasureString(s string) (w, h float64) {
|
||||||
|
d := &font.Drawer{
|
||||||
|
Face: dc.fontFace,
|
||||||
|
}
|
||||||
|
a := d.MeasureString(s)
|
||||||
|
return float64(a >> 6), dc.fontHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
// WordWrap wraps the specified string to the given max width and current
|
||||||
|
// font face.
|
||||||
|
func (dc *Context) WordWrap(s string, w float64) []string {
|
||||||
|
return wordWrap(dc, s, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transformation Matrix Operations
|
||||||
|
|
||||||
|
// Identity resets the current transformation matrix to the identity matrix.
|
||||||
|
// This results in no translating, scaling, rotating, or shearing.
|
||||||
|
func (dc *Context) Identity() {
|
||||||
|
dc.matrix = Identity()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate updates the current matrix with a translation.
|
||||||
|
func (dc *Context) Translate(x, y float64) {
|
||||||
|
dc.matrix = dc.matrix.Translate(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scale updates the current matrix with a scaling factor.
|
||||||
|
// Scaling occurs about the origin.
|
||||||
|
func (dc *Context) Scale(x, y float64) {
|
||||||
|
dc.matrix = dc.matrix.Scale(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScaleAbout updates the current matrix with a scaling factor.
|
||||||
|
// Scaling occurs about the specified point.
|
||||||
|
func (dc *Context) ScaleAbout(sx, sy, x, y float64) {
|
||||||
|
dc.Translate(x, y)
|
||||||
|
dc.Scale(sx, sy)
|
||||||
|
dc.Translate(-x, -y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotate updates the current matrix with a clockwise rotation.
|
||||||
|
// Rotation occurs about the origin. Angle is specified in radians.
|
||||||
|
func (dc *Context) Rotate(angle float64) {
|
||||||
|
dc.matrix = dc.matrix.Rotate(angle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RotateAbout updates the current matrix with a clockwise rotation.
|
||||||
|
// Rotation occurs about the specified point. Angle is specified in radians.
|
||||||
|
func (dc *Context) RotateAbout(angle, x, y float64) {
|
||||||
|
dc.Translate(x, y)
|
||||||
|
dc.Rotate(angle)
|
||||||
|
dc.Translate(-x, -y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shear updates the current matrix with a shearing angle.
|
||||||
|
// Shearing occurs about the origin.
|
||||||
|
func (dc *Context) Shear(x, y float64) {
|
||||||
|
dc.matrix = dc.matrix.Shear(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShearAbout updates the current matrix with a shearing angle.
|
||||||
|
// Shearing occurs about the specified point.
|
||||||
|
func (dc *Context) ShearAbout(sx, sy, x, y float64) {
|
||||||
|
dc.Translate(x, y)
|
||||||
|
dc.Shear(sx, sy)
|
||||||
|
dc.Translate(-x, -y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransformPoint multiplies the specified point by the current matrix,
|
||||||
|
// returning a transformed position.
|
||||||
|
func (dc *Context) TransformPoint(x, y float64) (tx, ty float64) {
|
||||||
|
return dc.matrix.TransformPoint(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvertY flips the Y axis so that Y grows from bottom to top and Y=0 is at
|
||||||
|
// the bottom of the image.
|
||||||
|
func (dc *Context) InvertY() {
|
||||||
|
dc.Translate(0, float64(dc.height))
|
||||||
|
dc.Scale(1, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stack
|
||||||
|
|
||||||
|
// Push saves the current state of the context for later retrieval. These
|
||||||
|
// can be nested.
|
||||||
|
func (dc *Context) Push() {
|
||||||
|
x := *dc
|
||||||
|
dc.stack = append(dc.stack, &x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop restores the last saved context state from the stack.
|
||||||
|
func (dc *Context) Pop() {
|
||||||
|
before := *dc
|
||||||
|
s := dc.stack
|
||||||
|
x, s := s[len(s)-1], s[:len(s)-1]
|
||||||
|
*dc = *x
|
||||||
|
dc.mask = before.mask
|
||||||
|
dc.strokePath = before.strokePath
|
||||||
|
dc.fillPath = before.fillPath
|
||||||
|
dc.start = before.start
|
||||||
|
dc.current = before.current
|
||||||
|
dc.hasCurrent = before.hasCurrent
|
||||||
|
}
|
202
vendor/github.com/fogleman/gg/gradient.go
generated
vendored
Normal file
202
vendor/github.com/fogleman/gg/gradient.go
generated
vendored
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
package gg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stop struct {
|
||||||
|
pos float64
|
||||||
|
color color.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
type stops []stop
|
||||||
|
|
||||||
|
// Len satisfies the Sort interface.
|
||||||
|
func (s stops) Len() int {
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less satisfies the Sort interface.
|
||||||
|
func (s stops) Less(i, j int) bool {
|
||||||
|
return s[i].pos < s[j].pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap satisfies the Sort interface.
|
||||||
|
func (s stops) Swap(i, j int) {
|
||||||
|
s[i], s[j] = s[j], s[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Gradient interface {
|
||||||
|
Pattern
|
||||||
|
AddColorStop(offset float64, color color.Color)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Linear Gradient
|
||||||
|
type linearGradient struct {
|
||||||
|
x0, y0, x1, y1 float64
|
||||||
|
stops stops
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *linearGradient) ColorAt(x, y int) color.Color {
|
||||||
|
if len(g.stops) == 0 {
|
||||||
|
return color.Transparent
|
||||||
|
}
|
||||||
|
|
||||||
|
fx, fy := float64(x), float64(y)
|
||||||
|
x0, y0, x1, y1 := g.x0, g.y0, g.x1, g.y1
|
||||||
|
dx, dy := x1-x0, y1-y0
|
||||||
|
|
||||||
|
// Horizontal
|
||||||
|
if dy == 0 && dx != 0 {
|
||||||
|
return getColor((fx-x0)/dx, g.stops)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertical
|
||||||
|
if dx == 0 && dy != 0 {
|
||||||
|
return getColor((fy-y0)/dy, g.stops)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dot product
|
||||||
|
s0 := dx*(fx-x0) + dy*(fy-y0)
|
||||||
|
if s0 < 0 {
|
||||||
|
return g.stops[0].color
|
||||||
|
}
|
||||||
|
// Calculate distance to (x0,y0) alone (x0,y0)->(x1,y1)
|
||||||
|
mag := math.Hypot(dx, dy)
|
||||||
|
u := ((fx-x0)*-dy + (fy-y0)*dx) / (mag * mag)
|
||||||
|
x2, y2 := x0+u*-dy, y0+u*dx
|
||||||
|
d := math.Hypot(fx-x2, fy-y2) / mag
|
||||||
|
return getColor(d, g.stops)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *linearGradient) AddColorStop(offset float64, color color.Color) {
|
||||||
|
g.stops = append(g.stops, stop{pos: offset, color: color})
|
||||||
|
sort.Sort(g.stops)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLinearGradient(x0, y0, x1, y1 float64) Gradient {
|
||||||
|
g := &linearGradient{
|
||||||
|
x0: x0, y0: y0,
|
||||||
|
x1: x1, y1: y1,
|
||||||
|
}
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
// Radial Gradient
|
||||||
|
type circle struct {
|
||||||
|
x, y, r float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type radialGradient struct {
|
||||||
|
c0, c1, cd circle
|
||||||
|
a, inva float64
|
||||||
|
mindr float64
|
||||||
|
stops stops
|
||||||
|
}
|
||||||
|
|
||||||
|
func dot3(x0, y0, z0, x1, y1, z1 float64) float64 {
|
||||||
|
return x0*x1 + y0*y1 + z0*z1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *radialGradient) ColorAt(x, y int) color.Color {
|
||||||
|
if len(g.stops) == 0 {
|
||||||
|
return color.Transparent
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy from pixman's pixman-radial-gradient.c
|
||||||
|
|
||||||
|
dx, dy := float64(x)+0.5-g.c0.x, float64(y)+0.5-g.c0.y
|
||||||
|
b := dot3(dx, dy, g.c0.r, g.cd.x, g.cd.y, g.cd.r)
|
||||||
|
c := dot3(dx, dy, -g.c0.r, dx, dy, g.c0.r)
|
||||||
|
|
||||||
|
if g.a == 0 {
|
||||||
|
if b == 0 {
|
||||||
|
return color.Transparent
|
||||||
|
}
|
||||||
|
t := 0.5 * c / b
|
||||||
|
if t*g.cd.r >= g.mindr {
|
||||||
|
return getColor(t, g.stops)
|
||||||
|
}
|
||||||
|
return color.Transparent
|
||||||
|
}
|
||||||
|
|
||||||
|
discr := dot3(b, g.a, 0, b, -c, 0)
|
||||||
|
if discr >= 0 {
|
||||||
|
sqrtdiscr := math.Sqrt(discr)
|
||||||
|
t0 := (b + sqrtdiscr) * g.inva
|
||||||
|
t1 := (b - sqrtdiscr) * g.inva
|
||||||
|
|
||||||
|
if t0*g.cd.r >= g.mindr {
|
||||||
|
return getColor(t0, g.stops)
|
||||||
|
} else if t1*g.cd.r >= g.mindr {
|
||||||
|
return getColor(t1, g.stops)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return color.Transparent
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *radialGradient) AddColorStop(offset float64, color color.Color) {
|
||||||
|
g.stops = append(g.stops, stop{pos: offset, color: color})
|
||||||
|
sort.Sort(g.stops)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRadialGradient(x0, y0, r0, x1, y1, r1 float64) Gradient {
|
||||||
|
c0 := circle{x0, y0, r0}
|
||||||
|
c1 := circle{x1, y1, r1}
|
||||||
|
cd := circle{x1 - x0, y1 - y0, r1 - r0}
|
||||||
|
a := dot3(cd.x, cd.y, -cd.r, cd.x, cd.y, cd.r)
|
||||||
|
var inva float64
|
||||||
|
if a != 0 {
|
||||||
|
inva = 1.0 / a
|
||||||
|
}
|
||||||
|
mindr := -c0.r
|
||||||
|
g := &radialGradient{
|
||||||
|
c0: c0,
|
||||||
|
c1: c1,
|
||||||
|
cd: cd,
|
||||||
|
a: a,
|
||||||
|
inva: inva,
|
||||||
|
mindr: mindr,
|
||||||
|
}
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func getColor(pos float64, stops stops) color.Color {
|
||||||
|
if pos <= 0.0 || len(stops) == 1 {
|
||||||
|
return stops[0].color
|
||||||
|
}
|
||||||
|
|
||||||
|
last := stops[len(stops)-1]
|
||||||
|
|
||||||
|
if pos >= last.pos {
|
||||||
|
return last.color
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, stop := range stops[1:] {
|
||||||
|
if pos < stop.pos {
|
||||||
|
pos = (pos - stops[i].pos) / (stop.pos - stops[i].pos)
|
||||||
|
return colorLerp(stops[i].color, stop.color, pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return last.color
|
||||||
|
}
|
||||||
|
|
||||||
|
func colorLerp(c0, c1 color.Color, t float64) color.Color {
|
||||||
|
r0, g0, b0, a0 := c0.RGBA()
|
||||||
|
r1, g1, b1, a1 := c1.RGBA()
|
||||||
|
|
||||||
|
return color.NRGBA{
|
||||||
|
lerp(r0, r1, t),
|
||||||
|
lerp(g0, g1, t),
|
||||||
|
lerp(b0, b1, t),
|
||||||
|
lerp(a0, a1, t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lerp(a, b uint32, t float64) uint8 {
|
||||||
|
return uint8(int32(float64(a)*(1.0-t)+float64(b)*t) >> 8)
|
||||||
|
}
|
88
vendor/github.com/fogleman/gg/matrix.go
generated
vendored
Normal file
88
vendor/github.com/fogleman/gg/matrix.go
generated
vendored
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
package gg
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
type Matrix struct {
|
||||||
|
XX, YX, XY, YY, X0, Y0 float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func Identity() Matrix {
|
||||||
|
return Matrix{
|
||||||
|
1, 0,
|
||||||
|
0, 1,
|
||||||
|
0, 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Translate(x, y float64) Matrix {
|
||||||
|
return Matrix{
|
||||||
|
1, 0,
|
||||||
|
0, 1,
|
||||||
|
x, y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Scale(x, y float64) Matrix {
|
||||||
|
return Matrix{
|
||||||
|
x, 0,
|
||||||
|
0, y,
|
||||||
|
0, 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Rotate(angle float64) Matrix {
|
||||||
|
c := math.Cos(angle)
|
||||||
|
s := math.Sin(angle)
|
||||||
|
return Matrix{
|
||||||
|
c, s,
|
||||||
|
-s, c,
|
||||||
|
0, 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Shear(x, y float64) Matrix {
|
||||||
|
return Matrix{
|
||||||
|
1, y,
|
||||||
|
x, 1,
|
||||||
|
0, 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Matrix) Multiply(b Matrix) Matrix {
|
||||||
|
return Matrix{
|
||||||
|
a.XX*b.XX + a.YX*b.XY,
|
||||||
|
a.XX*b.YX + a.YX*b.YY,
|
||||||
|
a.XY*b.XX + a.YY*b.XY,
|
||||||
|
a.XY*b.YX + a.YY*b.YY,
|
||||||
|
a.X0*b.XX + a.Y0*b.XY + b.X0,
|
||||||
|
a.X0*b.YX + a.Y0*b.YY + b.Y0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Matrix) TransformVector(x, y float64) (tx, ty float64) {
|
||||||
|
tx = a.XX*x + a.XY*y
|
||||||
|
ty = a.YX*x + a.YY*y
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Matrix) TransformPoint(x, y float64) (tx, ty float64) {
|
||||||
|
tx = a.XX*x + a.XY*y + a.X0
|
||||||
|
ty = a.YX*x + a.YY*y + a.Y0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Matrix) Translate(x, y float64) Matrix {
|
||||||
|
return Translate(x, y).Multiply(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Matrix) Scale(x, y float64) Matrix {
|
||||||
|
return Scale(x, y).Multiply(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Matrix) Rotate(angle float64) Matrix {
|
||||||
|
return Rotate(angle).Multiply(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Matrix) Shear(x, y float64) Matrix {
|
||||||
|
return Shear(x, y).Multiply(a)
|
||||||
|
}
|
140
vendor/github.com/fogleman/gg/path.go
generated
vendored
Normal file
140
vendor/github.com/fogleman/gg/path.go
generated
vendored
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
package gg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/golang/freetype/raster"
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
|
)
|
||||||
|
|
||||||
|
func flattenPath(p raster.Path) [][]Point {
|
||||||
|
var result [][]Point
|
||||||
|
var path []Point
|
||||||
|
var cx, cy float64
|
||||||
|
for i := 0; i < len(p); {
|
||||||
|
switch p[i] {
|
||||||
|
case 0:
|
||||||
|
if len(path) > 0 {
|
||||||
|
result = append(result, path)
|
||||||
|
path = nil
|
||||||
|
}
|
||||||
|
x := unfix(p[i+1])
|
||||||
|
y := unfix(p[i+2])
|
||||||
|
path = append(path, Point{x, y})
|
||||||
|
cx, cy = x, y
|
||||||
|
i += 4
|
||||||
|
case 1:
|
||||||
|
x := unfix(p[i+1])
|
||||||
|
y := unfix(p[i+2])
|
||||||
|
path = append(path, Point{x, y})
|
||||||
|
cx, cy = x, y
|
||||||
|
i += 4
|
||||||
|
case 2:
|
||||||
|
x1 := unfix(p[i+1])
|
||||||
|
y1 := unfix(p[i+2])
|
||||||
|
x2 := unfix(p[i+3])
|
||||||
|
y2 := unfix(p[i+4])
|
||||||
|
points := QuadraticBezier(cx, cy, x1, y1, x2, y2)
|
||||||
|
path = append(path, points...)
|
||||||
|
cx, cy = x2, y2
|
||||||
|
i += 6
|
||||||
|
case 3:
|
||||||
|
x1 := unfix(p[i+1])
|
||||||
|
y1 := unfix(p[i+2])
|
||||||
|
x2 := unfix(p[i+3])
|
||||||
|
y2 := unfix(p[i+4])
|
||||||
|
x3 := unfix(p[i+5])
|
||||||
|
y3 := unfix(p[i+6])
|
||||||
|
points := CubicBezier(cx, cy, x1, y1, x2, y2, x3, y3)
|
||||||
|
path = append(path, points...)
|
||||||
|
cx, cy = x3, y3
|
||||||
|
i += 8
|
||||||
|
default:
|
||||||
|
panic("bad path")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(path) > 0 {
|
||||||
|
result = append(result, path)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func dashPath(paths [][]Point, dashes []float64) [][]Point {
|
||||||
|
var result [][]Point
|
||||||
|
if len(dashes) == 0 {
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
if len(dashes) == 1 {
|
||||||
|
dashes = append(dashes, dashes[0])
|
||||||
|
}
|
||||||
|
for _, path := range paths {
|
||||||
|
if len(path) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
previous := path[0]
|
||||||
|
pathIndex := 1
|
||||||
|
dashIndex := 0
|
||||||
|
segmentLength := 0.0
|
||||||
|
var segment []Point
|
||||||
|
segment = append(segment, previous)
|
||||||
|
for pathIndex < len(path) {
|
||||||
|
dashLength := dashes[dashIndex]
|
||||||
|
point := path[pathIndex]
|
||||||
|
d := previous.Distance(point)
|
||||||
|
maxd := dashLength - segmentLength
|
||||||
|
if d > maxd {
|
||||||
|
t := maxd / d
|
||||||
|
p := previous.Interpolate(point, t)
|
||||||
|
segment = append(segment, p)
|
||||||
|
if dashIndex%2 == 0 && len(segment) > 1 {
|
||||||
|
result = append(result, segment)
|
||||||
|
}
|
||||||
|
segment = nil
|
||||||
|
segment = append(segment, p)
|
||||||
|
segmentLength = 0
|
||||||
|
previous = p
|
||||||
|
dashIndex = (dashIndex + 1) % len(dashes)
|
||||||
|
} else {
|
||||||
|
segment = append(segment, point)
|
||||||
|
previous = point
|
||||||
|
segmentLength += d
|
||||||
|
pathIndex++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dashIndex%2 == 0 && len(segment) > 1 {
|
||||||
|
result = append(result, segment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func rasterPath(paths [][]Point) raster.Path {
|
||||||
|
var result raster.Path
|
||||||
|
for _, path := range paths {
|
||||||
|
var previous fixed.Point26_6
|
||||||
|
for i, point := range path {
|
||||||
|
f := point.Fixed()
|
||||||
|
if i == 0 {
|
||||||
|
result.Start(f)
|
||||||
|
} else {
|
||||||
|
dx := f.X - previous.X
|
||||||
|
dy := f.Y - previous.Y
|
||||||
|
if dx < 0 {
|
||||||
|
dx = -dx
|
||||||
|
}
|
||||||
|
if dy < 0 {
|
||||||
|
dy = -dy
|
||||||
|
}
|
||||||
|
if dx+dy > 8 {
|
||||||
|
// TODO: this is a hack for cases where two points are
|
||||||
|
// too close - causes rendering issues with joins / caps
|
||||||
|
result.Add1(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
previous = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func dashed(path raster.Path, dashes []float64) raster.Path {
|
||||||
|
return rasterPath(dashPath(flattenPath(path), dashes))
|
||||||
|
}
|
123
vendor/github.com/fogleman/gg/pattern.go
generated
vendored
Normal file
123
vendor/github.com/fogleman/gg/pattern.go
generated
vendored
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
package gg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"github.com/golang/freetype/raster"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RepeatOp int
|
||||||
|
|
||||||
|
const (
|
||||||
|
RepeatBoth RepeatOp = iota
|
||||||
|
RepeatX
|
||||||
|
RepeatY
|
||||||
|
RepeatNone
|
||||||
|
)
|
||||||
|
|
||||||
|
type Pattern interface {
|
||||||
|
ColorAt(x, y int) color.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
// Solid Pattern
|
||||||
|
type solidPattern struct {
|
||||||
|
color color.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *solidPattern) ColorAt(x, y int) color.Color {
|
||||||
|
return p.color
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSolidPattern(color color.Color) Pattern {
|
||||||
|
return &solidPattern{color: color}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Surface Pattern
|
||||||
|
type surfacePattern struct {
|
||||||
|
im image.Image
|
||||||
|
op RepeatOp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *surfacePattern) ColorAt(x, y int) color.Color {
|
||||||
|
b := p.im.Bounds()
|
||||||
|
switch p.op {
|
||||||
|
case RepeatX:
|
||||||
|
if y >= b.Dy() {
|
||||||
|
return color.Transparent
|
||||||
|
}
|
||||||
|
case RepeatY:
|
||||||
|
if x >= b.Dx() {
|
||||||
|
return color.Transparent
|
||||||
|
}
|
||||||
|
case RepeatNone:
|
||||||
|
if x >= b.Dx() || y >= b.Dy() {
|
||||||
|
return color.Transparent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x = x%b.Dx() + b.Min.X
|
||||||
|
y = y%b.Dy() + b.Min.Y
|
||||||
|
return p.im.At(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSurfacePattern(im image.Image, op RepeatOp) Pattern {
|
||||||
|
return &surfacePattern{im: im, op: op}
|
||||||
|
}
|
||||||
|
|
||||||
|
type patternPainter struct {
|
||||||
|
im *image.RGBA
|
||||||
|
mask *image.Alpha
|
||||||
|
p Pattern
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paint satisfies the Painter interface.
|
||||||
|
func (r *patternPainter) Paint(ss []raster.Span, done bool) {
|
||||||
|
b := r.im.Bounds()
|
||||||
|
for _, s := range ss {
|
||||||
|
if s.Y < b.Min.Y {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if s.Y >= b.Max.Y {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.X0 < b.Min.X {
|
||||||
|
s.X0 = b.Min.X
|
||||||
|
}
|
||||||
|
if s.X1 > b.Max.X {
|
||||||
|
s.X1 = b.Max.X
|
||||||
|
}
|
||||||
|
if s.X0 >= s.X1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const m = 1<<16 - 1
|
||||||
|
y := s.Y - r.im.Rect.Min.Y
|
||||||
|
x0 := s.X0 - r.im.Rect.Min.X
|
||||||
|
// RGBAPainter.Paint() in $GOPATH/src/github.com/golang/freetype/raster/paint.go
|
||||||
|
i0 := (s.Y-r.im.Rect.Min.Y)*r.im.Stride + (s.X0-r.im.Rect.Min.X)*4
|
||||||
|
i1 := i0 + (s.X1-s.X0)*4
|
||||||
|
for i, x := i0, x0; i < i1; i, x = i+4, x+1 {
|
||||||
|
ma := s.Alpha
|
||||||
|
if r.mask != nil {
|
||||||
|
ma = ma * uint32(r.mask.AlphaAt(x, y).A) / 255
|
||||||
|
if ma == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c := r.p.ColorAt(x, y)
|
||||||
|
cr, cg, cb, ca := c.RGBA()
|
||||||
|
dr := uint32(r.im.Pix[i+0])
|
||||||
|
dg := uint32(r.im.Pix[i+1])
|
||||||
|
db := uint32(r.im.Pix[i+2])
|
||||||
|
da := uint32(r.im.Pix[i+3])
|
||||||
|
a := (m - (ca * ma / m)) * 0x101
|
||||||
|
r.im.Pix[i+0] = uint8((dr*a + cr*ma) / m >> 8)
|
||||||
|
r.im.Pix[i+1] = uint8((dg*a + cg*ma) / m >> 8)
|
||||||
|
r.im.Pix[i+2] = uint8((db*a + cb*ma) / m >> 8)
|
||||||
|
r.im.Pix[i+3] = uint8((da*a + ca*ma) / m >> 8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPatternPainter(im *image.RGBA, mask *image.Alpha, p Pattern) *patternPainter {
|
||||||
|
return &patternPainter{im, mask, p}
|
||||||
|
}
|
25
vendor/github.com/fogleman/gg/point.go
generated
vendored
Normal file
25
vendor/github.com/fogleman/gg/point.go
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package gg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Point struct {
|
||||||
|
X, Y float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Point) Fixed() fixed.Point26_6 {
|
||||||
|
return fixp(a.X, a.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Point) Distance(b Point) float64 {
|
||||||
|
return math.Hypot(a.X-b.X, a.Y-b.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Point) Interpolate(b Point, t float64) Point {
|
||||||
|
x := a.X + (b.X-a.X)*t
|
||||||
|
y := a.Y + (b.Y-a.Y)*t
|
||||||
|
return Point{x, y}
|
||||||
|
}
|
117
vendor/github.com/fogleman/gg/util.go
generated
vendored
Normal file
117
vendor/github.com/fogleman/gg/util.go
generated
vendored
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
package gg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/draw"
|
||||||
|
_ "image/jpeg"
|
||||||
|
"image/png"
|
||||||
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golang/freetype/truetype"
|
||||||
|
|
||||||
|
"golang.org/x/image/font"
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Radians(degrees float64) float64 {
|
||||||
|
return degrees * math.Pi / 180
|
||||||
|
}
|
||||||
|
|
||||||
|
func Degrees(radians float64) float64 {
|
||||||
|
return radians * 180 / math.Pi
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadImage(path string) (image.Image, error) {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
im, _, err := image.Decode(file)
|
||||||
|
return im, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadPNG(path string) (image.Image, error) {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
return png.Decode(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SavePNG(path string, im image.Image) error {
|
||||||
|
file, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
return png.Encode(file, im)
|
||||||
|
}
|
||||||
|
|
||||||
|
func imageToRGBA(src image.Image) *image.RGBA {
|
||||||
|
dst := image.NewRGBA(src.Bounds())
|
||||||
|
draw.Draw(dst, dst.Rect, src, image.ZP, draw.Src)
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseHexColor(x string) (r, g, b, a int) {
|
||||||
|
x = strings.TrimPrefix(x, "#")
|
||||||
|
a = 255
|
||||||
|
if len(x) == 3 {
|
||||||
|
format := "%1x%1x%1x"
|
||||||
|
fmt.Sscanf(x, format, &r, &g, &b)
|
||||||
|
r |= r << 4
|
||||||
|
g |= g << 4
|
||||||
|
b |= b << 4
|
||||||
|
}
|
||||||
|
if len(x) == 6 {
|
||||||
|
format := "%02x%02x%02x"
|
||||||
|
fmt.Sscanf(x, format, &r, &g, &b)
|
||||||
|
}
|
||||||
|
if len(x) == 8 {
|
||||||
|
format := "%02x%02x%02x%02x"
|
||||||
|
fmt.Sscanf(x, format, &r, &g, &b, &a)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixp(x, y float64) fixed.Point26_6 {
|
||||||
|
return fixed.Point26_6{fix(x), fix(y)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fix(x float64) fixed.Int26_6 {
|
||||||
|
return fixed.Int26_6(x * 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unfix(x fixed.Int26_6) float64 {
|
||||||
|
const shift, mask = 6, 1<<6 - 1
|
||||||
|
if x >= 0 {
|
||||||
|
return float64(x>>shift) + float64(x&mask)/64
|
||||||
|
}
|
||||||
|
x = -x
|
||||||
|
if x >= 0 {
|
||||||
|
return -(float64(x>>shift) + float64(x&mask)/64)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadFontFace(path string, points float64) (font.Face, error) {
|
||||||
|
fontBytes, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f, err := truetype.Parse(fontBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
face := truetype.NewFace(f, &truetype.Options{
|
||||||
|
Size: points,
|
||||||
|
// Hinting: font.HintingFull,
|
||||||
|
})
|
||||||
|
return face, nil
|
||||||
|
}
|
58
vendor/github.com/fogleman/gg/wrap.go
generated
vendored
Normal file
58
vendor/github.com/fogleman/gg/wrap.go
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package gg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
type measureStringer interface {
|
||||||
|
MeasureString(s string) (w, h float64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitOnSpace(x string) []string {
|
||||||
|
var result []string
|
||||||
|
pi := 0
|
||||||
|
ps := false
|
||||||
|
for i, c := range x {
|
||||||
|
s := unicode.IsSpace(c)
|
||||||
|
if s != ps && i > 0 {
|
||||||
|
result = append(result, x[pi:i])
|
||||||
|
pi = i
|
||||||
|
}
|
||||||
|
ps = s
|
||||||
|
}
|
||||||
|
result = append(result, x[pi:])
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func wordWrap(m measureStringer, s string, width float64) []string {
|
||||||
|
var result []string
|
||||||
|
for _, line := range strings.Split(s, "\n") {
|
||||||
|
fields := splitOnSpace(line)
|
||||||
|
if len(fields)%2 == 1 {
|
||||||
|
fields = append(fields, "")
|
||||||
|
}
|
||||||
|
x := ""
|
||||||
|
for i := 0; i < len(fields); i += 2 {
|
||||||
|
w, _ := m.MeasureString(x + fields[i])
|
||||||
|
if w > width {
|
||||||
|
if x == "" {
|
||||||
|
result = append(result, fields[i])
|
||||||
|
x = ""
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
result = append(result, x)
|
||||||
|
x = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x += fields[i] + fields[i+1]
|
||||||
|
}
|
||||||
|
if x != "" {
|
||||||
|
result = append(result, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, line := range result {
|
||||||
|
result[i] = strings.TrimSpace(line)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
20
vendor/github.com/golang/freetype/AUTHORS
generated
vendored
Normal file
20
vendor/github.com/golang/freetype/AUTHORS
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# This is the official list of Freetype-Go authors for copyright purposes.
|
||||||
|
# This file is distinct from the CONTRIBUTORS files.
|
||||||
|
# See the latter for an explanation.
|
||||||
|
#
|
||||||
|
# Freetype-Go is derived from Freetype, which is written in C. The latter
|
||||||
|
# is copyright 1996-2010 David Turner, Robert Wilhelm, and Werner Lemberg.
|
||||||
|
|
||||||
|
# Names should be added to this file as
|
||||||
|
# Name or Organization <email address>
|
||||||
|
# The email address is not required for organizations.
|
||||||
|
|
||||||
|
# Please keep the list sorted.
|
||||||
|
|
||||||
|
Google Inc.
|
||||||
|
Jeff R. Allen <jra@nella.org>
|
||||||
|
Maksim Kochkin <maxxarts@gmail.com>
|
||||||
|
Michael Fogleman <fogleman@gmail.com>
|
||||||
|
Rémy Oudompheng <oudomphe@phare.normalesup.org>
|
||||||
|
Roger Peppe <rogpeppe@gmail.com>
|
||||||
|
Steven Edwards <steven@stephenwithav.com>
|
38
vendor/github.com/golang/freetype/CONTRIBUTORS
generated
vendored
Normal file
38
vendor/github.com/golang/freetype/CONTRIBUTORS
generated
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# This is the official list of people who can contribute
|
||||||
|
# (and typically have contributed) code to the Freetype-Go repository.
|
||||||
|
# The AUTHORS file lists the copyright holders; this file
|
||||||
|
# lists people. For example, Google employees are listed here
|
||||||
|
# but not in AUTHORS, because Google holds the copyright.
|
||||||
|
#
|
||||||
|
# The submission process automatically checks to make sure
|
||||||
|
# that people submitting code are listed in this file (by email address).
|
||||||
|
#
|
||||||
|
# Names should be added to this file only after verifying that
|
||||||
|
# the individual or the individual's organization has agreed to
|
||||||
|
# the appropriate Contributor License Agreement, found here:
|
||||||
|
#
|
||||||
|
# http://code.google.com/legal/individual-cla-v1.0.html
|
||||||
|
# http://code.google.com/legal/corporate-cla-v1.0.html
|
||||||
|
#
|
||||||
|
# The agreement for individuals can be filled out on the web.
|
||||||
|
#
|
||||||
|
# When adding J Random Contributor's name to this file,
|
||||||
|
# either J's name or J's organization's name should be
|
||||||
|
# added to the AUTHORS file, depending on whether the
|
||||||
|
# individual or corporate CLA was used.
|
||||||
|
|
||||||
|
# Names should be added to this file like so:
|
||||||
|
# Name <email address>
|
||||||
|
|
||||||
|
# Please keep the list sorted.
|
||||||
|
|
||||||
|
Andrew Gerrand <adg@golang.org>
|
||||||
|
Jeff R. Allen <jra@nella.org> <jeff.allen@gmail.com>
|
||||||
|
Maksim Kochkin <maxxarts@gmail.com>
|
||||||
|
Michael Fogleman <fogleman@gmail.com>
|
||||||
|
Nigel Tao <nigeltao@golang.org>
|
||||||
|
Rémy Oudompheng <oudomphe@phare.normalesup.org> <remyoudompheng@gmail.com>
|
||||||
|
Rob Pike <r@golang.org>
|
||||||
|
Roger Peppe <rogpeppe@gmail.com>
|
||||||
|
Russ Cox <rsc@golang.org>
|
||||||
|
Steven Edwards <steven@stephenwithav.com>
|
12
vendor/github.com/golang/freetype/LICENSE
generated
vendored
Normal file
12
vendor/github.com/golang/freetype/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
Use of the Freetype-Go software is subject to your choice of exactly one of
|
||||||
|
the following two licenses:
|
||||||
|
* The FreeType License, which is similar to the original BSD license with
|
||||||
|
an advertising clause, or
|
||||||
|
* The GNU General Public License (GPL), version 2 or later.
|
||||||
|
|
||||||
|
The text of these licenses are available in the licenses/ftl.txt and the
|
||||||
|
licenses/gpl.txt files respectively. They are also available at
|
||||||
|
http://freetype.sourceforge.net/license.html
|
||||||
|
|
||||||
|
The Luxi fonts in the testdata directory are licensed separately. See the
|
||||||
|
testdata/COPYING file for details.
|
245
vendor/github.com/golang/freetype/raster/geom.go
generated
vendored
Normal file
245
vendor/github.com/golang/freetype/raster/geom.go
generated
vendored
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
// Copyright 2010 The Freetype-Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by your choice of either the
|
||||||
|
// FreeType License or the GNU General Public License version 2 (or
|
||||||
|
// any later version), both of which can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package raster
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
|
)
|
||||||
|
|
||||||
|
// maxAbs returns the maximum of abs(a) and abs(b).
|
||||||
|
func maxAbs(a, b fixed.Int26_6) fixed.Int26_6 {
|
||||||
|
if a < 0 {
|
||||||
|
a = -a
|
||||||
|
}
|
||||||
|
if b < 0 {
|
||||||
|
b = -b
|
||||||
|
}
|
||||||
|
if a < b {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// pNeg returns the vector -p, or equivalently p rotated by 180 degrees.
|
||||||
|
func pNeg(p fixed.Point26_6) fixed.Point26_6 {
|
||||||
|
return fixed.Point26_6{-p.X, -p.Y}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pDot returns the dot product p·q.
|
||||||
|
func pDot(p fixed.Point26_6, q fixed.Point26_6) fixed.Int52_12 {
|
||||||
|
px, py := int64(p.X), int64(p.Y)
|
||||||
|
qx, qy := int64(q.X), int64(q.Y)
|
||||||
|
return fixed.Int52_12(px*qx + py*qy)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pLen returns the length of the vector p.
|
||||||
|
func pLen(p fixed.Point26_6) fixed.Int26_6 {
|
||||||
|
// TODO(nigeltao): use fixed point math.
|
||||||
|
x := float64(p.X)
|
||||||
|
y := float64(p.Y)
|
||||||
|
return fixed.Int26_6(math.Sqrt(x*x + y*y))
|
||||||
|
}
|
||||||
|
|
||||||
|
// pNorm returns the vector p normalized to the given length, or zero if p is
|
||||||
|
// degenerate.
|
||||||
|
func pNorm(p fixed.Point26_6, length fixed.Int26_6) fixed.Point26_6 {
|
||||||
|
d := pLen(p)
|
||||||
|
if d == 0 {
|
||||||
|
return fixed.Point26_6{}
|
||||||
|
}
|
||||||
|
s, t := int64(length), int64(d)
|
||||||
|
x := int64(p.X) * s / t
|
||||||
|
y := int64(p.Y) * s / t
|
||||||
|
return fixed.Point26_6{fixed.Int26_6(x), fixed.Int26_6(y)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pRot45CW returns the vector p rotated clockwise by 45 degrees.
|
||||||
|
//
|
||||||
|
// Note that the Y-axis grows downwards, so {1, 0}.Rot45CW is {1/√2, 1/√2}.
|
||||||
|
func pRot45CW(p fixed.Point26_6) fixed.Point26_6 {
|
||||||
|
// 181/256 is approximately 1/√2, or sin(π/4).
|
||||||
|
px, py := int64(p.X), int64(p.Y)
|
||||||
|
qx := (+px - py) * 181 / 256
|
||||||
|
qy := (+px + py) * 181 / 256
|
||||||
|
return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pRot90CW returns the vector p rotated clockwise by 90 degrees.
|
||||||
|
//
|
||||||
|
// Note that the Y-axis grows downwards, so {1, 0}.Rot90CW is {0, 1}.
|
||||||
|
func pRot90CW(p fixed.Point26_6) fixed.Point26_6 {
|
||||||
|
return fixed.Point26_6{-p.Y, p.X}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pRot135CW returns the vector p rotated clockwise by 135 degrees.
|
||||||
|
//
|
||||||
|
// Note that the Y-axis grows downwards, so {1, 0}.Rot135CW is {-1/√2, 1/√2}.
|
||||||
|
func pRot135CW(p fixed.Point26_6) fixed.Point26_6 {
|
||||||
|
// 181/256 is approximately 1/√2, or sin(π/4).
|
||||||
|
px, py := int64(p.X), int64(p.Y)
|
||||||
|
qx := (-px - py) * 181 / 256
|
||||||
|
qy := (+px - py) * 181 / 256
|
||||||
|
return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pRot45CCW returns the vector p rotated counter-clockwise by 45 degrees.
|
||||||
|
//
|
||||||
|
// Note that the Y-axis grows downwards, so {1, 0}.Rot45CCW is {1/√2, -1/√2}.
|
||||||
|
func pRot45CCW(p fixed.Point26_6) fixed.Point26_6 {
|
||||||
|
// 181/256 is approximately 1/√2, or sin(π/4).
|
||||||
|
px, py := int64(p.X), int64(p.Y)
|
||||||
|
qx := (+px + py) * 181 / 256
|
||||||
|
qy := (-px + py) * 181 / 256
|
||||||
|
return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pRot90CCW returns the vector p rotated counter-clockwise by 90 degrees.
|
||||||
|
//
|
||||||
|
// Note that the Y-axis grows downwards, so {1, 0}.Rot90CCW is {0, -1}.
|
||||||
|
func pRot90CCW(p fixed.Point26_6) fixed.Point26_6 {
|
||||||
|
return fixed.Point26_6{p.Y, -p.X}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pRot135CCW returns the vector p rotated counter-clockwise by 135 degrees.
|
||||||
|
//
|
||||||
|
// Note that the Y-axis grows downwards, so {1, 0}.Rot135CCW is {-1/√2, -1/√2}.
|
||||||
|
func pRot135CCW(p fixed.Point26_6) fixed.Point26_6 {
|
||||||
|
// 181/256 is approximately 1/√2, or sin(π/4).
|
||||||
|
px, py := int64(p.X), int64(p.Y)
|
||||||
|
qx := (-px + py) * 181 / 256
|
||||||
|
qy := (-px - py) * 181 / 256
|
||||||
|
return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An Adder accumulates points on a curve.
|
||||||
|
type Adder interface {
|
||||||
|
// Start starts a new curve at the given point.
|
||||||
|
Start(a fixed.Point26_6)
|
||||||
|
// Add1 adds a linear segment to the current curve.
|
||||||
|
Add1(b fixed.Point26_6)
|
||||||
|
// Add2 adds a quadratic segment to the current curve.
|
||||||
|
Add2(b, c fixed.Point26_6)
|
||||||
|
// Add3 adds a cubic segment to the current curve.
|
||||||
|
Add3(b, c, d fixed.Point26_6)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Path is a sequence of curves, and a curve is a start point followed by a
|
||||||
|
// sequence of linear, quadratic or cubic segments.
|
||||||
|
type Path []fixed.Int26_6
|
||||||
|
|
||||||
|
// String returns a human-readable representation of a Path.
|
||||||
|
func (p Path) String() string {
|
||||||
|
s := ""
|
||||||
|
for i := 0; i < len(p); {
|
||||||
|
if i != 0 {
|
||||||
|
s += " "
|
||||||
|
}
|
||||||
|
switch p[i] {
|
||||||
|
case 0:
|
||||||
|
s += "S0" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+3]))
|
||||||
|
i += 4
|
||||||
|
case 1:
|
||||||
|
s += "A1" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+3]))
|
||||||
|
i += 4
|
||||||
|
case 2:
|
||||||
|
s += "A2" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+5]))
|
||||||
|
i += 6
|
||||||
|
case 3:
|
||||||
|
s += "A3" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+7]))
|
||||||
|
i += 8
|
||||||
|
default:
|
||||||
|
panic("freetype/raster: bad path")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear cancels any previous calls to p.Start or p.AddXxx.
|
||||||
|
func (p *Path) Clear() {
|
||||||
|
*p = (*p)[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts a new curve at the given point.
|
||||||
|
func (p *Path) Start(a fixed.Point26_6) {
|
||||||
|
*p = append(*p, 0, a.X, a.Y, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add1 adds a linear segment to the current curve.
|
||||||
|
func (p *Path) Add1(b fixed.Point26_6) {
|
||||||
|
*p = append(*p, 1, b.X, b.Y, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add2 adds a quadratic segment to the current curve.
|
||||||
|
func (p *Path) Add2(b, c fixed.Point26_6) {
|
||||||
|
*p = append(*p, 2, b.X, b.Y, c.X, c.Y, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add3 adds a cubic segment to the current curve.
|
||||||
|
func (p *Path) Add3(b, c, d fixed.Point26_6) {
|
||||||
|
*p = append(*p, 3, b.X, b.Y, c.X, c.Y, d.X, d.Y, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPath adds the Path q to p.
|
||||||
|
func (p *Path) AddPath(q Path) {
|
||||||
|
*p = append(*p, q...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddStroke adds a stroked Path.
|
||||||
|
func (p *Path) AddStroke(q Path, width fixed.Int26_6, cr Capper, jr Joiner) {
|
||||||
|
Stroke(p, q, width, cr, jr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// firstPoint returns the first point in a non-empty Path.
|
||||||
|
func (p Path) firstPoint() fixed.Point26_6 {
|
||||||
|
return fixed.Point26_6{p[1], p[2]}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lastPoint returns the last point in a non-empty Path.
|
||||||
|
func (p Path) lastPoint() fixed.Point26_6 {
|
||||||
|
return fixed.Point26_6{p[len(p)-3], p[len(p)-2]}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addPathReversed adds q reversed to p.
|
||||||
|
// For example, if q consists of a linear segment from A to B followed by a
|
||||||
|
// quadratic segment from B to C to D, then the values of q looks like:
|
||||||
|
// index: 01234567890123
|
||||||
|
// value: 0AA01BB12CCDD2
|
||||||
|
// So, when adding q backwards to p, we want to Add2(C, B) followed by Add1(A).
|
||||||
|
func addPathReversed(p Adder, q Path) {
|
||||||
|
if len(q) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i := len(q) - 1
|
||||||
|
for {
|
||||||
|
switch q[i] {
|
||||||
|
case 0:
|
||||||
|
return
|
||||||
|
case 1:
|
||||||
|
i -= 4
|
||||||
|
p.Add1(
|
||||||
|
fixed.Point26_6{q[i-2], q[i-1]},
|
||||||
|
)
|
||||||
|
case 2:
|
||||||
|
i -= 6
|
||||||
|
p.Add2(
|
||||||
|
fixed.Point26_6{q[i+2], q[i+3]},
|
||||||
|
fixed.Point26_6{q[i-2], q[i-1]},
|
||||||
|
)
|
||||||
|
case 3:
|
||||||
|
i -= 8
|
||||||
|
p.Add3(
|
||||||
|
fixed.Point26_6{q[i+4], q[i+5]},
|
||||||
|
fixed.Point26_6{q[i+2], q[i+3]},
|
||||||
|
fixed.Point26_6{q[i-2], q[i-1]},
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
panic("freetype/raster: bad path")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
287
vendor/github.com/golang/freetype/raster/paint.go
generated
vendored
Normal file
287
vendor/github.com/golang/freetype/raster/paint.go
generated
vendored
Normal file
|
@ -0,0 +1,287 @@
|
||||||
|
// Copyright 2010 The Freetype-Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by your choice of either the
|
||||||
|
// FreeType License or the GNU General Public License version 2 (or
|
||||||
|
// any later version), both of which can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package raster
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/draw"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Span is a horizontal segment of pixels with constant alpha. X0 is an
|
||||||
|
// inclusive bound and X1 is exclusive, the same as for slices. A fully opaque
|
||||||
|
// Span has Alpha == 0xffff.
|
||||||
|
type Span struct {
|
||||||
|
Y, X0, X1 int
|
||||||
|
Alpha uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Painter knows how to paint a batch of Spans. Rasterization may involve
|
||||||
|
// Painting multiple batches, and done will be true for the final batch. The
|
||||||
|
// Spans' Y values are monotonically increasing during a rasterization. Paint
|
||||||
|
// may use all of ss as scratch space during the call.
|
||||||
|
type Painter interface {
|
||||||
|
Paint(ss []Span, done bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The PainterFunc type adapts an ordinary function to the Painter interface.
|
||||||
|
type PainterFunc func(ss []Span, done bool)
|
||||||
|
|
||||||
|
// Paint just delegates the call to f.
|
||||||
|
func (f PainterFunc) Paint(ss []Span, done bool) { f(ss, done) }
|
||||||
|
|
||||||
|
// An AlphaOverPainter is a Painter that paints Spans onto a *image.Alpha using
|
||||||
|
// the Over Porter-Duff composition operator.
|
||||||
|
type AlphaOverPainter struct {
|
||||||
|
Image *image.Alpha
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paint satisfies the Painter interface.
|
||||||
|
func (r AlphaOverPainter) Paint(ss []Span, done bool) {
|
||||||
|
b := r.Image.Bounds()
|
||||||
|
for _, s := range ss {
|
||||||
|
if s.Y < b.Min.Y {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if s.Y >= b.Max.Y {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.X0 < b.Min.X {
|
||||||
|
s.X0 = b.Min.X
|
||||||
|
}
|
||||||
|
if s.X1 > b.Max.X {
|
||||||
|
s.X1 = b.Max.X
|
||||||
|
}
|
||||||
|
if s.X0 >= s.X1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
base := (s.Y-r.Image.Rect.Min.Y)*r.Image.Stride - r.Image.Rect.Min.X
|
||||||
|
p := r.Image.Pix[base+s.X0 : base+s.X1]
|
||||||
|
a := int(s.Alpha >> 8)
|
||||||
|
for i, c := range p {
|
||||||
|
v := int(c)
|
||||||
|
p[i] = uint8((v*255 + (255-v)*a) / 255)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAlphaOverPainter creates a new AlphaOverPainter for the given image.
|
||||||
|
func NewAlphaOverPainter(m *image.Alpha) AlphaOverPainter {
|
||||||
|
return AlphaOverPainter{m}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An AlphaSrcPainter is a Painter that paints Spans onto a *image.Alpha using
|
||||||
|
// the Src Porter-Duff composition operator.
|
||||||
|
type AlphaSrcPainter struct {
|
||||||
|
Image *image.Alpha
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paint satisfies the Painter interface.
|
||||||
|
func (r AlphaSrcPainter) Paint(ss []Span, done bool) {
|
||||||
|
b := r.Image.Bounds()
|
||||||
|
for _, s := range ss {
|
||||||
|
if s.Y < b.Min.Y {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if s.Y >= b.Max.Y {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.X0 < b.Min.X {
|
||||||
|
s.X0 = b.Min.X
|
||||||
|
}
|
||||||
|
if s.X1 > b.Max.X {
|
||||||
|
s.X1 = b.Max.X
|
||||||
|
}
|
||||||
|
if s.X0 >= s.X1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
base := (s.Y-r.Image.Rect.Min.Y)*r.Image.Stride - r.Image.Rect.Min.X
|
||||||
|
p := r.Image.Pix[base+s.X0 : base+s.X1]
|
||||||
|
color := uint8(s.Alpha >> 8)
|
||||||
|
for i := range p {
|
||||||
|
p[i] = color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAlphaSrcPainter creates a new AlphaSrcPainter for the given image.
|
||||||
|
func NewAlphaSrcPainter(m *image.Alpha) AlphaSrcPainter {
|
||||||
|
return AlphaSrcPainter{m}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An RGBAPainter is a Painter that paints Spans onto a *image.RGBA.
|
||||||
|
type RGBAPainter struct {
|
||||||
|
// Image is the image to compose onto.
|
||||||
|
Image *image.RGBA
|
||||||
|
// Op is the Porter-Duff composition operator.
|
||||||
|
Op draw.Op
|
||||||
|
// cr, cg, cb and ca are the 16-bit color to paint the spans.
|
||||||
|
cr, cg, cb, ca uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paint satisfies the Painter interface.
|
||||||
|
func (r *RGBAPainter) Paint(ss []Span, done bool) {
|
||||||
|
b := r.Image.Bounds()
|
||||||
|
for _, s := range ss {
|
||||||
|
if s.Y < b.Min.Y {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if s.Y >= b.Max.Y {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.X0 < b.Min.X {
|
||||||
|
s.X0 = b.Min.X
|
||||||
|
}
|
||||||
|
if s.X1 > b.Max.X {
|
||||||
|
s.X1 = b.Max.X
|
||||||
|
}
|
||||||
|
if s.X0 >= s.X1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// This code mimics drawGlyphOver in $GOROOT/src/image/draw/draw.go.
|
||||||
|
ma := s.Alpha
|
||||||
|
const m = 1<<16 - 1
|
||||||
|
i0 := (s.Y-r.Image.Rect.Min.Y)*r.Image.Stride + (s.X0-r.Image.Rect.Min.X)*4
|
||||||
|
i1 := i0 + (s.X1-s.X0)*4
|
||||||
|
if r.Op == draw.Over {
|
||||||
|
for i := i0; i < i1; i += 4 {
|
||||||
|
dr := uint32(r.Image.Pix[i+0])
|
||||||
|
dg := uint32(r.Image.Pix[i+1])
|
||||||
|
db := uint32(r.Image.Pix[i+2])
|
||||||
|
da := uint32(r.Image.Pix[i+3])
|
||||||
|
a := (m - (r.ca * ma / m)) * 0x101
|
||||||
|
r.Image.Pix[i+0] = uint8((dr*a + r.cr*ma) / m >> 8)
|
||||||
|
r.Image.Pix[i+1] = uint8((dg*a + r.cg*ma) / m >> 8)
|
||||||
|
r.Image.Pix[i+2] = uint8((db*a + r.cb*ma) / m >> 8)
|
||||||
|
r.Image.Pix[i+3] = uint8((da*a + r.ca*ma) / m >> 8)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i := i0; i < i1; i += 4 {
|
||||||
|
r.Image.Pix[i+0] = uint8(r.cr * ma / m >> 8)
|
||||||
|
r.Image.Pix[i+1] = uint8(r.cg * ma / m >> 8)
|
||||||
|
r.Image.Pix[i+2] = uint8(r.cb * ma / m >> 8)
|
||||||
|
r.Image.Pix[i+3] = uint8(r.ca * ma / m >> 8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetColor sets the color to paint the spans.
|
||||||
|
func (r *RGBAPainter) SetColor(c color.Color) {
|
||||||
|
r.cr, r.cg, r.cb, r.ca = c.RGBA()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRGBAPainter creates a new RGBAPainter for the given image.
|
||||||
|
func NewRGBAPainter(m *image.RGBA) *RGBAPainter {
|
||||||
|
return &RGBAPainter{Image: m}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A MonochromePainter wraps another Painter, quantizing each Span's alpha to
|
||||||
|
// be either fully opaque or fully transparent.
|
||||||
|
type MonochromePainter struct {
|
||||||
|
Painter Painter
|
||||||
|
y, x0, x1 int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paint delegates to the wrapped Painter after quantizing each Span's alpha
|
||||||
|
// value and merging adjacent fully opaque Spans.
|
||||||
|
func (m *MonochromePainter) Paint(ss []Span, done bool) {
|
||||||
|
// We compact the ss slice, discarding any Spans whose alpha quantizes to zero.
|
||||||
|
j := 0
|
||||||
|
for _, s := range ss {
|
||||||
|
if s.Alpha >= 0x8000 {
|
||||||
|
if m.y == s.Y && m.x1 == s.X0 {
|
||||||
|
m.x1 = s.X1
|
||||||
|
} else {
|
||||||
|
ss[j] = Span{m.y, m.x0, m.x1, 1<<16 - 1}
|
||||||
|
j++
|
||||||
|
m.y, m.x0, m.x1 = s.Y, s.X0, s.X1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if done {
|
||||||
|
// Flush the accumulated Span.
|
||||||
|
finalSpan := Span{m.y, m.x0, m.x1, 1<<16 - 1}
|
||||||
|
if j < len(ss) {
|
||||||
|
ss[j] = finalSpan
|
||||||
|
j++
|
||||||
|
m.Painter.Paint(ss[:j], true)
|
||||||
|
} else if j == len(ss) {
|
||||||
|
m.Painter.Paint(ss, false)
|
||||||
|
if cap(ss) > 0 {
|
||||||
|
ss = ss[:1]
|
||||||
|
} else {
|
||||||
|
ss = make([]Span, 1)
|
||||||
|
}
|
||||||
|
ss[0] = finalSpan
|
||||||
|
m.Painter.Paint(ss, true)
|
||||||
|
} else {
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
// Reset the accumulator, so that this Painter can be re-used.
|
||||||
|
m.y, m.x0, m.x1 = 0, 0, 0
|
||||||
|
} else {
|
||||||
|
m.Painter.Paint(ss[:j], false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMonochromePainter creates a new MonochromePainter that wraps the given
|
||||||
|
// Painter.
|
||||||
|
func NewMonochromePainter(p Painter) *MonochromePainter {
|
||||||
|
return &MonochromePainter{Painter: p}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A GammaCorrectionPainter wraps another Painter, performing gamma-correction
|
||||||
|
// on each Span's alpha value.
|
||||||
|
type GammaCorrectionPainter struct {
|
||||||
|
// Painter is the wrapped Painter.
|
||||||
|
Painter Painter
|
||||||
|
// a is the precomputed alpha values for linear interpolation, with fully
|
||||||
|
// opaque == 0xffff.
|
||||||
|
a [256]uint16
|
||||||
|
// gammaIsOne is whether gamma correction is a no-op.
|
||||||
|
gammaIsOne bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paint delegates to the wrapped Painter after performing gamma-correction on
|
||||||
|
// each Span.
|
||||||
|
func (g *GammaCorrectionPainter) Paint(ss []Span, done bool) {
|
||||||
|
if !g.gammaIsOne {
|
||||||
|
const n = 0x101
|
||||||
|
for i, s := range ss {
|
||||||
|
if s.Alpha == 0 || s.Alpha == 0xffff {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p, q := s.Alpha/n, s.Alpha%n
|
||||||
|
// The resultant alpha is a linear interpolation of g.a[p] and g.a[p+1].
|
||||||
|
a := uint32(g.a[p])*(n-q) + uint32(g.a[p+1])*q
|
||||||
|
ss[i].Alpha = (a + n/2) / n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.Painter.Paint(ss, done)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetGamma sets the gamma value.
|
||||||
|
func (g *GammaCorrectionPainter) SetGamma(gamma float64) {
|
||||||
|
g.gammaIsOne = gamma == 1
|
||||||
|
if g.gammaIsOne {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := 0; i < 256; i++ {
|
||||||
|
a := float64(i) / 0xff
|
||||||
|
a = math.Pow(a, gamma)
|
||||||
|
g.a[i] = uint16(0xffff * a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGammaCorrectionPainter creates a new GammaCorrectionPainter that wraps
|
||||||
|
// the given Painter.
|
||||||
|
func NewGammaCorrectionPainter(p Painter, gamma float64) *GammaCorrectionPainter {
|
||||||
|
g := &GammaCorrectionPainter{Painter: p}
|
||||||
|
g.SetGamma(gamma)
|
||||||
|
return g
|
||||||
|
}
|
601
vendor/github.com/golang/freetype/raster/raster.go
generated
vendored
Normal file
601
vendor/github.com/golang/freetype/raster/raster.go
generated
vendored
Normal file
|
@ -0,0 +1,601 @@
|
||||||
|
// Copyright 2010 The Freetype-Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by your choice of either the
|
||||||
|
// FreeType License or the GNU General Public License version 2 (or
|
||||||
|
// any later version), both of which can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package raster provides an anti-aliasing 2-D rasterizer.
|
||||||
|
//
|
||||||
|
// It is part of the larger Freetype suite of font-related packages, but the
|
||||||
|
// raster package is not specific to font rasterization, and can be used
|
||||||
|
// standalone without any other Freetype package.
|
||||||
|
//
|
||||||
|
// Rasterization is done by the same area/coverage accumulation algorithm as
|
||||||
|
// the Freetype "smooth" module, and the Anti-Grain Geometry library. A
|
||||||
|
// description of the area/coverage algorithm is at
|
||||||
|
// http://projects.tuxee.net/cl-vectors/section-the-cl-aa-algorithm
|
||||||
|
package raster
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A cell is part of a linked list (for a given yi co-ordinate) of accumulated
|
||||||
|
// area/coverage for the pixel at (xi, yi).
|
||||||
|
type cell struct {
|
||||||
|
xi int
|
||||||
|
area, cover int
|
||||||
|
next int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rasterizer struct {
|
||||||
|
// If false, the default behavior is to use the even-odd winding fill
|
||||||
|
// rule during Rasterize.
|
||||||
|
UseNonZeroWinding bool
|
||||||
|
// An offset (in pixels) to the painted spans.
|
||||||
|
Dx, Dy int
|
||||||
|
|
||||||
|
// The width of the Rasterizer. The height is implicit in len(cellIndex).
|
||||||
|
width int
|
||||||
|
// splitScaleN is the scaling factor used to determine how many times
|
||||||
|
// to decompose a quadratic or cubic segment into a linear approximation.
|
||||||
|
splitScale2, splitScale3 int
|
||||||
|
|
||||||
|
// The current pen position.
|
||||||
|
a fixed.Point26_6
|
||||||
|
// The current cell and its area/coverage being accumulated.
|
||||||
|
xi, yi int
|
||||||
|
area, cover int
|
||||||
|
|
||||||
|
// Saved cells.
|
||||||
|
cell []cell
|
||||||
|
// Linked list of cells, one per row.
|
||||||
|
cellIndex []int
|
||||||
|
// Buffers.
|
||||||
|
cellBuf [256]cell
|
||||||
|
cellIndexBuf [64]int
|
||||||
|
spanBuf [64]Span
|
||||||
|
}
|
||||||
|
|
||||||
|
// findCell returns the index in r.cell for the cell corresponding to
|
||||||
|
// (r.xi, r.yi). The cell is created if necessary.
|
||||||
|
func (r *Rasterizer) findCell() int {
|
||||||
|
if r.yi < 0 || r.yi >= len(r.cellIndex) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
xi := r.xi
|
||||||
|
if xi < 0 {
|
||||||
|
xi = -1
|
||||||
|
} else if xi > r.width {
|
||||||
|
xi = r.width
|
||||||
|
}
|
||||||
|
i, prev := r.cellIndex[r.yi], -1
|
||||||
|
for i != -1 && r.cell[i].xi <= xi {
|
||||||
|
if r.cell[i].xi == xi {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
i, prev = r.cell[i].next, i
|
||||||
|
}
|
||||||
|
c := len(r.cell)
|
||||||
|
if c == cap(r.cell) {
|
||||||
|
buf := make([]cell, c, 4*c)
|
||||||
|
copy(buf, r.cell)
|
||||||
|
r.cell = buf[0 : c+1]
|
||||||
|
} else {
|
||||||
|
r.cell = r.cell[0 : c+1]
|
||||||
|
}
|
||||||
|
r.cell[c] = cell{xi, 0, 0, i}
|
||||||
|
if prev == -1 {
|
||||||
|
r.cellIndex[r.yi] = c
|
||||||
|
} else {
|
||||||
|
r.cell[prev].next = c
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// saveCell saves any accumulated r.area/r.cover for (r.xi, r.yi).
|
||||||
|
func (r *Rasterizer) saveCell() {
|
||||||
|
if r.area != 0 || r.cover != 0 {
|
||||||
|
i := r.findCell()
|
||||||
|
if i != -1 {
|
||||||
|
r.cell[i].area += r.area
|
||||||
|
r.cell[i].cover += r.cover
|
||||||
|
}
|
||||||
|
r.area = 0
|
||||||
|
r.cover = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setCell sets the (xi, yi) cell that r is accumulating area/coverage for.
|
||||||
|
func (r *Rasterizer) setCell(xi, yi int) {
|
||||||
|
if r.xi != xi || r.yi != yi {
|
||||||
|
r.saveCell()
|
||||||
|
r.xi, r.yi = xi, yi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// scan accumulates area/coverage for the yi'th scanline, going from
|
||||||
|
// x0 to x1 in the horizontal direction (in 26.6 fixed point co-ordinates)
|
||||||
|
// and from y0f to y1f fractional vertical units within that scanline.
|
||||||
|
func (r *Rasterizer) scan(yi int, x0, y0f, x1, y1f fixed.Int26_6) {
|
||||||
|
// Break the 26.6 fixed point X co-ordinates into integral and fractional parts.
|
||||||
|
x0i := int(x0) / 64
|
||||||
|
x0f := x0 - fixed.Int26_6(64*x0i)
|
||||||
|
x1i := int(x1) / 64
|
||||||
|
x1f := x1 - fixed.Int26_6(64*x1i)
|
||||||
|
|
||||||
|
// A perfectly horizontal scan.
|
||||||
|
if y0f == y1f {
|
||||||
|
r.setCell(x1i, yi)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dx, dy := x1-x0, y1f-y0f
|
||||||
|
// A single cell scan.
|
||||||
|
if x0i == x1i {
|
||||||
|
r.area += int((x0f + x1f) * dy)
|
||||||
|
r.cover += int(dy)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// There are at least two cells. Apart from the first and last cells,
|
||||||
|
// all intermediate cells go through the full width of the cell,
|
||||||
|
// or 64 units in 26.6 fixed point format.
|
||||||
|
var (
|
||||||
|
p, q, edge0, edge1 fixed.Int26_6
|
||||||
|
xiDelta int
|
||||||
|
)
|
||||||
|
if dx > 0 {
|
||||||
|
p, q = (64-x0f)*dy, dx
|
||||||
|
edge0, edge1, xiDelta = 0, 64, 1
|
||||||
|
} else {
|
||||||
|
p, q = x0f*dy, -dx
|
||||||
|
edge0, edge1, xiDelta = 64, 0, -1
|
||||||
|
}
|
||||||
|
yDelta, yRem := p/q, p%q
|
||||||
|
if yRem < 0 {
|
||||||
|
yDelta -= 1
|
||||||
|
yRem += q
|
||||||
|
}
|
||||||
|
// Do the first cell.
|
||||||
|
xi, y := x0i, y0f
|
||||||
|
r.area += int((x0f + edge1) * yDelta)
|
||||||
|
r.cover += int(yDelta)
|
||||||
|
xi, y = xi+xiDelta, y+yDelta
|
||||||
|
r.setCell(xi, yi)
|
||||||
|
if xi != x1i {
|
||||||
|
// Do all the intermediate cells.
|
||||||
|
p = 64 * (y1f - y + yDelta)
|
||||||
|
fullDelta, fullRem := p/q, p%q
|
||||||
|
if fullRem < 0 {
|
||||||
|
fullDelta -= 1
|
||||||
|
fullRem += q
|
||||||
|
}
|
||||||
|
yRem -= q
|
||||||
|
for xi != x1i {
|
||||||
|
yDelta = fullDelta
|
||||||
|
yRem += fullRem
|
||||||
|
if yRem >= 0 {
|
||||||
|
yDelta += 1
|
||||||
|
yRem -= q
|
||||||
|
}
|
||||||
|
r.area += int(64 * yDelta)
|
||||||
|
r.cover += int(yDelta)
|
||||||
|
xi, y = xi+xiDelta, y+yDelta
|
||||||
|
r.setCell(xi, yi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Do the last cell.
|
||||||
|
yDelta = y1f - y
|
||||||
|
r.area += int((edge0 + x1f) * yDelta)
|
||||||
|
r.cover += int(yDelta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts a new curve at the given point.
|
||||||
|
func (r *Rasterizer) Start(a fixed.Point26_6) {
|
||||||
|
r.setCell(int(a.X/64), int(a.Y/64))
|
||||||
|
r.a = a
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add1 adds a linear segment to the current curve.
|
||||||
|
func (r *Rasterizer) Add1(b fixed.Point26_6) {
|
||||||
|
x0, y0 := r.a.X, r.a.Y
|
||||||
|
x1, y1 := b.X, b.Y
|
||||||
|
dx, dy := x1-x0, y1-y0
|
||||||
|
// Break the 26.6 fixed point Y co-ordinates into integral and fractional
|
||||||
|
// parts.
|
||||||
|
y0i := int(y0) / 64
|
||||||
|
y0f := y0 - fixed.Int26_6(64*y0i)
|
||||||
|
y1i := int(y1) / 64
|
||||||
|
y1f := y1 - fixed.Int26_6(64*y1i)
|
||||||
|
|
||||||
|
if y0i == y1i {
|
||||||
|
// There is only one scanline.
|
||||||
|
r.scan(y0i, x0, y0f, x1, y1f)
|
||||||
|
|
||||||
|
} else if dx == 0 {
|
||||||
|
// This is a vertical line segment. We avoid calling r.scan and instead
|
||||||
|
// manipulate r.area and r.cover directly.
|
||||||
|
var (
|
||||||
|
edge0, edge1 fixed.Int26_6
|
||||||
|
yiDelta int
|
||||||
|
)
|
||||||
|
if dy > 0 {
|
||||||
|
edge0, edge1, yiDelta = 0, 64, 1
|
||||||
|
} else {
|
||||||
|
edge0, edge1, yiDelta = 64, 0, -1
|
||||||
|
}
|
||||||
|
x0i, yi := int(x0)/64, y0i
|
||||||
|
x0fTimes2 := (int(x0) - (64 * x0i)) * 2
|
||||||
|
// Do the first pixel.
|
||||||
|
dcover := int(edge1 - y0f)
|
||||||
|
darea := int(x0fTimes2 * dcover)
|
||||||
|
r.area += darea
|
||||||
|
r.cover += dcover
|
||||||
|
yi += yiDelta
|
||||||
|
r.setCell(x0i, yi)
|
||||||
|
// Do all the intermediate pixels.
|
||||||
|
dcover = int(edge1 - edge0)
|
||||||
|
darea = int(x0fTimes2 * dcover)
|
||||||
|
for yi != y1i {
|
||||||
|
r.area += darea
|
||||||
|
r.cover += dcover
|
||||||
|
yi += yiDelta
|
||||||
|
r.setCell(x0i, yi)
|
||||||
|
}
|
||||||
|
// Do the last pixel.
|
||||||
|
dcover = int(y1f - edge0)
|
||||||
|
darea = int(x0fTimes2 * dcover)
|
||||||
|
r.area += darea
|
||||||
|
r.cover += dcover
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// There are at least two scanlines. Apart from the first and last
|
||||||
|
// scanlines, all intermediate scanlines go through the full height of
|
||||||
|
// the row, or 64 units in 26.6 fixed point format.
|
||||||
|
var (
|
||||||
|
p, q, edge0, edge1 fixed.Int26_6
|
||||||
|
yiDelta int
|
||||||
|
)
|
||||||
|
if dy > 0 {
|
||||||
|
p, q = (64-y0f)*dx, dy
|
||||||
|
edge0, edge1, yiDelta = 0, 64, 1
|
||||||
|
} else {
|
||||||
|
p, q = y0f*dx, -dy
|
||||||
|
edge0, edge1, yiDelta = 64, 0, -1
|
||||||
|
}
|
||||||
|
xDelta, xRem := p/q, p%q
|
||||||
|
if xRem < 0 {
|
||||||
|
xDelta -= 1
|
||||||
|
xRem += q
|
||||||
|
}
|
||||||
|
// Do the first scanline.
|
||||||
|
x, yi := x0, y0i
|
||||||
|
r.scan(yi, x, y0f, x+xDelta, edge1)
|
||||||
|
x, yi = x+xDelta, yi+yiDelta
|
||||||
|
r.setCell(int(x)/64, yi)
|
||||||
|
if yi != y1i {
|
||||||
|
// Do all the intermediate scanlines.
|
||||||
|
p = 64 * dx
|
||||||
|
fullDelta, fullRem := p/q, p%q
|
||||||
|
if fullRem < 0 {
|
||||||
|
fullDelta -= 1
|
||||||
|
fullRem += q
|
||||||
|
}
|
||||||
|
xRem -= q
|
||||||
|
for yi != y1i {
|
||||||
|
xDelta = fullDelta
|
||||||
|
xRem += fullRem
|
||||||
|
if xRem >= 0 {
|
||||||
|
xDelta += 1
|
||||||
|
xRem -= q
|
||||||
|
}
|
||||||
|
r.scan(yi, x, edge0, x+xDelta, edge1)
|
||||||
|
x, yi = x+xDelta, yi+yiDelta
|
||||||
|
r.setCell(int(x)/64, yi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Do the last scanline.
|
||||||
|
r.scan(yi, x, edge0, x1, y1f)
|
||||||
|
}
|
||||||
|
// The next lineTo starts from b.
|
||||||
|
r.a = b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add2 adds a quadratic segment to the current curve.
|
||||||
|
func (r *Rasterizer) Add2(b, c fixed.Point26_6) {
|
||||||
|
// Calculate nSplit (the number of recursive decompositions) based on how
|
||||||
|
// 'curvy' it is. Specifically, how much the middle point b deviates from
|
||||||
|
// (a+c)/2.
|
||||||
|
dev := maxAbs(r.a.X-2*b.X+c.X, r.a.Y-2*b.Y+c.Y) / fixed.Int26_6(r.splitScale2)
|
||||||
|
nsplit := 0
|
||||||
|
for dev > 0 {
|
||||||
|
dev /= 4
|
||||||
|
nsplit++
|
||||||
|
}
|
||||||
|
// dev is 32-bit, and nsplit++ every time we shift off 2 bits, so maxNsplit
|
||||||
|
// is 16.
|
||||||
|
const maxNsplit = 16
|
||||||
|
if nsplit > maxNsplit {
|
||||||
|
panic("freetype/raster: Add2 nsplit too large: " + strconv.Itoa(nsplit))
|
||||||
|
}
|
||||||
|
// Recursively decompose the curve nSplit levels deep.
|
||||||
|
var (
|
||||||
|
pStack [2*maxNsplit + 3]fixed.Point26_6
|
||||||
|
sStack [maxNsplit + 1]int
|
||||||
|
i int
|
||||||
|
)
|
||||||
|
sStack[0] = nsplit
|
||||||
|
pStack[0] = c
|
||||||
|
pStack[1] = b
|
||||||
|
pStack[2] = r.a
|
||||||
|
for i >= 0 {
|
||||||
|
s := sStack[i]
|
||||||
|
p := pStack[2*i:]
|
||||||
|
if s > 0 {
|
||||||
|
// Split the quadratic curve p[:3] into an equivalent set of two
|
||||||
|
// shorter curves: p[:3] and p[2:5]. The new p[4] is the old p[2],
|
||||||
|
// and p[0] is unchanged.
|
||||||
|
mx := p[1].X
|
||||||
|
p[4].X = p[2].X
|
||||||
|
p[3].X = (p[4].X + mx) / 2
|
||||||
|
p[1].X = (p[0].X + mx) / 2
|
||||||
|
p[2].X = (p[1].X + p[3].X) / 2
|
||||||
|
my := p[1].Y
|
||||||
|
p[4].Y = p[2].Y
|
||||||
|
p[3].Y = (p[4].Y + my) / 2
|
||||||
|
p[1].Y = (p[0].Y + my) / 2
|
||||||
|
p[2].Y = (p[1].Y + p[3].Y) / 2
|
||||||
|
// The two shorter curves have one less split to do.
|
||||||
|
sStack[i] = s - 1
|
||||||
|
sStack[i+1] = s - 1
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
// Replace the level-0 quadratic with a two-linear-piece
|
||||||
|
// approximation.
|
||||||
|
midx := (p[0].X + 2*p[1].X + p[2].X) / 4
|
||||||
|
midy := (p[0].Y + 2*p[1].Y + p[2].Y) / 4
|
||||||
|
r.Add1(fixed.Point26_6{midx, midy})
|
||||||
|
r.Add1(p[0])
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add3 adds a cubic segment to the current curve.
|
||||||
|
func (r *Rasterizer) Add3(b, c, d fixed.Point26_6) {
|
||||||
|
// Calculate nSplit (the number of recursive decompositions) based on how
|
||||||
|
// 'curvy' it is.
|
||||||
|
dev2 := maxAbs(r.a.X-3*(b.X+c.X)+d.X, r.a.Y-3*(b.Y+c.Y)+d.Y) / fixed.Int26_6(r.splitScale2)
|
||||||
|
dev3 := maxAbs(r.a.X-2*b.X+d.X, r.a.Y-2*b.Y+d.Y) / fixed.Int26_6(r.splitScale3)
|
||||||
|
nsplit := 0
|
||||||
|
for dev2 > 0 || dev3 > 0 {
|
||||||
|
dev2 /= 8
|
||||||
|
dev3 /= 4
|
||||||
|
nsplit++
|
||||||
|
}
|
||||||
|
// devN is 32-bit, and nsplit++ every time we shift off 2 bits, so
|
||||||
|
// maxNsplit is 16.
|
||||||
|
const maxNsplit = 16
|
||||||
|
if nsplit > maxNsplit {
|
||||||
|
panic("freetype/raster: Add3 nsplit too large: " + strconv.Itoa(nsplit))
|
||||||
|
}
|
||||||
|
// Recursively decompose the curve nSplit levels deep.
|
||||||
|
var (
|
||||||
|
pStack [3*maxNsplit + 4]fixed.Point26_6
|
||||||
|
sStack [maxNsplit + 1]int
|
||||||
|
i int
|
||||||
|
)
|
||||||
|
sStack[0] = nsplit
|
||||||
|
pStack[0] = d
|
||||||
|
pStack[1] = c
|
||||||
|
pStack[2] = b
|
||||||
|
pStack[3] = r.a
|
||||||
|
for i >= 0 {
|
||||||
|
s := sStack[i]
|
||||||
|
p := pStack[3*i:]
|
||||||
|
if s > 0 {
|
||||||
|
// Split the cubic curve p[:4] into an equivalent set of two
|
||||||
|
// shorter curves: p[:4] and p[3:7]. The new p[6] is the old p[3],
|
||||||
|
// and p[0] is unchanged.
|
||||||
|
m01x := (p[0].X + p[1].X) / 2
|
||||||
|
m12x := (p[1].X + p[2].X) / 2
|
||||||
|
m23x := (p[2].X + p[3].X) / 2
|
||||||
|
p[6].X = p[3].X
|
||||||
|
p[5].X = m23x
|
||||||
|
p[1].X = m01x
|
||||||
|
p[2].X = (m01x + m12x) / 2
|
||||||
|
p[4].X = (m12x + m23x) / 2
|
||||||
|
p[3].X = (p[2].X + p[4].X) / 2
|
||||||
|
m01y := (p[0].Y + p[1].Y) / 2
|
||||||
|
m12y := (p[1].Y + p[2].Y) / 2
|
||||||
|
m23y := (p[2].Y + p[3].Y) / 2
|
||||||
|
p[6].Y = p[3].Y
|
||||||
|
p[5].Y = m23y
|
||||||
|
p[1].Y = m01y
|
||||||
|
p[2].Y = (m01y + m12y) / 2
|
||||||
|
p[4].Y = (m12y + m23y) / 2
|
||||||
|
p[3].Y = (p[2].Y + p[4].Y) / 2
|
||||||
|
// The two shorter curves have one less split to do.
|
||||||
|
sStack[i] = s - 1
|
||||||
|
sStack[i+1] = s - 1
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
// Replace the level-0 cubic with a two-linear-piece approximation.
|
||||||
|
midx := (p[0].X + 3*(p[1].X+p[2].X) + p[3].X) / 8
|
||||||
|
midy := (p[0].Y + 3*(p[1].Y+p[2].Y) + p[3].Y) / 8
|
||||||
|
r.Add1(fixed.Point26_6{midx, midy})
|
||||||
|
r.Add1(p[0])
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPath adds the given Path.
|
||||||
|
func (r *Rasterizer) AddPath(p Path) {
|
||||||
|
for i := 0; i < len(p); {
|
||||||
|
switch p[i] {
|
||||||
|
case 0:
|
||||||
|
r.Start(
|
||||||
|
fixed.Point26_6{p[i+1], p[i+2]},
|
||||||
|
)
|
||||||
|
i += 4
|
||||||
|
case 1:
|
||||||
|
r.Add1(
|
||||||
|
fixed.Point26_6{p[i+1], p[i+2]},
|
||||||
|
)
|
||||||
|
i += 4
|
||||||
|
case 2:
|
||||||
|
r.Add2(
|
||||||
|
fixed.Point26_6{p[i+1], p[i+2]},
|
||||||
|
fixed.Point26_6{p[i+3], p[i+4]},
|
||||||
|
)
|
||||||
|
i += 6
|
||||||
|
case 3:
|
||||||
|
r.Add3(
|
||||||
|
fixed.Point26_6{p[i+1], p[i+2]},
|
||||||
|
fixed.Point26_6{p[i+3], p[i+4]},
|
||||||
|
fixed.Point26_6{p[i+5], p[i+6]},
|
||||||
|
)
|
||||||
|
i += 8
|
||||||
|
default:
|
||||||
|
panic("freetype/raster: bad path")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddStroke adds a stroked Path.
|
||||||
|
func (r *Rasterizer) AddStroke(q Path, width fixed.Int26_6, cr Capper, jr Joiner) {
|
||||||
|
Stroke(r, q, width, cr, jr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// areaToAlpha converts an area value to a uint32 alpha value. A completely
|
||||||
|
// filled pixel corresponds to an area of 64*64*2, and an alpha of 0xffff. The
|
||||||
|
// conversion of area values greater than this depends on the winding rule:
|
||||||
|
// even-odd or non-zero.
|
||||||
|
func (r *Rasterizer) areaToAlpha(area int) uint32 {
|
||||||
|
// The C Freetype implementation (version 2.3.12) does "alpha := area>>1"
|
||||||
|
// without the +1. Round-to-nearest gives a more symmetric result than
|
||||||
|
// round-down. The C implementation also returns 8-bit alpha, not 16-bit
|
||||||
|
// alpha.
|
||||||
|
a := (area + 1) >> 1
|
||||||
|
if a < 0 {
|
||||||
|
a = -a
|
||||||
|
}
|
||||||
|
alpha := uint32(a)
|
||||||
|
if r.UseNonZeroWinding {
|
||||||
|
if alpha > 0x0fff {
|
||||||
|
alpha = 0x0fff
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alpha &= 0x1fff
|
||||||
|
if alpha > 0x1000 {
|
||||||
|
alpha = 0x2000 - alpha
|
||||||
|
} else if alpha == 0x1000 {
|
||||||
|
alpha = 0x0fff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// alpha is now in the range [0x0000, 0x0fff]. Convert that 12-bit alpha to
|
||||||
|
// 16-bit alpha.
|
||||||
|
return alpha<<4 | alpha>>8
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rasterize converts r's accumulated curves into Spans for p. The Spans passed
|
||||||
|
// to p are non-overlapping, and sorted by Y and then X. They all have non-zero
|
||||||
|
// width (and 0 <= X0 < X1 <= r.width) and non-zero A, except for the final
|
||||||
|
// Span, which has Y, X0, X1 and A all equal to zero.
|
||||||
|
func (r *Rasterizer) Rasterize(p Painter) {
|
||||||
|
r.saveCell()
|
||||||
|
s := 0
|
||||||
|
for yi := 0; yi < len(r.cellIndex); yi++ {
|
||||||
|
xi, cover := 0, 0
|
||||||
|
for c := r.cellIndex[yi]; c != -1; c = r.cell[c].next {
|
||||||
|
if cover != 0 && r.cell[c].xi > xi {
|
||||||
|
alpha := r.areaToAlpha(cover * 64 * 2)
|
||||||
|
if alpha != 0 {
|
||||||
|
xi0, xi1 := xi, r.cell[c].xi
|
||||||
|
if xi0 < 0 {
|
||||||
|
xi0 = 0
|
||||||
|
}
|
||||||
|
if xi1 >= r.width {
|
||||||
|
xi1 = r.width
|
||||||
|
}
|
||||||
|
if xi0 < xi1 {
|
||||||
|
r.spanBuf[s] = Span{yi + r.Dy, xi0 + r.Dx, xi1 + r.Dx, alpha}
|
||||||
|
s++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cover += r.cell[c].cover
|
||||||
|
alpha := r.areaToAlpha(cover*64*2 - r.cell[c].area)
|
||||||
|
xi = r.cell[c].xi + 1
|
||||||
|
if alpha != 0 {
|
||||||
|
xi0, xi1 := r.cell[c].xi, xi
|
||||||
|
if xi0 < 0 {
|
||||||
|
xi0 = 0
|
||||||
|
}
|
||||||
|
if xi1 >= r.width {
|
||||||
|
xi1 = r.width
|
||||||
|
}
|
||||||
|
if xi0 < xi1 {
|
||||||
|
r.spanBuf[s] = Span{yi + r.Dy, xi0 + r.Dx, xi1 + r.Dx, alpha}
|
||||||
|
s++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s > len(r.spanBuf)-2 {
|
||||||
|
p.Paint(r.spanBuf[:s], false)
|
||||||
|
s = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.Paint(r.spanBuf[:s], true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear cancels any previous calls to r.Start or r.AddXxx.
|
||||||
|
func (r *Rasterizer) Clear() {
|
||||||
|
r.a = fixed.Point26_6{}
|
||||||
|
r.xi = 0
|
||||||
|
r.yi = 0
|
||||||
|
r.area = 0
|
||||||
|
r.cover = 0
|
||||||
|
r.cell = r.cell[:0]
|
||||||
|
for i := 0; i < len(r.cellIndex); i++ {
|
||||||
|
r.cellIndex[i] = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBounds sets the maximum width and height of the rasterized image and
|
||||||
|
// calls Clear. The width and height are in pixels, not fixed.Int26_6 units.
|
||||||
|
func (r *Rasterizer) SetBounds(width, height int) {
|
||||||
|
if width < 0 {
|
||||||
|
width = 0
|
||||||
|
}
|
||||||
|
if height < 0 {
|
||||||
|
height = 0
|
||||||
|
}
|
||||||
|
// Use the same ssN heuristic as the C Freetype (version 2.4.0)
|
||||||
|
// implementation.
|
||||||
|
ss2, ss3 := 32, 16
|
||||||
|
if width > 24 || height > 24 {
|
||||||
|
ss2, ss3 = 2*ss2, 2*ss3
|
||||||
|
if width > 120 || height > 120 {
|
||||||
|
ss2, ss3 = 2*ss2, 2*ss3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.width = width
|
||||||
|
r.splitScale2 = ss2
|
||||||
|
r.splitScale3 = ss3
|
||||||
|
r.cell = r.cellBuf[:0]
|
||||||
|
if height > len(r.cellIndexBuf) {
|
||||||
|
r.cellIndex = make([]int, height)
|
||||||
|
} else {
|
||||||
|
r.cellIndex = r.cellIndexBuf[:height]
|
||||||
|
}
|
||||||
|
r.Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRasterizer creates a new Rasterizer with the given bounds.
|
||||||
|
func NewRasterizer(width, height int) *Rasterizer {
|
||||||
|
r := new(Rasterizer)
|
||||||
|
r.SetBounds(width, height)
|
||||||
|
return r
|
||||||
|
}
|
483
vendor/github.com/golang/freetype/raster/stroke.go
generated
vendored
Normal file
483
vendor/github.com/golang/freetype/raster/stroke.go
generated
vendored
Normal file
|
@ -0,0 +1,483 @@
|
||||||
|
// Copyright 2010 The Freetype-Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by your choice of either the
|
||||||
|
// FreeType License or the GNU General Public License version 2 (or
|
||||||
|
// any later version), both of which can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package raster
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Two points are considered practically equal if the square of the distance
|
||||||
|
// between them is less than one quarter (i.e. 1024 / 4096).
|
||||||
|
const epsilon = fixed.Int52_12(1024)
|
||||||
|
|
||||||
|
// A Capper signifies how to begin or end a stroked path.
|
||||||
|
type Capper interface {
|
||||||
|
// Cap adds a cap to p given a pivot point and the normal vector of a
|
||||||
|
// terminal segment. The normal's length is half of the stroke width.
|
||||||
|
Cap(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The CapperFunc type adapts an ordinary function to be a Capper.
|
||||||
|
type CapperFunc func(Adder, fixed.Int26_6, fixed.Point26_6, fixed.Point26_6)
|
||||||
|
|
||||||
|
func (f CapperFunc) Cap(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) {
|
||||||
|
f(p, halfWidth, pivot, n1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Joiner signifies how to join interior nodes of a stroked path.
|
||||||
|
type Joiner interface {
|
||||||
|
// Join adds a join to the two sides of a stroked path given a pivot
|
||||||
|
// point and the normal vectors of the trailing and leading segments.
|
||||||
|
// Both normals have length equal to half of the stroke width.
|
||||||
|
Join(lhs, rhs Adder, halfWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The JoinerFunc type adapts an ordinary function to be a Joiner.
|
||||||
|
type JoinerFunc func(lhs, rhs Adder, halfWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6)
|
||||||
|
|
||||||
|
func (f JoinerFunc) Join(lhs, rhs Adder, halfWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6) {
|
||||||
|
f(lhs, rhs, halfWidth, pivot, n0, n1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundCapper adds round caps to a stroked path.
|
||||||
|
var RoundCapper Capper = CapperFunc(roundCapper)
|
||||||
|
|
||||||
|
func roundCapper(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) {
|
||||||
|
// The cubic Bézier approximation to a circle involves the magic number
|
||||||
|
// (√2 - 1) * 4/3, which is approximately 35/64.
|
||||||
|
const k = 35
|
||||||
|
e := pRot90CCW(n1)
|
||||||
|
side := pivot.Add(e)
|
||||||
|
start, end := pivot.Sub(n1), pivot.Add(n1)
|
||||||
|
d, e := n1.Mul(k), e.Mul(k)
|
||||||
|
p.Add3(start.Add(e), side.Sub(d), side)
|
||||||
|
p.Add3(side.Add(d), end.Add(e), end)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ButtCapper adds butt caps to a stroked path.
|
||||||
|
var ButtCapper Capper = CapperFunc(buttCapper)
|
||||||
|
|
||||||
|
func buttCapper(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) {
|
||||||
|
p.Add1(pivot.Add(n1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SquareCapper adds square caps to a stroked path.
|
||||||
|
var SquareCapper Capper = CapperFunc(squareCapper)
|
||||||
|
|
||||||
|
func squareCapper(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) {
|
||||||
|
e := pRot90CCW(n1)
|
||||||
|
side := pivot.Add(e)
|
||||||
|
p.Add1(side.Sub(n1))
|
||||||
|
p.Add1(side.Add(n1))
|
||||||
|
p.Add1(pivot.Add(n1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundJoiner adds round joins to a stroked path.
|
||||||
|
var RoundJoiner Joiner = JoinerFunc(roundJoiner)
|
||||||
|
|
||||||
|
func roundJoiner(lhs, rhs Adder, haflWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6) {
|
||||||
|
dot := pDot(pRot90CW(n0), n1)
|
||||||
|
if dot >= 0 {
|
||||||
|
addArc(lhs, pivot, n0, n1)
|
||||||
|
rhs.Add1(pivot.Sub(n1))
|
||||||
|
} else {
|
||||||
|
lhs.Add1(pivot.Add(n1))
|
||||||
|
addArc(rhs, pivot, pNeg(n0), pNeg(n1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BevelJoiner adds bevel joins to a stroked path.
|
||||||
|
var BevelJoiner Joiner = JoinerFunc(bevelJoiner)
|
||||||
|
|
||||||
|
func bevelJoiner(lhs, rhs Adder, haflWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6) {
|
||||||
|
lhs.Add1(pivot.Add(n1))
|
||||||
|
rhs.Add1(pivot.Sub(n1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// addArc adds a circular arc from pivot+n0 to pivot+n1 to p. The shorter of
|
||||||
|
// the two possible arcs is taken, i.e. the one spanning <= 180 degrees. The
|
||||||
|
// two vectors n0 and n1 must be of equal length.
|
||||||
|
func addArc(p Adder, pivot, n0, n1 fixed.Point26_6) {
|
||||||
|
// r2 is the square of the length of n0.
|
||||||
|
r2 := pDot(n0, n0)
|
||||||
|
if r2 < epsilon {
|
||||||
|
// The arc radius is so small that we collapse to a straight line.
|
||||||
|
p.Add1(pivot.Add(n1))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// We approximate the arc by 0, 1, 2 or 3 45-degree quadratic segments plus
|
||||||
|
// a final quadratic segment from s to n1. Each 45-degree segment has
|
||||||
|
// control points {1, 0}, {1, tan(π/8)} and {1/√2, 1/√2} suitably scaled,
|
||||||
|
// rotated and translated. tan(π/8) is approximately 27/64.
|
||||||
|
const tpo8 = 27
|
||||||
|
var s fixed.Point26_6
|
||||||
|
// We determine which octant the angle between n0 and n1 is in via three
|
||||||
|
// dot products. m0, m1 and m2 are n0 rotated clockwise by 45, 90 and 135
|
||||||
|
// degrees.
|
||||||
|
m0 := pRot45CW(n0)
|
||||||
|
m1 := pRot90CW(n0)
|
||||||
|
m2 := pRot90CW(m0)
|
||||||
|
if pDot(m1, n1) >= 0 {
|
||||||
|
if pDot(n0, n1) >= 0 {
|
||||||
|
if pDot(m2, n1) <= 0 {
|
||||||
|
// n1 is between 0 and 45 degrees clockwise of n0.
|
||||||
|
s = n0
|
||||||
|
} else {
|
||||||
|
// n1 is between 45 and 90 degrees clockwise of n0.
|
||||||
|
p.Add2(pivot.Add(n0).Add(m1.Mul(tpo8)), pivot.Add(m0))
|
||||||
|
s = m0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pm1, n0t := pivot.Add(m1), n0.Mul(tpo8)
|
||||||
|
p.Add2(pivot.Add(n0).Add(m1.Mul(tpo8)), pivot.Add(m0))
|
||||||
|
p.Add2(pm1.Add(n0t), pm1)
|
||||||
|
if pDot(m0, n1) >= 0 {
|
||||||
|
// n1 is between 90 and 135 degrees clockwise of n0.
|
||||||
|
s = m1
|
||||||
|
} else {
|
||||||
|
// n1 is between 135 and 180 degrees clockwise of n0.
|
||||||
|
p.Add2(pm1.Sub(n0t), pivot.Add(m2))
|
||||||
|
s = m2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if pDot(n0, n1) >= 0 {
|
||||||
|
if pDot(m0, n1) >= 0 {
|
||||||
|
// n1 is between 0 and 45 degrees counter-clockwise of n0.
|
||||||
|
s = n0
|
||||||
|
} else {
|
||||||
|
// n1 is between 45 and 90 degrees counter-clockwise of n0.
|
||||||
|
p.Add2(pivot.Add(n0).Sub(m1.Mul(tpo8)), pivot.Sub(m2))
|
||||||
|
s = pNeg(m2)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pm1, n0t := pivot.Sub(m1), n0.Mul(tpo8)
|
||||||
|
p.Add2(pivot.Add(n0).Sub(m1.Mul(tpo8)), pivot.Sub(m2))
|
||||||
|
p.Add2(pm1.Add(n0t), pm1)
|
||||||
|
if pDot(m2, n1) <= 0 {
|
||||||
|
// n1 is between 90 and 135 degrees counter-clockwise of n0.
|
||||||
|
s = pNeg(m1)
|
||||||
|
} else {
|
||||||
|
// n1 is between 135 and 180 degrees counter-clockwise of n0.
|
||||||
|
p.Add2(pm1.Sub(n0t), pivot.Sub(m0))
|
||||||
|
s = pNeg(m0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The final quadratic segment has two endpoints s and n1 and the middle
|
||||||
|
// control point is a multiple of s.Add(n1), i.e. it is on the angle
|
||||||
|
// bisector of those two points. The multiple ranges between 128/256 and
|
||||||
|
// 150/256 as the angle between s and n1 ranges between 0 and 45 degrees.
|
||||||
|
//
|
||||||
|
// When the angle is 0 degrees (i.e. s and n1 are coincident) then
|
||||||
|
// s.Add(n1) is twice s and so the middle control point of the degenerate
|
||||||
|
// quadratic segment should be half s.Add(n1), and half = 128/256.
|
||||||
|
//
|
||||||
|
// When the angle is 45 degrees then 150/256 is the ratio of the lengths of
|
||||||
|
// the two vectors {1, tan(π/8)} and {1 + 1/√2, 1/√2}.
|
||||||
|
//
|
||||||
|
// d is the normalized dot product between s and n1. Since the angle ranges
|
||||||
|
// between 0 and 45 degrees then d ranges between 256/256 and 181/256.
|
||||||
|
d := 256 * pDot(s, n1) / r2
|
||||||
|
multiple := fixed.Int26_6(150-(150-128)*(d-181)/(256-181)) >> 2
|
||||||
|
p.Add2(pivot.Add(s.Add(n1).Mul(multiple)), pivot.Add(n1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// midpoint returns the midpoint of two Points.
|
||||||
|
func midpoint(a, b fixed.Point26_6) fixed.Point26_6 {
|
||||||
|
return fixed.Point26_6{(a.X + b.X) / 2, (a.Y + b.Y) / 2}
|
||||||
|
}
|
||||||
|
|
||||||
|
// angleGreaterThan45 returns whether the angle between two vectors is more
|
||||||
|
// than 45 degrees.
|
||||||
|
func angleGreaterThan45(v0, v1 fixed.Point26_6) bool {
|
||||||
|
v := pRot45CCW(v0)
|
||||||
|
return pDot(v, v1) < 0 || pDot(pRot90CW(v), v1) < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// interpolate returns the point (1-t)*a + t*b.
|
||||||
|
func interpolate(a, b fixed.Point26_6, t fixed.Int52_12) fixed.Point26_6 {
|
||||||
|
s := 1<<12 - t
|
||||||
|
x := s*fixed.Int52_12(a.X) + t*fixed.Int52_12(b.X)
|
||||||
|
y := s*fixed.Int52_12(a.Y) + t*fixed.Int52_12(b.Y)
|
||||||
|
return fixed.Point26_6{fixed.Int26_6(x >> 12), fixed.Int26_6(y >> 12)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// curviest2 returns the value of t for which the quadratic parametric curve
|
||||||
|
// (1-t)²*a + 2*t*(1-t).b + t²*c has maximum curvature.
|
||||||
|
//
|
||||||
|
// The curvature of the parametric curve f(t) = (x(t), y(t)) is
|
||||||
|
// |x′y″-y′x″| / (x′²+y′²)^(3/2).
|
||||||
|
//
|
||||||
|
// Let d = b-a and e = c-2*b+a, so that f′(t) = 2*d+2*e*t and f″(t) = 2*e.
|
||||||
|
// The curvature's numerator is (2*dx+2*ex*t)*(2*ey)-(2*dy+2*ey*t)*(2*ex),
|
||||||
|
// which simplifies to 4*dx*ey-4*dy*ex, which is constant with respect to t.
|
||||||
|
//
|
||||||
|
// Thus, curvature is extreme where the denominator is extreme, i.e. where
|
||||||
|
// (x′²+y′²) is extreme. The first order condition is that
|
||||||
|
// 2*x′*x″+2*y′*y″ = 0, or (dx+ex*t)*ex + (dy+ey*t)*ey = 0.
|
||||||
|
// Solving for t gives t = -(dx*ex+dy*ey) / (ex*ex+ey*ey).
|
||||||
|
func curviest2(a, b, c fixed.Point26_6) fixed.Int52_12 {
|
||||||
|
dx := int64(b.X - a.X)
|
||||||
|
dy := int64(b.Y - a.Y)
|
||||||
|
ex := int64(c.X - 2*b.X + a.X)
|
||||||
|
ey := int64(c.Y - 2*b.Y + a.Y)
|
||||||
|
if ex == 0 && ey == 0 {
|
||||||
|
return 2048
|
||||||
|
}
|
||||||
|
return fixed.Int52_12(-4096 * (dx*ex + dy*ey) / (ex*ex + ey*ey))
|
||||||
|
}
|
||||||
|
|
||||||
|
// A stroker holds state for stroking a path.
|
||||||
|
type stroker struct {
|
||||||
|
// p is the destination that records the stroked path.
|
||||||
|
p Adder
|
||||||
|
// u is the half-width of the stroke.
|
||||||
|
u fixed.Int26_6
|
||||||
|
// cr and jr specify how to end and connect path segments.
|
||||||
|
cr Capper
|
||||||
|
jr Joiner
|
||||||
|
// r is the reverse path. Stroking a path involves constructing two
|
||||||
|
// parallel paths 2*u apart. The first path is added immediately to p,
|
||||||
|
// the second path is accumulated in r and eventually added in reverse.
|
||||||
|
r Path
|
||||||
|
// a is the most recent segment point. anorm is the segment normal of
|
||||||
|
// length u at that point.
|
||||||
|
a, anorm fixed.Point26_6
|
||||||
|
}
|
||||||
|
|
||||||
|
// addNonCurvy2 adds a quadratic segment to the stroker, where the segment
|
||||||
|
// defined by (k.a, b, c) achieves maximum curvature at either k.a or c.
|
||||||
|
func (k *stroker) addNonCurvy2(b, c fixed.Point26_6) {
|
||||||
|
// We repeatedly divide the segment at its middle until it is straight
|
||||||
|
// enough to approximate the stroke by just translating the control points.
|
||||||
|
// ds and ps are stacks of depths and points. t is the top of the stack.
|
||||||
|
const maxDepth = 5
|
||||||
|
var (
|
||||||
|
ds [maxDepth + 1]int
|
||||||
|
ps [2*maxDepth + 3]fixed.Point26_6
|
||||||
|
t int
|
||||||
|
)
|
||||||
|
// Initially the ps stack has one quadratic segment of depth zero.
|
||||||
|
ds[0] = 0
|
||||||
|
ps[2] = k.a
|
||||||
|
ps[1] = b
|
||||||
|
ps[0] = c
|
||||||
|
anorm := k.anorm
|
||||||
|
var cnorm fixed.Point26_6
|
||||||
|
|
||||||
|
for {
|
||||||
|
depth := ds[t]
|
||||||
|
a := ps[2*t+2]
|
||||||
|
b := ps[2*t+1]
|
||||||
|
c := ps[2*t+0]
|
||||||
|
ab := b.Sub(a)
|
||||||
|
bc := c.Sub(b)
|
||||||
|
abIsSmall := pDot(ab, ab) < fixed.Int52_12(1<<12)
|
||||||
|
bcIsSmall := pDot(bc, bc) < fixed.Int52_12(1<<12)
|
||||||
|
if abIsSmall && bcIsSmall {
|
||||||
|
// Approximate the segment by a circular arc.
|
||||||
|
cnorm = pRot90CCW(pNorm(bc, k.u))
|
||||||
|
mac := midpoint(a, c)
|
||||||
|
addArc(k.p, mac, anorm, cnorm)
|
||||||
|
addArc(&k.r, mac, pNeg(anorm), pNeg(cnorm))
|
||||||
|
} else if depth < maxDepth && angleGreaterThan45(ab, bc) {
|
||||||
|
// Divide the segment in two and push both halves on the stack.
|
||||||
|
mab := midpoint(a, b)
|
||||||
|
mbc := midpoint(b, c)
|
||||||
|
t++
|
||||||
|
ds[t+0] = depth + 1
|
||||||
|
ds[t-1] = depth + 1
|
||||||
|
ps[2*t+2] = a
|
||||||
|
ps[2*t+1] = mab
|
||||||
|
ps[2*t+0] = midpoint(mab, mbc)
|
||||||
|
ps[2*t-1] = mbc
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
// Translate the control points.
|
||||||
|
bnorm := pRot90CCW(pNorm(c.Sub(a), k.u))
|
||||||
|
cnorm = pRot90CCW(pNorm(bc, k.u))
|
||||||
|
k.p.Add2(b.Add(bnorm), c.Add(cnorm))
|
||||||
|
k.r.Add2(b.Sub(bnorm), c.Sub(cnorm))
|
||||||
|
}
|
||||||
|
if t == 0 {
|
||||||
|
k.a, k.anorm = c, cnorm
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t--
|
||||||
|
anorm = cnorm
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add1 adds a linear segment to the stroker.
|
||||||
|
func (k *stroker) Add1(b fixed.Point26_6) {
|
||||||
|
bnorm := pRot90CCW(pNorm(b.Sub(k.a), k.u))
|
||||||
|
if len(k.r) == 0 {
|
||||||
|
k.p.Start(k.a.Add(bnorm))
|
||||||
|
k.r.Start(k.a.Sub(bnorm))
|
||||||
|
} else {
|
||||||
|
k.jr.Join(k.p, &k.r, k.u, k.a, k.anorm, bnorm)
|
||||||
|
}
|
||||||
|
k.p.Add1(b.Add(bnorm))
|
||||||
|
k.r.Add1(b.Sub(bnorm))
|
||||||
|
k.a, k.anorm = b, bnorm
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add2 adds a quadratic segment to the stroker.
|
||||||
|
func (k *stroker) Add2(b, c fixed.Point26_6) {
|
||||||
|
ab := b.Sub(k.a)
|
||||||
|
bc := c.Sub(b)
|
||||||
|
abnorm := pRot90CCW(pNorm(ab, k.u))
|
||||||
|
if len(k.r) == 0 {
|
||||||
|
k.p.Start(k.a.Add(abnorm))
|
||||||
|
k.r.Start(k.a.Sub(abnorm))
|
||||||
|
} else {
|
||||||
|
k.jr.Join(k.p, &k.r, k.u, k.a, k.anorm, abnorm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Approximate nearly-degenerate quadratics by linear segments.
|
||||||
|
abIsSmall := pDot(ab, ab) < epsilon
|
||||||
|
bcIsSmall := pDot(bc, bc) < epsilon
|
||||||
|
if abIsSmall || bcIsSmall {
|
||||||
|
acnorm := pRot90CCW(pNorm(c.Sub(k.a), k.u))
|
||||||
|
k.p.Add1(c.Add(acnorm))
|
||||||
|
k.r.Add1(c.Sub(acnorm))
|
||||||
|
k.a, k.anorm = c, acnorm
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The quadratic segment (k.a, b, c) has a point of maximum curvature.
|
||||||
|
// If this occurs at an end point, we process the segment as a whole.
|
||||||
|
t := curviest2(k.a, b, c)
|
||||||
|
if t <= 0 || 4096 <= t {
|
||||||
|
k.addNonCurvy2(b, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, we perform a de Casteljau decomposition at the point of
|
||||||
|
// maximum curvature and process the two straighter parts.
|
||||||
|
mab := interpolate(k.a, b, t)
|
||||||
|
mbc := interpolate(b, c, t)
|
||||||
|
mabc := interpolate(mab, mbc, t)
|
||||||
|
|
||||||
|
// If the vectors ab and bc are close to being in opposite directions,
|
||||||
|
// then the decomposition can become unstable, so we approximate the
|
||||||
|
// quadratic segment by two linear segments joined by an arc.
|
||||||
|
bcnorm := pRot90CCW(pNorm(bc, k.u))
|
||||||
|
if pDot(abnorm, bcnorm) < -fixed.Int52_12(k.u)*fixed.Int52_12(k.u)*2047/2048 {
|
||||||
|
pArc := pDot(abnorm, bc) < 0
|
||||||
|
|
||||||
|
k.p.Add1(mabc.Add(abnorm))
|
||||||
|
if pArc {
|
||||||
|
z := pRot90CW(abnorm)
|
||||||
|
addArc(k.p, mabc, abnorm, z)
|
||||||
|
addArc(k.p, mabc, z, bcnorm)
|
||||||
|
}
|
||||||
|
k.p.Add1(mabc.Add(bcnorm))
|
||||||
|
k.p.Add1(c.Add(bcnorm))
|
||||||
|
|
||||||
|
k.r.Add1(mabc.Sub(abnorm))
|
||||||
|
if !pArc {
|
||||||
|
z := pRot90CW(abnorm)
|
||||||
|
addArc(&k.r, mabc, pNeg(abnorm), z)
|
||||||
|
addArc(&k.r, mabc, z, pNeg(bcnorm))
|
||||||
|
}
|
||||||
|
k.r.Add1(mabc.Sub(bcnorm))
|
||||||
|
k.r.Add1(c.Sub(bcnorm))
|
||||||
|
|
||||||
|
k.a, k.anorm = c, bcnorm
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the decomposed parts.
|
||||||
|
k.addNonCurvy2(mab, mabc)
|
||||||
|
k.addNonCurvy2(mbc, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add3 adds a cubic segment to the stroker.
|
||||||
|
func (k *stroker) Add3(b, c, d fixed.Point26_6) {
|
||||||
|
panic("freetype/raster: stroke unimplemented for cubic segments")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stroke adds the stroked Path q to p, where q consists of exactly one curve.
|
||||||
|
func (k *stroker) stroke(q Path) {
|
||||||
|
// Stroking is implemented by deriving two paths each k.u apart from q.
|
||||||
|
// The left-hand-side path is added immediately to k.p; the right-hand-side
|
||||||
|
// path is accumulated in k.r. Once we've finished adding the LHS to k.p,
|
||||||
|
// we add the RHS in reverse order.
|
||||||
|
k.r = make(Path, 0, len(q))
|
||||||
|
k.a = fixed.Point26_6{q[1], q[2]}
|
||||||
|
for i := 4; i < len(q); {
|
||||||
|
switch q[i] {
|
||||||
|
case 1:
|
||||||
|
k.Add1(
|
||||||
|
fixed.Point26_6{q[i+1], q[i+2]},
|
||||||
|
)
|
||||||
|
i += 4
|
||||||
|
case 2:
|
||||||
|
k.Add2(
|
||||||
|
fixed.Point26_6{q[i+1], q[i+2]},
|
||||||
|
fixed.Point26_6{q[i+3], q[i+4]},
|
||||||
|
)
|
||||||
|
i += 6
|
||||||
|
case 3:
|
||||||
|
k.Add3(
|
||||||
|
fixed.Point26_6{q[i+1], q[i+2]},
|
||||||
|
fixed.Point26_6{q[i+3], q[i+4]},
|
||||||
|
fixed.Point26_6{q[i+5], q[i+6]},
|
||||||
|
)
|
||||||
|
i += 8
|
||||||
|
default:
|
||||||
|
panic("freetype/raster: bad path")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(k.r) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO(nigeltao): if q is a closed curve then we should join the first and
|
||||||
|
// last segments instead of capping them.
|
||||||
|
k.cr.Cap(k.p, k.u, q.lastPoint(), pNeg(k.anorm))
|
||||||
|
addPathReversed(k.p, k.r)
|
||||||
|
pivot := q.firstPoint()
|
||||||
|
k.cr.Cap(k.p, k.u, pivot, pivot.Sub(fixed.Point26_6{k.r[1], k.r[2]}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stroke adds q stroked with the given width to p. The result is typically
|
||||||
|
// self-intersecting and should be rasterized with UseNonZeroWinding.
|
||||||
|
// cr and jr may be nil, which defaults to a RoundCapper or RoundJoiner.
|
||||||
|
func Stroke(p Adder, q Path, width fixed.Int26_6, cr Capper, jr Joiner) {
|
||||||
|
if len(q) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cr == nil {
|
||||||
|
cr = RoundCapper
|
||||||
|
}
|
||||||
|
if jr == nil {
|
||||||
|
jr = RoundJoiner
|
||||||
|
}
|
||||||
|
if q[0] != 0 {
|
||||||
|
panic("freetype/raster: bad path")
|
||||||
|
}
|
||||||
|
s := stroker{p: p, u: width / 2, cr: cr, jr: jr}
|
||||||
|
i := 0
|
||||||
|
for j := 4; j < len(q); {
|
||||||
|
switch q[j] {
|
||||||
|
case 0:
|
||||||
|
s.stroke(q[i:j])
|
||||||
|
i, j = j, j+4
|
||||||
|
case 1:
|
||||||
|
j += 4
|
||||||
|
case 2:
|
||||||
|
j += 6
|
||||||
|
case 3:
|
||||||
|
j += 8
|
||||||
|
default:
|
||||||
|
panic("freetype/raster: bad path")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.stroke(q[i:])
|
||||||
|
}
|
507
vendor/github.com/golang/freetype/truetype/face.go
generated
vendored
Normal file
507
vendor/github.com/golang/freetype/truetype/face.go
generated
vendored
Normal file
|
@ -0,0 +1,507 @@
|
||||||
|
// Copyright 2015 The Freetype-Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by your choice of either the
|
||||||
|
// FreeType License or the GNU General Public License version 2 (or
|
||||||
|
// any later version), both of which can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package truetype
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/golang/freetype/raster"
|
||||||
|
"golang.org/x/image/font"
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
|
)
|
||||||
|
|
||||||
|
func powerOf2(i int) bool {
|
||||||
|
return i != 0 && (i&(i-1)) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options are optional arguments to NewFace.
|
||||||
|
type Options struct {
|
||||||
|
// Size is the font size in points, as in "a 10 point font size".
|
||||||
|
//
|
||||||
|
// A zero value means to use a 12 point font size.
|
||||||
|
Size float64
|
||||||
|
|
||||||
|
// DPI is the dots-per-inch resolution.
|
||||||
|
//
|
||||||
|
// A zero value means to use 72 DPI.
|
||||||
|
DPI float64
|
||||||
|
|
||||||
|
// Hinting is how to quantize the glyph nodes.
|
||||||
|
//
|
||||||
|
// A zero value means to use no hinting.
|
||||||
|
Hinting font.Hinting
|
||||||
|
|
||||||
|
// GlyphCacheEntries is the number of entries in the glyph mask image
|
||||||
|
// cache.
|
||||||
|
//
|
||||||
|
// If non-zero, it must be a power of 2.
|
||||||
|
//
|
||||||
|
// A zero value means to use 512 entries.
|
||||||
|
GlyphCacheEntries int
|
||||||
|
|
||||||
|
// SubPixelsX is the number of sub-pixel locations a glyph's dot is
|
||||||
|
// quantized to, in the horizontal direction. For example, a value of 8
|
||||||
|
// means that the dot is quantized to 1/8th of a pixel. This quantization
|
||||||
|
// only affects the glyph mask image, not its bounding box or advance
|
||||||
|
// width. A higher value gives a more faithful glyph image, but reduces the
|
||||||
|
// effectiveness of the glyph cache.
|
||||||
|
//
|
||||||
|
// If non-zero, it must be a power of 2, and be between 1 and 64 inclusive.
|
||||||
|
//
|
||||||
|
// A zero value means to use 4 sub-pixel locations.
|
||||||
|
SubPixelsX int
|
||||||
|
|
||||||
|
// SubPixelsY is the number of sub-pixel locations a glyph's dot is
|
||||||
|
// quantized to, in the vertical direction. For example, a value of 8
|
||||||
|
// means that the dot is quantized to 1/8th of a pixel. This quantization
|
||||||
|
// only affects the glyph mask image, not its bounding box or advance
|
||||||
|
// width. A higher value gives a more faithful glyph image, but reduces the
|
||||||
|
// effectiveness of the glyph cache.
|
||||||
|
//
|
||||||
|
// If non-zero, it must be a power of 2, and be between 1 and 64 inclusive.
|
||||||
|
//
|
||||||
|
// A zero value means to use 1 sub-pixel location.
|
||||||
|
SubPixelsY int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Options) size() float64 {
|
||||||
|
if o != nil && o.Size > 0 {
|
||||||
|
return o.Size
|
||||||
|
}
|
||||||
|
return 12
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Options) dpi() float64 {
|
||||||
|
if o != nil && o.DPI > 0 {
|
||||||
|
return o.DPI
|
||||||
|
}
|
||||||
|
return 72
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Options) hinting() font.Hinting {
|
||||||
|
if o != nil {
|
||||||
|
switch o.Hinting {
|
||||||
|
case font.HintingVertical, font.HintingFull:
|
||||||
|
// TODO: support vertical hinting.
|
||||||
|
return font.HintingFull
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return font.HintingNone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Options) glyphCacheEntries() int {
|
||||||
|
if o != nil && powerOf2(o.GlyphCacheEntries) {
|
||||||
|
return o.GlyphCacheEntries
|
||||||
|
}
|
||||||
|
// 512 is 128 * 4 * 1, which lets us cache 128 glyphs at 4 * 1 subpixel
|
||||||
|
// locations in the X and Y direction.
|
||||||
|
return 512
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Options) subPixelsX() (value uint32, halfQuantum, mask fixed.Int26_6) {
|
||||||
|
if o != nil {
|
||||||
|
switch o.SubPixelsX {
|
||||||
|
case 1, 2, 4, 8, 16, 32, 64:
|
||||||
|
return subPixels(o.SubPixelsX)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This default value of 4 isn't based on anything scientific, merely as
|
||||||
|
// small a number as possible that looks almost as good as no quantization,
|
||||||
|
// or returning subPixels(64).
|
||||||
|
return subPixels(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Options) subPixelsY() (value uint32, halfQuantum, mask fixed.Int26_6) {
|
||||||
|
if o != nil {
|
||||||
|
switch o.SubPixelsX {
|
||||||
|
case 1, 2, 4, 8, 16, 32, 64:
|
||||||
|
return subPixels(o.SubPixelsX)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This default value of 1 isn't based on anything scientific, merely that
|
||||||
|
// vertical sub-pixel glyph rendering is pretty rare. Baseline locations
|
||||||
|
// can usually afford to snap to the pixel grid, so the vertical direction
|
||||||
|
// doesn't have the deal with the horizontal's fractional advance widths.
|
||||||
|
return subPixels(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// subPixels returns q and the bias and mask that leads to q quantized
|
||||||
|
// sub-pixel locations per full pixel.
|
||||||
|
//
|
||||||
|
// For example, q == 4 leads to a bias of 8 and a mask of 0xfffffff0, or -16,
|
||||||
|
// because we want to round fractions of fixed.Int26_6 as:
|
||||||
|
// - 0 to 7 rounds to 0.
|
||||||
|
// - 8 to 23 rounds to 16.
|
||||||
|
// - 24 to 39 rounds to 32.
|
||||||
|
// - 40 to 55 rounds to 48.
|
||||||
|
// - 56 to 63 rounds to 64.
|
||||||
|
// which means to add 8 and then bitwise-and with -16, in two's complement
|
||||||
|
// representation.
|
||||||
|
//
|
||||||
|
// When q == 1, we want bias == 32 and mask == -64.
|
||||||
|
// When q == 2, we want bias == 16 and mask == -32.
|
||||||
|
// When q == 4, we want bias == 8 and mask == -16.
|
||||||
|
// ...
|
||||||
|
// When q == 64, we want bias == 0 and mask == -1. (The no-op case).
|
||||||
|
// The pattern is clear.
|
||||||
|
func subPixels(q int) (value uint32, bias, mask fixed.Int26_6) {
|
||||||
|
return uint32(q), 32 / fixed.Int26_6(q), -64 / fixed.Int26_6(q)
|
||||||
|
}
|
||||||
|
|
||||||
|
// glyphCacheEntry caches the arguments and return values of rasterize.
|
||||||
|
type glyphCacheEntry struct {
|
||||||
|
key glyphCacheKey
|
||||||
|
val glyphCacheVal
|
||||||
|
}
|
||||||
|
|
||||||
|
type glyphCacheKey struct {
|
||||||
|
index Index
|
||||||
|
fx, fy uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
type glyphCacheVal struct {
|
||||||
|
advanceWidth fixed.Int26_6
|
||||||
|
offset image.Point
|
||||||
|
gw int
|
||||||
|
gh int
|
||||||
|
}
|
||||||
|
|
||||||
|
type indexCacheEntry struct {
|
||||||
|
rune rune
|
||||||
|
index Index
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFace returns a new font.Face for the given Font.
|
||||||
|
func NewFace(f *Font, opts *Options) font.Face {
|
||||||
|
a := &face{
|
||||||
|
f: f,
|
||||||
|
hinting: opts.hinting(),
|
||||||
|
scale: fixed.Int26_6(0.5 + (opts.size() * opts.dpi() * 64 / 72)),
|
||||||
|
glyphCache: make([]glyphCacheEntry, opts.glyphCacheEntries()),
|
||||||
|
}
|
||||||
|
a.subPixelX, a.subPixelBiasX, a.subPixelMaskX = opts.subPixelsX()
|
||||||
|
a.subPixelY, a.subPixelBiasY, a.subPixelMaskY = opts.subPixelsY()
|
||||||
|
|
||||||
|
// Fill the cache with invalid entries. Valid glyph cache entries have fx
|
||||||
|
// and fy in the range [0, 64). Valid index cache entries have rune >= 0.
|
||||||
|
for i := range a.glyphCache {
|
||||||
|
a.glyphCache[i].key.fy = 0xff
|
||||||
|
}
|
||||||
|
for i := range a.indexCache {
|
||||||
|
a.indexCache[i].rune = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the rasterizer's bounds to be big enough to handle the largest glyph.
|
||||||
|
b := f.Bounds(a.scale)
|
||||||
|
xmin := +int(b.Min.X) >> 6
|
||||||
|
ymin := -int(b.Max.Y) >> 6
|
||||||
|
xmax := +int(b.Max.X+63) >> 6
|
||||||
|
ymax := -int(b.Min.Y-63) >> 6
|
||||||
|
a.maxw = xmax - xmin
|
||||||
|
a.maxh = ymax - ymin
|
||||||
|
a.masks = image.NewAlpha(image.Rect(0, 0, a.maxw, a.maxh*len(a.glyphCache)))
|
||||||
|
a.r.SetBounds(a.maxw, a.maxh)
|
||||||
|
a.p = facePainter{a}
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
type face struct {
|
||||||
|
f *Font
|
||||||
|
hinting font.Hinting
|
||||||
|
scale fixed.Int26_6
|
||||||
|
subPixelX uint32
|
||||||
|
subPixelBiasX fixed.Int26_6
|
||||||
|
subPixelMaskX fixed.Int26_6
|
||||||
|
subPixelY uint32
|
||||||
|
subPixelBiasY fixed.Int26_6
|
||||||
|
subPixelMaskY fixed.Int26_6
|
||||||
|
masks *image.Alpha
|
||||||
|
glyphCache []glyphCacheEntry
|
||||||
|
r raster.Rasterizer
|
||||||
|
p raster.Painter
|
||||||
|
paintOffset int
|
||||||
|
maxw int
|
||||||
|
maxh int
|
||||||
|
glyphBuf GlyphBuf
|
||||||
|
indexCache [indexCacheLen]indexCacheEntry
|
||||||
|
|
||||||
|
// TODO: clip rectangle?
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexCacheLen = 256
|
||||||
|
|
||||||
|
func (a *face) index(r rune) Index {
|
||||||
|
const mask = indexCacheLen - 1
|
||||||
|
c := &a.indexCache[r&mask]
|
||||||
|
if c.rune == r {
|
||||||
|
return c.index
|
||||||
|
}
|
||||||
|
i := a.f.Index(r)
|
||||||
|
c.rune = r
|
||||||
|
c.index = i
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close satisfies the font.Face interface.
|
||||||
|
func (a *face) Close() error { return nil }
|
||||||
|
|
||||||
|
// Metrics satisfies the font.Face interface.
|
||||||
|
func (a *face) Metrics() font.Metrics {
|
||||||
|
scale := float64(a.scale)
|
||||||
|
fupe := float64(a.f.FUnitsPerEm())
|
||||||
|
return font.Metrics{
|
||||||
|
Height: a.scale,
|
||||||
|
Ascent: fixed.Int26_6(math.Ceil(scale * float64(+a.f.ascent) / fupe)),
|
||||||
|
Descent: fixed.Int26_6(math.Ceil(scale * float64(-a.f.descent) / fupe)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kern satisfies the font.Face interface.
|
||||||
|
func (a *face) Kern(r0, r1 rune) fixed.Int26_6 {
|
||||||
|
i0 := a.index(r0)
|
||||||
|
i1 := a.index(r1)
|
||||||
|
kern := a.f.Kern(a.scale, i0, i1)
|
||||||
|
if a.hinting != font.HintingNone {
|
||||||
|
kern = (kern + 32) &^ 63
|
||||||
|
}
|
||||||
|
return kern
|
||||||
|
}
|
||||||
|
|
||||||
|
// Glyph satisfies the font.Face interface.
|
||||||
|
func (a *face) Glyph(dot fixed.Point26_6, r rune) (
|
||||||
|
dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {
|
||||||
|
|
||||||
|
// Quantize to the sub-pixel granularity.
|
||||||
|
dotX := (dot.X + a.subPixelBiasX) & a.subPixelMaskX
|
||||||
|
dotY := (dot.Y + a.subPixelBiasY) & a.subPixelMaskY
|
||||||
|
|
||||||
|
// Split the coordinates into their integer and fractional parts.
|
||||||
|
ix, fx := int(dotX>>6), dotX&0x3f
|
||||||
|
iy, fy := int(dotY>>6), dotY&0x3f
|
||||||
|
|
||||||
|
index := a.index(r)
|
||||||
|
cIndex := uint32(index)
|
||||||
|
cIndex = cIndex*a.subPixelX - uint32(fx/a.subPixelMaskX)
|
||||||
|
cIndex = cIndex*a.subPixelY - uint32(fy/a.subPixelMaskY)
|
||||||
|
cIndex &= uint32(len(a.glyphCache) - 1)
|
||||||
|
a.paintOffset = a.maxh * int(cIndex)
|
||||||
|
k := glyphCacheKey{
|
||||||
|
index: index,
|
||||||
|
fx: uint8(fx),
|
||||||
|
fy: uint8(fy),
|
||||||
|
}
|
||||||
|
var v glyphCacheVal
|
||||||
|
if a.glyphCache[cIndex].key != k {
|
||||||
|
var ok bool
|
||||||
|
v, ok = a.rasterize(index, fx, fy)
|
||||||
|
if !ok {
|
||||||
|
return image.Rectangle{}, nil, image.Point{}, 0, false
|
||||||
|
}
|
||||||
|
a.glyphCache[cIndex] = glyphCacheEntry{k, v}
|
||||||
|
} else {
|
||||||
|
v = a.glyphCache[cIndex].val
|
||||||
|
}
|
||||||
|
|
||||||
|
dr.Min = image.Point{
|
||||||
|
X: ix + v.offset.X,
|
||||||
|
Y: iy + v.offset.Y,
|
||||||
|
}
|
||||||
|
dr.Max = image.Point{
|
||||||
|
X: dr.Min.X + v.gw,
|
||||||
|
Y: dr.Min.Y + v.gh,
|
||||||
|
}
|
||||||
|
return dr, a.masks, image.Point{Y: a.paintOffset}, v.advanceWidth, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *face) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
|
||||||
|
if err := a.glyphBuf.Load(a.f, a.scale, a.index(r), a.hinting); err != nil {
|
||||||
|
return fixed.Rectangle26_6{}, 0, false
|
||||||
|
}
|
||||||
|
xmin := +a.glyphBuf.Bounds.Min.X
|
||||||
|
ymin := -a.glyphBuf.Bounds.Max.Y
|
||||||
|
xmax := +a.glyphBuf.Bounds.Max.X
|
||||||
|
ymax := -a.glyphBuf.Bounds.Min.Y
|
||||||
|
if xmin > xmax || ymin > ymax {
|
||||||
|
return fixed.Rectangle26_6{}, 0, false
|
||||||
|
}
|
||||||
|
return fixed.Rectangle26_6{
|
||||||
|
Min: fixed.Point26_6{
|
||||||
|
X: xmin,
|
||||||
|
Y: ymin,
|
||||||
|
},
|
||||||
|
Max: fixed.Point26_6{
|
||||||
|
X: xmax,
|
||||||
|
Y: ymax,
|
||||||
|
},
|
||||||
|
}, a.glyphBuf.AdvanceWidth, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *face) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
|
||||||
|
if err := a.glyphBuf.Load(a.f, a.scale, a.index(r), a.hinting); err != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return a.glyphBuf.AdvanceWidth, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// rasterize returns the advance width, integer-pixel offset to render at, and
|
||||||
|
// the width and height of the given glyph at the given sub-pixel offsets.
|
||||||
|
//
|
||||||
|
// The 26.6 fixed point arguments fx and fy must be in the range [0, 1).
|
||||||
|
func (a *face) rasterize(index Index, fx, fy fixed.Int26_6) (v glyphCacheVal, ok bool) {
|
||||||
|
if err := a.glyphBuf.Load(a.f, a.scale, index, a.hinting); err != nil {
|
||||||
|
return glyphCacheVal{}, false
|
||||||
|
}
|
||||||
|
// Calculate the integer-pixel bounds for the glyph.
|
||||||
|
xmin := int(fx+a.glyphBuf.Bounds.Min.X) >> 6
|
||||||
|
ymin := int(fy-a.glyphBuf.Bounds.Max.Y) >> 6
|
||||||
|
xmax := int(fx+a.glyphBuf.Bounds.Max.X+0x3f) >> 6
|
||||||
|
ymax := int(fy-a.glyphBuf.Bounds.Min.Y+0x3f) >> 6
|
||||||
|
if xmin > xmax || ymin > ymax {
|
||||||
|
return glyphCacheVal{}, false
|
||||||
|
}
|
||||||
|
// A TrueType's glyph's nodes can have negative co-ordinates, but the
|
||||||
|
// rasterizer clips anything left of x=0 or above y=0. xmin and ymin are
|
||||||
|
// the pixel offsets, based on the font's FUnit metrics, that let a
|
||||||
|
// negative co-ordinate in TrueType space be non-negative in rasterizer
|
||||||
|
// space. xmin and ymin are typically <= 0.
|
||||||
|
fx -= fixed.Int26_6(xmin << 6)
|
||||||
|
fy -= fixed.Int26_6(ymin << 6)
|
||||||
|
// Rasterize the glyph's vectors.
|
||||||
|
a.r.Clear()
|
||||||
|
pixOffset := a.paintOffset * a.maxw
|
||||||
|
clear(a.masks.Pix[pixOffset : pixOffset+a.maxw*a.maxh])
|
||||||
|
e0 := 0
|
||||||
|
for _, e1 := range a.glyphBuf.Ends {
|
||||||
|
a.drawContour(a.glyphBuf.Points[e0:e1], fx, fy)
|
||||||
|
e0 = e1
|
||||||
|
}
|
||||||
|
a.r.Rasterize(a.p)
|
||||||
|
return glyphCacheVal{
|
||||||
|
a.glyphBuf.AdvanceWidth,
|
||||||
|
image.Point{xmin, ymin},
|
||||||
|
xmax - xmin,
|
||||||
|
ymax - ymin,
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func clear(pix []byte) {
|
||||||
|
for i := range pix {
|
||||||
|
pix[i] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// drawContour draws the given closed contour with the given offset.
|
||||||
|
func (a *face) drawContour(ps []Point, dx, dy fixed.Int26_6) {
|
||||||
|
if len(ps) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The low bit of each point's Flags value is whether the point is on the
|
||||||
|
// curve. Truetype fonts only have quadratic Bézier curves, not cubics.
|
||||||
|
// Thus, two consecutive off-curve points imply an on-curve point in the
|
||||||
|
// middle of those two.
|
||||||
|
//
|
||||||
|
// See http://chanae.walon.org/pub/ttf/ttf_glyphs.htm for more details.
|
||||||
|
|
||||||
|
// ps[0] is a truetype.Point measured in FUnits and positive Y going
|
||||||
|
// upwards. start is the same thing measured in fixed point units and
|
||||||
|
// positive Y going downwards, and offset by (dx, dy).
|
||||||
|
start := fixed.Point26_6{
|
||||||
|
X: dx + ps[0].X,
|
||||||
|
Y: dy - ps[0].Y,
|
||||||
|
}
|
||||||
|
var others []Point
|
||||||
|
if ps[0].Flags&0x01 != 0 {
|
||||||
|
others = ps[1:]
|
||||||
|
} else {
|
||||||
|
last := fixed.Point26_6{
|
||||||
|
X: dx + ps[len(ps)-1].X,
|
||||||
|
Y: dy - ps[len(ps)-1].Y,
|
||||||
|
}
|
||||||
|
if ps[len(ps)-1].Flags&0x01 != 0 {
|
||||||
|
start = last
|
||||||
|
others = ps[:len(ps)-1]
|
||||||
|
} else {
|
||||||
|
start = fixed.Point26_6{
|
||||||
|
X: (start.X + last.X) / 2,
|
||||||
|
Y: (start.Y + last.Y) / 2,
|
||||||
|
}
|
||||||
|
others = ps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.r.Start(start)
|
||||||
|
q0, on0 := start, true
|
||||||
|
for _, p := range others {
|
||||||
|
q := fixed.Point26_6{
|
||||||
|
X: dx + p.X,
|
||||||
|
Y: dy - p.Y,
|
||||||
|
}
|
||||||
|
on := p.Flags&0x01 != 0
|
||||||
|
if on {
|
||||||
|
if on0 {
|
||||||
|
a.r.Add1(q)
|
||||||
|
} else {
|
||||||
|
a.r.Add2(q0, q)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if on0 {
|
||||||
|
// No-op.
|
||||||
|
} else {
|
||||||
|
mid := fixed.Point26_6{
|
||||||
|
X: (q0.X + q.X) / 2,
|
||||||
|
Y: (q0.Y + q.Y) / 2,
|
||||||
|
}
|
||||||
|
a.r.Add2(q0, mid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
q0, on0 = q, on
|
||||||
|
}
|
||||||
|
// Close the curve.
|
||||||
|
if on0 {
|
||||||
|
a.r.Add1(start)
|
||||||
|
} else {
|
||||||
|
a.r.Add2(q0, start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// facePainter is like a raster.AlphaSrcPainter, with an additional Y offset
|
||||||
|
// (face.paintOffset) to the painted spans.
|
||||||
|
type facePainter struct {
|
||||||
|
a *face
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p facePainter) Paint(ss []raster.Span, done bool) {
|
||||||
|
m := p.a.masks
|
||||||
|
b := m.Bounds()
|
||||||
|
b.Min.Y = p.a.paintOffset
|
||||||
|
b.Max.Y = p.a.paintOffset + p.a.maxh
|
||||||
|
for _, s := range ss {
|
||||||
|
s.Y += p.a.paintOffset
|
||||||
|
if s.Y < b.Min.Y {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if s.Y >= b.Max.Y {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.X0 < b.Min.X {
|
||||||
|
s.X0 = b.Min.X
|
||||||
|
}
|
||||||
|
if s.X1 > b.Max.X {
|
||||||
|
s.X1 = b.Max.X
|
||||||
|
}
|
||||||
|
if s.X0 >= s.X1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
base := (s.Y-m.Rect.Min.Y)*m.Stride - m.Rect.Min.X
|
||||||
|
p := m.Pix[base+s.X0 : base+s.X1]
|
||||||
|
color := uint8(s.Alpha >> 8)
|
||||||
|
for i := range p {
|
||||||
|
p[i] = color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
517
vendor/github.com/golang/freetype/truetype/glyph.go
generated
vendored
Normal file
517
vendor/github.com/golang/freetype/truetype/glyph.go
generated
vendored
Normal file
|
@ -0,0 +1,517 @@
|
||||||
|
// Copyright 2010 The Freetype-Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by your choice of either the
|
||||||
|
// FreeType License or the GNU General Public License version 2 (or
|
||||||
|
// any later version), both of which can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package truetype
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/image/font"
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: implement VerticalHinting.
|
||||||
|
|
||||||
|
// A Point is a co-ordinate pair plus whether it is 'on' a contour or an 'off'
|
||||||
|
// control point.
|
||||||
|
type Point struct {
|
||||||
|
X, Y fixed.Int26_6
|
||||||
|
// The Flags' LSB means whether or not this Point is 'on' the contour.
|
||||||
|
// Other bits are reserved for internal use.
|
||||||
|
Flags uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// A GlyphBuf holds a glyph's contours. A GlyphBuf can be re-used to load a
|
||||||
|
// series of glyphs from a Font.
|
||||||
|
type GlyphBuf struct {
|
||||||
|
// AdvanceWidth is the glyph's advance width.
|
||||||
|
AdvanceWidth fixed.Int26_6
|
||||||
|
// Bounds is the glyph's bounding box.
|
||||||
|
Bounds fixed.Rectangle26_6
|
||||||
|
// Points contains all Points from all contours of the glyph. If hinting
|
||||||
|
// was used to load a glyph then Unhinted contains those Points before they
|
||||||
|
// were hinted, and InFontUnits contains those Points before they were
|
||||||
|
// hinted and scaled.
|
||||||
|
Points, Unhinted, InFontUnits []Point
|
||||||
|
// Ends is the point indexes of the end point of each contour. The length
|
||||||
|
// of Ends is the number of contours in the glyph. The i'th contour
|
||||||
|
// consists of points Points[Ends[i-1]:Ends[i]], where Ends[-1] is
|
||||||
|
// interpreted to mean zero.
|
||||||
|
Ends []int
|
||||||
|
|
||||||
|
font *Font
|
||||||
|
scale fixed.Int26_6
|
||||||
|
hinting font.Hinting
|
||||||
|
hinter hinter
|
||||||
|
// phantomPoints are the co-ordinates of the synthetic phantom points
|
||||||
|
// used for hinting and bounding box calculations.
|
||||||
|
phantomPoints [4]Point
|
||||||
|
// pp1x is the X co-ordinate of the first phantom point. The '1' is
|
||||||
|
// using 1-based indexing; pp1x is almost always phantomPoints[0].X.
|
||||||
|
// TODO: eliminate this and consistently use phantomPoints[0].X.
|
||||||
|
pp1x fixed.Int26_6
|
||||||
|
// metricsSet is whether the glyph's metrics have been set yet. For a
|
||||||
|
// compound glyph, a sub-glyph may override the outer glyph's metrics.
|
||||||
|
metricsSet bool
|
||||||
|
// tmp is a scratch buffer.
|
||||||
|
tmp []Point
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flags for decoding a glyph's contours. These flags are documented at
|
||||||
|
// http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html.
|
||||||
|
const (
|
||||||
|
flagOnCurve = 1 << iota
|
||||||
|
flagXShortVector
|
||||||
|
flagYShortVector
|
||||||
|
flagRepeat
|
||||||
|
flagPositiveXShortVector
|
||||||
|
flagPositiveYShortVector
|
||||||
|
|
||||||
|
// The remaining flags are for internal use.
|
||||||
|
flagTouchedX
|
||||||
|
flagTouchedY
|
||||||
|
)
|
||||||
|
|
||||||
|
// The same flag bits (0x10 and 0x20) are overloaded to have two meanings,
|
||||||
|
// dependent on the value of the flag{X,Y}ShortVector bits.
|
||||||
|
const (
|
||||||
|
flagThisXIsSame = flagPositiveXShortVector
|
||||||
|
flagThisYIsSame = flagPositiveYShortVector
|
||||||
|
)
|
||||||
|
|
||||||
|
// Load loads a glyph's contours from a Font, overwriting any previously loaded
|
||||||
|
// contours for this GlyphBuf. scale is the number of 26.6 fixed point units in
|
||||||
|
// 1 em, i is the glyph index, and h is the hinting policy.
|
||||||
|
func (g *GlyphBuf) Load(f *Font, scale fixed.Int26_6, i Index, h font.Hinting) error {
|
||||||
|
g.Points = g.Points[:0]
|
||||||
|
g.Unhinted = g.Unhinted[:0]
|
||||||
|
g.InFontUnits = g.InFontUnits[:0]
|
||||||
|
g.Ends = g.Ends[:0]
|
||||||
|
g.font = f
|
||||||
|
g.hinting = h
|
||||||
|
g.scale = scale
|
||||||
|
g.pp1x = 0
|
||||||
|
g.phantomPoints = [4]Point{}
|
||||||
|
g.metricsSet = false
|
||||||
|
|
||||||
|
if h != font.HintingNone {
|
||||||
|
if err := g.hinter.init(f, scale); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := g.load(0, i, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// TODO: this selection of either g.pp1x or g.phantomPoints[0].X isn't ideal,
|
||||||
|
// and should be cleaned up once we have all the testScaling tests passing,
|
||||||
|
// plus additional tests for Freetype-Go's bounding boxes matching C Freetype's.
|
||||||
|
pp1x := g.pp1x
|
||||||
|
if h != font.HintingNone {
|
||||||
|
pp1x = g.phantomPoints[0].X
|
||||||
|
}
|
||||||
|
if pp1x != 0 {
|
||||||
|
for i := range g.Points {
|
||||||
|
g.Points[i].X -= pp1x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
advanceWidth := g.phantomPoints[1].X - g.phantomPoints[0].X
|
||||||
|
if h != font.HintingNone {
|
||||||
|
if len(f.hdmx) >= 8 {
|
||||||
|
if n := u32(f.hdmx, 4); n > 3+uint32(i) {
|
||||||
|
for hdmx := f.hdmx[8:]; uint32(len(hdmx)) >= n; hdmx = hdmx[n:] {
|
||||||
|
if fixed.Int26_6(hdmx[0]) == scale>>6 {
|
||||||
|
advanceWidth = fixed.Int26_6(hdmx[2+i]) << 6
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
advanceWidth = (advanceWidth + 32) &^ 63
|
||||||
|
}
|
||||||
|
g.AdvanceWidth = advanceWidth
|
||||||
|
|
||||||
|
// Set g.Bounds to the 'control box', which is the bounding box of the
|
||||||
|
// Bézier curves' control points. This is easier to calculate, no smaller
|
||||||
|
// than and often equal to the tightest possible bounding box of the curves
|
||||||
|
// themselves. This approach is what C Freetype does. We can't just scale
|
||||||
|
// the nominal bounding box in the glyf data as the hinting process and
|
||||||
|
// phantom point adjustment may move points outside of that box.
|
||||||
|
if len(g.Points) == 0 {
|
||||||
|
g.Bounds = fixed.Rectangle26_6{}
|
||||||
|
} else {
|
||||||
|
p := g.Points[0]
|
||||||
|
g.Bounds.Min.X = p.X
|
||||||
|
g.Bounds.Max.X = p.X
|
||||||
|
g.Bounds.Min.Y = p.Y
|
||||||
|
g.Bounds.Max.Y = p.Y
|
||||||
|
for _, p := range g.Points[1:] {
|
||||||
|
if g.Bounds.Min.X > p.X {
|
||||||
|
g.Bounds.Min.X = p.X
|
||||||
|
} else if g.Bounds.Max.X < p.X {
|
||||||
|
g.Bounds.Max.X = p.X
|
||||||
|
}
|
||||||
|
if g.Bounds.Min.Y > p.Y {
|
||||||
|
g.Bounds.Min.Y = p.Y
|
||||||
|
} else if g.Bounds.Max.Y < p.Y {
|
||||||
|
g.Bounds.Max.Y = p.Y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Snap the box to the grid, if hinting is on.
|
||||||
|
if h != font.HintingNone {
|
||||||
|
g.Bounds.Min.X &^= 63
|
||||||
|
g.Bounds.Min.Y &^= 63
|
||||||
|
g.Bounds.Max.X += 63
|
||||||
|
g.Bounds.Max.X &^= 63
|
||||||
|
g.Bounds.Max.Y += 63
|
||||||
|
g.Bounds.Max.Y &^= 63
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GlyphBuf) load(recursion uint32, i Index, useMyMetrics bool) (err error) {
|
||||||
|
// The recursion limit here is arbitrary, but defends against malformed glyphs.
|
||||||
|
if recursion >= 32 {
|
||||||
|
return UnsupportedError("excessive compound glyph recursion")
|
||||||
|
}
|
||||||
|
// Find the relevant slice of g.font.glyf.
|
||||||
|
var g0, g1 uint32
|
||||||
|
if g.font.locaOffsetFormat == locaOffsetFormatShort {
|
||||||
|
g0 = 2 * uint32(u16(g.font.loca, 2*int(i)))
|
||||||
|
g1 = 2 * uint32(u16(g.font.loca, 2*int(i)+2))
|
||||||
|
} else {
|
||||||
|
g0 = u32(g.font.loca, 4*int(i))
|
||||||
|
g1 = u32(g.font.loca, 4*int(i)+4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the contour count and nominal bounding box, from the first
|
||||||
|
// 10 bytes of the glyf data. boundsYMin and boundsXMax, at offsets 4
|
||||||
|
// and 6, are unused.
|
||||||
|
glyf, ne, boundsXMin, boundsYMax := []byte(nil), 0, fixed.Int26_6(0), fixed.Int26_6(0)
|
||||||
|
if g0+10 <= g1 {
|
||||||
|
glyf = g.font.glyf[g0:g1]
|
||||||
|
ne = int(int16(u16(glyf, 0)))
|
||||||
|
boundsXMin = fixed.Int26_6(int16(u16(glyf, 2)))
|
||||||
|
boundsYMax = fixed.Int26_6(int16(u16(glyf, 8)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the phantom points.
|
||||||
|
uhm, pp1x := g.font.unscaledHMetric(i), fixed.Int26_6(0)
|
||||||
|
uvm := g.font.unscaledVMetric(i, boundsYMax)
|
||||||
|
g.phantomPoints = [4]Point{
|
||||||
|
{X: boundsXMin - uhm.LeftSideBearing},
|
||||||
|
{X: boundsXMin - uhm.LeftSideBearing + uhm.AdvanceWidth},
|
||||||
|
{X: uhm.AdvanceWidth / 2, Y: boundsYMax + uvm.TopSideBearing},
|
||||||
|
{X: uhm.AdvanceWidth / 2, Y: boundsYMax + uvm.TopSideBearing - uvm.AdvanceHeight},
|
||||||
|
}
|
||||||
|
if len(glyf) == 0 {
|
||||||
|
g.addPhantomsAndScale(len(g.Points), len(g.Points), true, true)
|
||||||
|
copy(g.phantomPoints[:], g.Points[len(g.Points)-4:])
|
||||||
|
g.Points = g.Points[:len(g.Points)-4]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load and hint the contours.
|
||||||
|
if ne < 0 {
|
||||||
|
if ne != -1 {
|
||||||
|
// http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html says that
|
||||||
|
// "the values -2, -3, and so forth, are reserved for future use."
|
||||||
|
return UnsupportedError("negative number of contours")
|
||||||
|
}
|
||||||
|
pp1x = g.font.scale(g.scale * (boundsXMin - uhm.LeftSideBearing))
|
||||||
|
if err := g.loadCompound(recursion, uhm, i, glyf, useMyMetrics); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
np0, ne0 := len(g.Points), len(g.Ends)
|
||||||
|
program := g.loadSimple(glyf, ne)
|
||||||
|
g.addPhantomsAndScale(np0, np0, true, true)
|
||||||
|
pp1x = g.Points[len(g.Points)-4].X
|
||||||
|
if g.hinting != font.HintingNone {
|
||||||
|
if len(program) != 0 {
|
||||||
|
err := g.hinter.run(
|
||||||
|
program,
|
||||||
|
g.Points[np0:],
|
||||||
|
g.Unhinted[np0:],
|
||||||
|
g.InFontUnits[np0:],
|
||||||
|
g.Ends[ne0:],
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Drop the four phantom points.
|
||||||
|
g.InFontUnits = g.InFontUnits[:len(g.InFontUnits)-4]
|
||||||
|
g.Unhinted = g.Unhinted[:len(g.Unhinted)-4]
|
||||||
|
}
|
||||||
|
if useMyMetrics {
|
||||||
|
copy(g.phantomPoints[:], g.Points[len(g.Points)-4:])
|
||||||
|
}
|
||||||
|
g.Points = g.Points[:len(g.Points)-4]
|
||||||
|
if np0 != 0 {
|
||||||
|
// The hinting program expects the []Ends values to be indexed
|
||||||
|
// relative to the inner glyph, not the outer glyph, so we delay
|
||||||
|
// adding np0 until after the hinting program (if any) has run.
|
||||||
|
for i := ne0; i < len(g.Ends); i++ {
|
||||||
|
g.Ends[i] += np0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if useMyMetrics && !g.metricsSet {
|
||||||
|
g.metricsSet = true
|
||||||
|
g.pp1x = pp1x
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadOffset is the initial offset for loadSimple and loadCompound. The first
|
||||||
|
// 10 bytes are the number of contours and the bounding box.
|
||||||
|
const loadOffset = 10
|
||||||
|
|
||||||
|
func (g *GlyphBuf) loadSimple(glyf []byte, ne int) (program []byte) {
|
||||||
|
offset := loadOffset
|
||||||
|
for i := 0; i < ne; i++ {
|
||||||
|
g.Ends = append(g.Ends, 1+int(u16(glyf, offset)))
|
||||||
|
offset += 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note the TrueType hinting instructions.
|
||||||
|
instrLen := int(u16(glyf, offset))
|
||||||
|
offset += 2
|
||||||
|
program = glyf[offset : offset+instrLen]
|
||||||
|
offset += instrLen
|
||||||
|
|
||||||
|
np0 := len(g.Points)
|
||||||
|
np1 := np0 + int(g.Ends[len(g.Ends)-1])
|
||||||
|
|
||||||
|
// Decode the flags.
|
||||||
|
for i := np0; i < np1; {
|
||||||
|
c := uint32(glyf[offset])
|
||||||
|
offset++
|
||||||
|
g.Points = append(g.Points, Point{Flags: c})
|
||||||
|
i++
|
||||||
|
if c&flagRepeat != 0 {
|
||||||
|
count := glyf[offset]
|
||||||
|
offset++
|
||||||
|
for ; count > 0; count-- {
|
||||||
|
g.Points = append(g.Points, Point{Flags: c})
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the co-ordinates.
|
||||||
|
var x int16
|
||||||
|
for i := np0; i < np1; i++ {
|
||||||
|
f := g.Points[i].Flags
|
||||||
|
if f&flagXShortVector != 0 {
|
||||||
|
dx := int16(glyf[offset])
|
||||||
|
offset++
|
||||||
|
if f&flagPositiveXShortVector == 0 {
|
||||||
|
x -= dx
|
||||||
|
} else {
|
||||||
|
x += dx
|
||||||
|
}
|
||||||
|
} else if f&flagThisXIsSame == 0 {
|
||||||
|
x += int16(u16(glyf, offset))
|
||||||
|
offset += 2
|
||||||
|
}
|
||||||
|
g.Points[i].X = fixed.Int26_6(x)
|
||||||
|
}
|
||||||
|
var y int16
|
||||||
|
for i := np0; i < np1; i++ {
|
||||||
|
f := g.Points[i].Flags
|
||||||
|
if f&flagYShortVector != 0 {
|
||||||
|
dy := int16(glyf[offset])
|
||||||
|
offset++
|
||||||
|
if f&flagPositiveYShortVector == 0 {
|
||||||
|
y -= dy
|
||||||
|
} else {
|
||||||
|
y += dy
|
||||||
|
}
|
||||||
|
} else if f&flagThisYIsSame == 0 {
|
||||||
|
y += int16(u16(glyf, offset))
|
||||||
|
offset += 2
|
||||||
|
}
|
||||||
|
g.Points[i].Y = fixed.Int26_6(y)
|
||||||
|
}
|
||||||
|
|
||||||
|
return program
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GlyphBuf) loadCompound(recursion uint32, uhm HMetric, i Index,
|
||||||
|
glyf []byte, useMyMetrics bool) error {
|
||||||
|
|
||||||
|
// Flags for decoding a compound glyph. These flags are documented at
|
||||||
|
// http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html.
|
||||||
|
const (
|
||||||
|
flagArg1And2AreWords = 1 << iota
|
||||||
|
flagArgsAreXYValues
|
||||||
|
flagRoundXYToGrid
|
||||||
|
flagWeHaveAScale
|
||||||
|
flagUnused
|
||||||
|
flagMoreComponents
|
||||||
|
flagWeHaveAnXAndYScale
|
||||||
|
flagWeHaveATwoByTwo
|
||||||
|
flagWeHaveInstructions
|
||||||
|
flagUseMyMetrics
|
||||||
|
flagOverlapCompound
|
||||||
|
)
|
||||||
|
np0, ne0 := len(g.Points), len(g.Ends)
|
||||||
|
offset := loadOffset
|
||||||
|
for {
|
||||||
|
flags := u16(glyf, offset)
|
||||||
|
component := Index(u16(glyf, offset+2))
|
||||||
|
dx, dy, transform, hasTransform := fixed.Int26_6(0), fixed.Int26_6(0), [4]int16{}, false
|
||||||
|
if flags&flagArg1And2AreWords != 0 {
|
||||||
|
dx = fixed.Int26_6(int16(u16(glyf, offset+4)))
|
||||||
|
dy = fixed.Int26_6(int16(u16(glyf, offset+6)))
|
||||||
|
offset += 8
|
||||||
|
} else {
|
||||||
|
dx = fixed.Int26_6(int16(int8(glyf[offset+4])))
|
||||||
|
dy = fixed.Int26_6(int16(int8(glyf[offset+5])))
|
||||||
|
offset += 6
|
||||||
|
}
|
||||||
|
if flags&flagArgsAreXYValues == 0 {
|
||||||
|
return UnsupportedError("compound glyph transform vector")
|
||||||
|
}
|
||||||
|
if flags&(flagWeHaveAScale|flagWeHaveAnXAndYScale|flagWeHaveATwoByTwo) != 0 {
|
||||||
|
hasTransform = true
|
||||||
|
switch {
|
||||||
|
case flags&flagWeHaveAScale != 0:
|
||||||
|
transform[0] = int16(u16(glyf, offset+0))
|
||||||
|
transform[3] = transform[0]
|
||||||
|
offset += 2
|
||||||
|
case flags&flagWeHaveAnXAndYScale != 0:
|
||||||
|
transform[0] = int16(u16(glyf, offset+0))
|
||||||
|
transform[3] = int16(u16(glyf, offset+2))
|
||||||
|
offset += 4
|
||||||
|
case flags&flagWeHaveATwoByTwo != 0:
|
||||||
|
transform[0] = int16(u16(glyf, offset+0))
|
||||||
|
transform[1] = int16(u16(glyf, offset+2))
|
||||||
|
transform[2] = int16(u16(glyf, offset+4))
|
||||||
|
transform[3] = int16(u16(glyf, offset+6))
|
||||||
|
offset += 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
savedPP := g.phantomPoints
|
||||||
|
np0 := len(g.Points)
|
||||||
|
componentUMM := useMyMetrics && (flags&flagUseMyMetrics != 0)
|
||||||
|
if err := g.load(recursion+1, component, componentUMM); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if flags&flagUseMyMetrics == 0 {
|
||||||
|
g.phantomPoints = savedPP
|
||||||
|
}
|
||||||
|
if hasTransform {
|
||||||
|
for j := np0; j < len(g.Points); j++ {
|
||||||
|
p := &g.Points[j]
|
||||||
|
newX := 0 +
|
||||||
|
fixed.Int26_6((int64(p.X)*int64(transform[0])+1<<13)>>14) +
|
||||||
|
fixed.Int26_6((int64(p.Y)*int64(transform[2])+1<<13)>>14)
|
||||||
|
newY := 0 +
|
||||||
|
fixed.Int26_6((int64(p.X)*int64(transform[1])+1<<13)>>14) +
|
||||||
|
fixed.Int26_6((int64(p.Y)*int64(transform[3])+1<<13)>>14)
|
||||||
|
p.X, p.Y = newX, newY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dx = g.font.scale(g.scale * dx)
|
||||||
|
dy = g.font.scale(g.scale * dy)
|
||||||
|
if flags&flagRoundXYToGrid != 0 {
|
||||||
|
dx = (dx + 32) &^ 63
|
||||||
|
dy = (dy + 32) &^ 63
|
||||||
|
}
|
||||||
|
for j := np0; j < len(g.Points); j++ {
|
||||||
|
p := &g.Points[j]
|
||||||
|
p.X += dx
|
||||||
|
p.Y += dy
|
||||||
|
}
|
||||||
|
// TODO: also adjust g.InFontUnits and g.Unhinted?
|
||||||
|
if flags&flagMoreComponents == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instrLen := 0
|
||||||
|
if g.hinting != font.HintingNone && offset+2 <= len(glyf) {
|
||||||
|
instrLen = int(u16(glyf, offset))
|
||||||
|
offset += 2
|
||||||
|
}
|
||||||
|
|
||||||
|
g.addPhantomsAndScale(np0, len(g.Points), false, instrLen > 0)
|
||||||
|
points, ends := g.Points[np0:], g.Ends[ne0:]
|
||||||
|
g.Points = g.Points[:len(g.Points)-4]
|
||||||
|
for j := range points {
|
||||||
|
points[j].Flags &^= flagTouchedX | flagTouchedY
|
||||||
|
}
|
||||||
|
|
||||||
|
if instrLen == 0 {
|
||||||
|
if !g.metricsSet {
|
||||||
|
copy(g.phantomPoints[:], points[len(points)-4:])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hint the compound glyph.
|
||||||
|
program := glyf[offset : offset+instrLen]
|
||||||
|
// Temporarily adjust the ends to be relative to this compound glyph.
|
||||||
|
if np0 != 0 {
|
||||||
|
for i := range ends {
|
||||||
|
ends[i] -= np0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Hinting instructions of a composite glyph completely refer to the
|
||||||
|
// (already) hinted subglyphs.
|
||||||
|
g.tmp = append(g.tmp[:0], points...)
|
||||||
|
if err := g.hinter.run(program, points, g.tmp, g.tmp, ends); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if np0 != 0 {
|
||||||
|
for i := range ends {
|
||||||
|
ends[i] += np0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !g.metricsSet {
|
||||||
|
copy(g.phantomPoints[:], points[len(points)-4:])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GlyphBuf) addPhantomsAndScale(np0, np1 int, simple, adjust bool) {
|
||||||
|
// Add the four phantom points.
|
||||||
|
g.Points = append(g.Points, g.phantomPoints[:]...)
|
||||||
|
// Scale the points.
|
||||||
|
if simple && g.hinting != font.HintingNone {
|
||||||
|
g.InFontUnits = append(g.InFontUnits, g.Points[np1:]...)
|
||||||
|
}
|
||||||
|
for i := np1; i < len(g.Points); i++ {
|
||||||
|
p := &g.Points[i]
|
||||||
|
p.X = g.font.scale(g.scale * p.X)
|
||||||
|
p.Y = g.font.scale(g.scale * p.Y)
|
||||||
|
}
|
||||||
|
if g.hinting == font.HintingNone {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Round the 1st phantom point to the grid, shifting all other points equally.
|
||||||
|
// Note that "all other points" starts from np0, not np1.
|
||||||
|
// TODO: delete this adjustment and the np0/np1 distinction, when
|
||||||
|
// we update the compatibility tests to C Freetype 2.5.3.
|
||||||
|
// See http://git.savannah.gnu.org/cgit/freetype/freetype2.git/commit/?id=05c786d990390a7ca18e62962641dac740bacb06
|
||||||
|
if adjust {
|
||||||
|
pp1x := g.Points[len(g.Points)-4].X
|
||||||
|
if dx := ((pp1x + 32) &^ 63) - pp1x; dx != 0 {
|
||||||
|
for i := np0; i < len(g.Points); i++ {
|
||||||
|
g.Points[i].X += dx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if simple {
|
||||||
|
g.Unhinted = append(g.Unhinted, g.Points[np1:]...)
|
||||||
|
}
|
||||||
|
// Round the 2nd and 4th phantom point to the grid.
|
||||||
|
p := &g.Points[len(g.Points)-3]
|
||||||
|
p.X = (p.X + 32) &^ 63
|
||||||
|
p = &g.Points[len(g.Points)-1]
|
||||||
|
p.Y = (p.Y + 32) &^ 63
|
||||||
|
}
|
1770
vendor/github.com/golang/freetype/truetype/hint.go
generated
vendored
Normal file
1770
vendor/github.com/golang/freetype/truetype/hint.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
289
vendor/github.com/golang/freetype/truetype/opcodes.go
generated
vendored
Normal file
289
vendor/github.com/golang/freetype/truetype/opcodes.go
generated
vendored
Normal file
|
@ -0,0 +1,289 @@
|
||||||
|
// Copyright 2012 The Freetype-Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by your choice of either the
|
||||||
|
// FreeType License or the GNU General Public License version 2 (or
|
||||||
|
// any later version), both of which can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package truetype
|
||||||
|
|
||||||
|
// The Truetype opcodes are summarized at
|
||||||
|
// https://developer.apple.com/fonts/TTRefMan/RM07/appendixA.html
|
||||||
|
|
||||||
|
const (
|
||||||
|
opSVTCA0 = 0x00 // Set freedom and projection Vectors To Coordinate Axis
|
||||||
|
opSVTCA1 = 0x01 // .
|
||||||
|
opSPVTCA0 = 0x02 // Set Projection Vector To Coordinate Axis
|
||||||
|
opSPVTCA1 = 0x03 // .
|
||||||
|
opSFVTCA0 = 0x04 // Set Freedom Vector to Coordinate Axis
|
||||||
|
opSFVTCA1 = 0x05 // .
|
||||||
|
opSPVTL0 = 0x06 // Set Projection Vector To Line
|
||||||
|
opSPVTL1 = 0x07 // .
|
||||||
|
opSFVTL0 = 0x08 // Set Freedom Vector To Line
|
||||||
|
opSFVTL1 = 0x09 // .
|
||||||
|
opSPVFS = 0x0a // Set Projection Vector From Stack
|
||||||
|
opSFVFS = 0x0b // Set Freedom Vector From Stack
|
||||||
|
opGPV = 0x0c // Get Projection Vector
|
||||||
|
opGFV = 0x0d // Get Freedom Vector
|
||||||
|
opSFVTPV = 0x0e // Set Freedom Vector To Projection Vector
|
||||||
|
opISECT = 0x0f // moves point p to the InterSECTion of two lines
|
||||||
|
opSRP0 = 0x10 // Set Reference Point 0
|
||||||
|
opSRP1 = 0x11 // Set Reference Point 1
|
||||||
|
opSRP2 = 0x12 // Set Reference Point 2
|
||||||
|
opSZP0 = 0x13 // Set Zone Pointer 0
|
||||||
|
opSZP1 = 0x14 // Set Zone Pointer 1
|
||||||
|
opSZP2 = 0x15 // Set Zone Pointer 2
|
||||||
|
opSZPS = 0x16 // Set Zone PointerS
|
||||||
|
opSLOOP = 0x17 // Set LOOP variable
|
||||||
|
opRTG = 0x18 // Round To Grid
|
||||||
|
opRTHG = 0x19 // Round To Half Grid
|
||||||
|
opSMD = 0x1a // Set Minimum Distance
|
||||||
|
opELSE = 0x1b // ELSE clause
|
||||||
|
opJMPR = 0x1c // JuMP Relative
|
||||||
|
opSCVTCI = 0x1d // Set Control Value Table Cut-In
|
||||||
|
opSSWCI = 0x1e // Set Single Width Cut-In
|
||||||
|
opSSW = 0x1f // Set Single Width
|
||||||
|
opDUP = 0x20 // DUPlicate top stack element
|
||||||
|
opPOP = 0x21 // POP top stack element
|
||||||
|
opCLEAR = 0x22 // CLEAR the stack
|
||||||
|
opSWAP = 0x23 // SWAP the top two elements on the stack
|
||||||
|
opDEPTH = 0x24 // DEPTH of the stack
|
||||||
|
opCINDEX = 0x25 // Copy the INDEXed element to the top of the stack
|
||||||
|
opMINDEX = 0x26 // Move the INDEXed element to the top of the stack
|
||||||
|
opALIGNPTS = 0x27 // ALIGN PoinTS
|
||||||
|
op_0x28 = 0x28 // deprecated
|
||||||
|
opUTP = 0x29 // UnTouch Point
|
||||||
|
opLOOPCALL = 0x2a // LOOP and CALL function
|
||||||
|
opCALL = 0x2b // CALL function
|
||||||
|
opFDEF = 0x2c // Function DEFinition
|
||||||
|
opENDF = 0x2d // END Function definition
|
||||||
|
opMDAP0 = 0x2e // Move Direct Absolute Point
|
||||||
|
opMDAP1 = 0x2f // .
|
||||||
|
opIUP0 = 0x30 // Interpolate Untouched Points through the outline
|
||||||
|
opIUP1 = 0x31 // .
|
||||||
|
opSHP0 = 0x32 // SHift Point using reference point
|
||||||
|
opSHP1 = 0x33 // .
|
||||||
|
opSHC0 = 0x34 // SHift Contour using reference point
|
||||||
|
opSHC1 = 0x35 // .
|
||||||
|
opSHZ0 = 0x36 // SHift Zone using reference point
|
||||||
|
opSHZ1 = 0x37 // .
|
||||||
|
opSHPIX = 0x38 // SHift point by a PIXel amount
|
||||||
|
opIP = 0x39 // Interpolate Point
|
||||||
|
opMSIRP0 = 0x3a // Move Stack Indirect Relative Point
|
||||||
|
opMSIRP1 = 0x3b // .
|
||||||
|
opALIGNRP = 0x3c // ALIGN to Reference Point
|
||||||
|
opRTDG = 0x3d // Round To Double Grid
|
||||||
|
opMIAP0 = 0x3e // Move Indirect Absolute Point
|
||||||
|
opMIAP1 = 0x3f // .
|
||||||
|
opNPUSHB = 0x40 // PUSH N Bytes
|
||||||
|
opNPUSHW = 0x41 // PUSH N Words
|
||||||
|
opWS = 0x42 // Write Store
|
||||||
|
opRS = 0x43 // Read Store
|
||||||
|
opWCVTP = 0x44 // Write Control Value Table in Pixel units
|
||||||
|
opRCVT = 0x45 // Read Control Value Table entry
|
||||||
|
opGC0 = 0x46 // Get Coordinate projected onto the projection vector
|
||||||
|
opGC1 = 0x47 // .
|
||||||
|
opSCFS = 0x48 // Sets Coordinate From the Stack using projection vector and freedom vector
|
||||||
|
opMD0 = 0x49 // Measure Distance
|
||||||
|
opMD1 = 0x4a // .
|
||||||
|
opMPPEM = 0x4b // Measure Pixels Per EM
|
||||||
|
opMPS = 0x4c // Measure Point Size
|
||||||
|
opFLIPON = 0x4d // set the auto FLIP Boolean to ON
|
||||||
|
opFLIPOFF = 0x4e // set the auto FLIP Boolean to OFF
|
||||||
|
opDEBUG = 0x4f // DEBUG call
|
||||||
|
opLT = 0x50 // Less Than
|
||||||
|
opLTEQ = 0x51 // Less Than or EQual
|
||||||
|
opGT = 0x52 // Greater Than
|
||||||
|
opGTEQ = 0x53 // Greater Than or EQual
|
||||||
|
opEQ = 0x54 // EQual
|
||||||
|
opNEQ = 0x55 // Not EQual
|
||||||
|
opODD = 0x56 // ODD
|
||||||
|
opEVEN = 0x57 // EVEN
|
||||||
|
opIF = 0x58 // IF test
|
||||||
|
opEIF = 0x59 // End IF
|
||||||
|
opAND = 0x5a // logical AND
|
||||||
|
opOR = 0x5b // logical OR
|
||||||
|
opNOT = 0x5c // logical NOT
|
||||||
|
opDELTAP1 = 0x5d // DELTA exception P1
|
||||||
|
opSDB = 0x5e // Set Delta Base in the graphics state
|
||||||
|
opSDS = 0x5f // Set Delta Shift in the graphics state
|
||||||
|
opADD = 0x60 // ADD
|
||||||
|
opSUB = 0x61 // SUBtract
|
||||||
|
opDIV = 0x62 // DIVide
|
||||||
|
opMUL = 0x63 // MULtiply
|
||||||
|
opABS = 0x64 // ABSolute value
|
||||||
|
opNEG = 0x65 // NEGate
|
||||||
|
opFLOOR = 0x66 // FLOOR
|
||||||
|
opCEILING = 0x67 // CEILING
|
||||||
|
opROUND00 = 0x68 // ROUND value
|
||||||
|
opROUND01 = 0x69 // .
|
||||||
|
opROUND10 = 0x6a // .
|
||||||
|
opROUND11 = 0x6b // .
|
||||||
|
opNROUND00 = 0x6c // No ROUNDing of value
|
||||||
|
opNROUND01 = 0x6d // .
|
||||||
|
opNROUND10 = 0x6e // .
|
||||||
|
opNROUND11 = 0x6f // .
|
||||||
|
opWCVTF = 0x70 // Write Control Value Table in Funits
|
||||||
|
opDELTAP2 = 0x71 // DELTA exception P2
|
||||||
|
opDELTAP3 = 0x72 // DELTA exception P3
|
||||||
|
opDELTAC1 = 0x73 // DELTA exception C1
|
||||||
|
opDELTAC2 = 0x74 // DELTA exception C2
|
||||||
|
opDELTAC3 = 0x75 // DELTA exception C3
|
||||||
|
opSROUND = 0x76 // Super ROUND
|
||||||
|
opS45ROUND = 0x77 // Super ROUND 45 degrees
|
||||||
|
opJROT = 0x78 // Jump Relative On True
|
||||||
|
opJROF = 0x79 // Jump Relative On False
|
||||||
|
opROFF = 0x7a // Round OFF
|
||||||
|
op_0x7b = 0x7b // deprecated
|
||||||
|
opRUTG = 0x7c // Round Up To Grid
|
||||||
|
opRDTG = 0x7d // Round Down To Grid
|
||||||
|
opSANGW = 0x7e // Set ANGle Weight
|
||||||
|
opAA = 0x7f // Adjust Angle
|
||||||
|
opFLIPPT = 0x80 // FLIP PoinT
|
||||||
|
opFLIPRGON = 0x81 // FLIP RanGe ON
|
||||||
|
opFLIPRGOFF = 0x82 // FLIP RanGe OFF
|
||||||
|
op_0x83 = 0x83 // deprecated
|
||||||
|
op_0x84 = 0x84 // deprecated
|
||||||
|
opSCANCTRL = 0x85 // SCAN conversion ConTRoL
|
||||||
|
opSDPVTL0 = 0x86 // Set Dual Projection Vector To Line
|
||||||
|
opSDPVTL1 = 0x87 // .
|
||||||
|
opGETINFO = 0x88 // GET INFOrmation
|
||||||
|
opIDEF = 0x89 // Instruction DEFinition
|
||||||
|
opROLL = 0x8a // ROLL the top three stack elements
|
||||||
|
opMAX = 0x8b // MAXimum of top two stack elements
|
||||||
|
opMIN = 0x8c // MINimum of top two stack elements
|
||||||
|
opSCANTYPE = 0x8d // SCANTYPE
|
||||||
|
opINSTCTRL = 0x8e // INSTRuction execution ConTRoL
|
||||||
|
op_0x8f = 0x8f
|
||||||
|
op_0x90 = 0x90
|
||||||
|
op_0x91 = 0x91
|
||||||
|
op_0x92 = 0x92
|
||||||
|
op_0x93 = 0x93
|
||||||
|
op_0x94 = 0x94
|
||||||
|
op_0x95 = 0x95
|
||||||
|
op_0x96 = 0x96
|
||||||
|
op_0x97 = 0x97
|
||||||
|
op_0x98 = 0x98
|
||||||
|
op_0x99 = 0x99
|
||||||
|
op_0x9a = 0x9a
|
||||||
|
op_0x9b = 0x9b
|
||||||
|
op_0x9c = 0x9c
|
||||||
|
op_0x9d = 0x9d
|
||||||
|
op_0x9e = 0x9e
|
||||||
|
op_0x9f = 0x9f
|
||||||
|
op_0xa0 = 0xa0
|
||||||
|
op_0xa1 = 0xa1
|
||||||
|
op_0xa2 = 0xa2
|
||||||
|
op_0xa3 = 0xa3
|
||||||
|
op_0xa4 = 0xa4
|
||||||
|
op_0xa5 = 0xa5
|
||||||
|
op_0xa6 = 0xa6
|
||||||
|
op_0xa7 = 0xa7
|
||||||
|
op_0xa8 = 0xa8
|
||||||
|
op_0xa9 = 0xa9
|
||||||
|
op_0xaa = 0xaa
|
||||||
|
op_0xab = 0xab
|
||||||
|
op_0xac = 0xac
|
||||||
|
op_0xad = 0xad
|
||||||
|
op_0xae = 0xae
|
||||||
|
op_0xaf = 0xaf
|
||||||
|
opPUSHB000 = 0xb0 // PUSH Bytes
|
||||||
|
opPUSHB001 = 0xb1 // .
|
||||||
|
opPUSHB010 = 0xb2 // .
|
||||||
|
opPUSHB011 = 0xb3 // .
|
||||||
|
opPUSHB100 = 0xb4 // .
|
||||||
|
opPUSHB101 = 0xb5 // .
|
||||||
|
opPUSHB110 = 0xb6 // .
|
||||||
|
opPUSHB111 = 0xb7 // .
|
||||||
|
opPUSHW000 = 0xb8 // PUSH Words
|
||||||
|
opPUSHW001 = 0xb9 // .
|
||||||
|
opPUSHW010 = 0xba // .
|
||||||
|
opPUSHW011 = 0xbb // .
|
||||||
|
opPUSHW100 = 0xbc // .
|
||||||
|
opPUSHW101 = 0xbd // .
|
||||||
|
opPUSHW110 = 0xbe // .
|
||||||
|
opPUSHW111 = 0xbf // .
|
||||||
|
opMDRP00000 = 0xc0 // Move Direct Relative Point
|
||||||
|
opMDRP00001 = 0xc1 // .
|
||||||
|
opMDRP00010 = 0xc2 // .
|
||||||
|
opMDRP00011 = 0xc3 // .
|
||||||
|
opMDRP00100 = 0xc4 // .
|
||||||
|
opMDRP00101 = 0xc5 // .
|
||||||
|
opMDRP00110 = 0xc6 // .
|
||||||
|
opMDRP00111 = 0xc7 // .
|
||||||
|
opMDRP01000 = 0xc8 // .
|
||||||
|
opMDRP01001 = 0xc9 // .
|
||||||
|
opMDRP01010 = 0xca // .
|
||||||
|
opMDRP01011 = 0xcb // .
|
||||||
|
opMDRP01100 = 0xcc // .
|
||||||
|
opMDRP01101 = 0xcd // .
|
||||||
|
opMDRP01110 = 0xce // .
|
||||||
|
opMDRP01111 = 0xcf // .
|
||||||
|
opMDRP10000 = 0xd0 // .
|
||||||
|
opMDRP10001 = 0xd1 // .
|
||||||
|
opMDRP10010 = 0xd2 // .
|
||||||
|
opMDRP10011 = 0xd3 // .
|
||||||
|
opMDRP10100 = 0xd4 // .
|
||||||
|
opMDRP10101 = 0xd5 // .
|
||||||
|
opMDRP10110 = 0xd6 // .
|
||||||
|
opMDRP10111 = 0xd7 // .
|
||||||
|
opMDRP11000 = 0xd8 // .
|
||||||
|
opMDRP11001 = 0xd9 // .
|
||||||
|
opMDRP11010 = 0xda // .
|
||||||
|
opMDRP11011 = 0xdb // .
|
||||||
|
opMDRP11100 = 0xdc // .
|
||||||
|
opMDRP11101 = 0xdd // .
|
||||||
|
opMDRP11110 = 0xde // .
|
||||||
|
opMDRP11111 = 0xdf // .
|
||||||
|
opMIRP00000 = 0xe0 // Move Indirect Relative Point
|
||||||
|
opMIRP00001 = 0xe1 // .
|
||||||
|
opMIRP00010 = 0xe2 // .
|
||||||
|
opMIRP00011 = 0xe3 // .
|
||||||
|
opMIRP00100 = 0xe4 // .
|
||||||
|
opMIRP00101 = 0xe5 // .
|
||||||
|
opMIRP00110 = 0xe6 // .
|
||||||
|
opMIRP00111 = 0xe7 // .
|
||||||
|
opMIRP01000 = 0xe8 // .
|
||||||
|
opMIRP01001 = 0xe9 // .
|
||||||
|
opMIRP01010 = 0xea // .
|
||||||
|
opMIRP01011 = 0xeb // .
|
||||||
|
opMIRP01100 = 0xec // .
|
||||||
|
opMIRP01101 = 0xed // .
|
||||||
|
opMIRP01110 = 0xee // .
|
||||||
|
opMIRP01111 = 0xef // .
|
||||||
|
opMIRP10000 = 0xf0 // .
|
||||||
|
opMIRP10001 = 0xf1 // .
|
||||||
|
opMIRP10010 = 0xf2 // .
|
||||||
|
opMIRP10011 = 0xf3 // .
|
||||||
|
opMIRP10100 = 0xf4 // .
|
||||||
|
opMIRP10101 = 0xf5 // .
|
||||||
|
opMIRP10110 = 0xf6 // .
|
||||||
|
opMIRP10111 = 0xf7 // .
|
||||||
|
opMIRP11000 = 0xf8 // .
|
||||||
|
opMIRP11001 = 0xf9 // .
|
||||||
|
opMIRP11010 = 0xfa // .
|
||||||
|
opMIRP11011 = 0xfb // .
|
||||||
|
opMIRP11100 = 0xfc // .
|
||||||
|
opMIRP11101 = 0xfd // .
|
||||||
|
opMIRP11110 = 0xfe // .
|
||||||
|
opMIRP11111 = 0xff // .
|
||||||
|
)
|
||||||
|
|
||||||
|
// popCount is the number of stack elements that each opcode pops.
|
||||||
|
var popCount = [256]uint8{
|
||||||
|
// 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f
|
||||||
|
0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 5, // 0x00 - 0x0f
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, // 0x10 - 0x1f
|
||||||
|
1, 1, 0, 2, 0, 1, 1, 2, 0, 1, 2, 1, 1, 0, 1, 1, // 0x20 - 0x2f
|
||||||
|
0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 2, 2, 0, 0, 2, 2, // 0x30 - 0x3f
|
||||||
|
0, 0, 2, 1, 2, 1, 1, 1, 2, 2, 2, 0, 0, 0, 0, 0, // 0x40 - 0x4f
|
||||||
|
2, 2, 2, 2, 2, 2, 1, 1, 1, 0, 2, 2, 1, 1, 1, 1, // 0x50 - 0x5f
|
||||||
|
2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6f
|
||||||
|
2, 1, 1, 1, 1, 1, 1, 1, 2, 2, 0, 0, 0, 0, 1, 1, // 0x70 - 0x7f
|
||||||
|
0, 2, 2, 0, 0, 1, 2, 2, 1, 1, 3, 2, 2, 1, 2, 0, // 0x80 - 0x8f
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90 - 0x9f
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xa0 - 0xaf
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xb0 - 0xbf
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xc0 - 0xcf
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xd0 - 0xdf
|
||||||
|
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xe0 - 0xef
|
||||||
|
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xf0 - 0xff
|
||||||
|
}
|
643
vendor/github.com/golang/freetype/truetype/truetype.go
generated
vendored
Normal file
643
vendor/github.com/golang/freetype/truetype/truetype.go
generated
vendored
Normal file
|
@ -0,0 +1,643 @@
|
||||||
|
// Copyright 2010 The Freetype-Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by your choice of either the
|
||||||
|
// FreeType License or the GNU General Public License version 2 (or
|
||||||
|
// any later version), both of which can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package truetype provides a parser for the TTF and TTC file formats.
|
||||||
|
// Those formats are documented at http://developer.apple.com/fonts/TTRefMan/
|
||||||
|
// and http://www.microsoft.com/typography/otspec/
|
||||||
|
//
|
||||||
|
// Some of a font's methods provide lengths or co-ordinates, e.g. bounds, font
|
||||||
|
// metrics and control points. All these methods take a scale parameter, which
|
||||||
|
// is the number of pixels in 1 em, expressed as a 26.6 fixed point value. For
|
||||||
|
// example, if 1 em is 10 pixels then scale is fixed.I(10), which is equal to
|
||||||
|
// fixed.Int26_6(10 << 6).
|
||||||
|
//
|
||||||
|
// To measure a TrueType font in ideal FUnit space, use scale equal to
|
||||||
|
// font.FUnitsPerEm().
|
||||||
|
package truetype
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An Index is a Font's index of a rune.
|
||||||
|
type Index uint16
|
||||||
|
|
||||||
|
// A NameID identifies a name table entry.
|
||||||
|
//
|
||||||
|
// See https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html
|
||||||
|
type NameID uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
NameIDCopyright NameID = 0
|
||||||
|
NameIDFontFamily = 1
|
||||||
|
NameIDFontSubfamily = 2
|
||||||
|
NameIDUniqueSubfamilyID = 3
|
||||||
|
NameIDFontFullName = 4
|
||||||
|
NameIDNameTableVersion = 5
|
||||||
|
NameIDPostscriptName = 6
|
||||||
|
NameIDTrademarkNotice = 7
|
||||||
|
NameIDManufacturerName = 8
|
||||||
|
NameIDDesignerName = 9
|
||||||
|
NameIDFontDescription = 10
|
||||||
|
NameIDFontVendorURL = 11
|
||||||
|
NameIDFontDesignerURL = 12
|
||||||
|
NameIDFontLicense = 13
|
||||||
|
NameIDFontLicenseURL = 14
|
||||||
|
NameIDPreferredFamily = 16
|
||||||
|
NameIDPreferredSubfamily = 17
|
||||||
|
NameIDCompatibleName = 18
|
||||||
|
NameIDSampleText = 19
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// A 32-bit encoding consists of a most-significant 16-bit Platform ID and a
|
||||||
|
// least-significant 16-bit Platform Specific ID. The magic numbers are
|
||||||
|
// specified at https://www.microsoft.com/typography/otspec/name.htm
|
||||||
|
unicodeEncoding = 0x00000003 // PID = 0 (Unicode), PSID = 3 (Unicode 2.0)
|
||||||
|
microsoftSymbolEncoding = 0x00030000 // PID = 3 (Microsoft), PSID = 0 (Symbol)
|
||||||
|
microsoftUCS2Encoding = 0x00030001 // PID = 3 (Microsoft), PSID = 1 (UCS-2)
|
||||||
|
microsoftUCS4Encoding = 0x0003000a // PID = 3 (Microsoft), PSID = 10 (UCS-4)
|
||||||
|
)
|
||||||
|
|
||||||
|
// An HMetric holds the horizontal metrics of a single glyph.
|
||||||
|
type HMetric struct {
|
||||||
|
AdvanceWidth, LeftSideBearing fixed.Int26_6
|
||||||
|
}
|
||||||
|
|
||||||
|
// A VMetric holds the vertical metrics of a single glyph.
|
||||||
|
type VMetric struct {
|
||||||
|
AdvanceHeight, TopSideBearing fixed.Int26_6
|
||||||
|
}
|
||||||
|
|
||||||
|
// A FormatError reports that the input is not a valid TrueType font.
|
||||||
|
type FormatError string
|
||||||
|
|
||||||
|
func (e FormatError) Error() string {
|
||||||
|
return "freetype: invalid TrueType format: " + string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// An UnsupportedError reports that the input uses a valid but unimplemented
|
||||||
|
// TrueType feature.
|
||||||
|
type UnsupportedError string
|
||||||
|
|
||||||
|
func (e UnsupportedError) Error() string {
|
||||||
|
return "freetype: unsupported TrueType feature: " + string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// u32 returns the big-endian uint32 at b[i:].
|
||||||
|
func u32(b []byte, i int) uint32 {
|
||||||
|
return uint32(b[i])<<24 | uint32(b[i+1])<<16 | uint32(b[i+2])<<8 | uint32(b[i+3])
|
||||||
|
}
|
||||||
|
|
||||||
|
// u16 returns the big-endian uint16 at b[i:].
|
||||||
|
func u16(b []byte, i int) uint16 {
|
||||||
|
return uint16(b[i])<<8 | uint16(b[i+1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// readTable returns a slice of the TTF data given by a table's directory entry.
|
||||||
|
func readTable(ttf []byte, offsetLength []byte) ([]byte, error) {
|
||||||
|
offset := int(u32(offsetLength, 0))
|
||||||
|
if offset < 0 {
|
||||||
|
return nil, FormatError(fmt.Sprintf("offset too large: %d", uint32(offset)))
|
||||||
|
}
|
||||||
|
length := int(u32(offsetLength, 4))
|
||||||
|
if length < 0 {
|
||||||
|
return nil, FormatError(fmt.Sprintf("length too large: %d", uint32(length)))
|
||||||
|
}
|
||||||
|
end := offset + length
|
||||||
|
if end < 0 || end > len(ttf) {
|
||||||
|
return nil, FormatError(fmt.Sprintf("offset + length too large: %d", uint32(offset)+uint32(length)))
|
||||||
|
}
|
||||||
|
return ttf[offset:end], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSubtables returns the offset and platformID of the best subtable in
|
||||||
|
// table, where best favors a Unicode cmap encoding, and failing that, a
|
||||||
|
// Microsoft cmap encoding. offset is the offset of the first subtable in
|
||||||
|
// table, and size is the size of each subtable.
|
||||||
|
//
|
||||||
|
// If pred is non-nil, then only subtables that satisfy that predicate will be
|
||||||
|
// considered.
|
||||||
|
func parseSubtables(table []byte, name string, offset, size int, pred func([]byte) bool) (
|
||||||
|
bestOffset int, bestPID uint32, retErr error) {
|
||||||
|
|
||||||
|
if len(table) < 4 {
|
||||||
|
return 0, 0, FormatError(name + " too short")
|
||||||
|
}
|
||||||
|
nSubtables := int(u16(table, 2))
|
||||||
|
if len(table) < size*nSubtables+offset {
|
||||||
|
return 0, 0, FormatError(name + " too short")
|
||||||
|
}
|
||||||
|
ok := false
|
||||||
|
for i := 0; i < nSubtables; i, offset = i+1, offset+size {
|
||||||
|
if pred != nil && !pred(table[offset:]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// We read the 16-bit Platform ID and 16-bit Platform Specific ID as a single uint32.
|
||||||
|
// All values are big-endian.
|
||||||
|
pidPsid := u32(table, offset)
|
||||||
|
// We prefer the Unicode cmap encoding. Failing to find that, we fall
|
||||||
|
// back onto the Microsoft cmap encoding.
|
||||||
|
if pidPsid == unicodeEncoding {
|
||||||
|
bestOffset, bestPID, ok = offset, pidPsid>>16, true
|
||||||
|
break
|
||||||
|
|
||||||
|
} else if pidPsid == microsoftSymbolEncoding ||
|
||||||
|
pidPsid == microsoftUCS2Encoding ||
|
||||||
|
pidPsid == microsoftUCS4Encoding {
|
||||||
|
|
||||||
|
bestOffset, bestPID, ok = offset, pidPsid>>16, true
|
||||||
|
// We don't break out of the for loop, so that Unicode can override Microsoft.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return 0, 0, UnsupportedError(name + " encoding")
|
||||||
|
}
|
||||||
|
return bestOffset, bestPID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
locaOffsetFormatUnknown int = iota
|
||||||
|
locaOffsetFormatShort
|
||||||
|
locaOffsetFormatLong
|
||||||
|
)
|
||||||
|
|
||||||
|
// A cm holds a parsed cmap entry.
|
||||||
|
type cm struct {
|
||||||
|
start, end, delta, offset uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Font represents a Truetype font.
|
||||||
|
type Font struct {
|
||||||
|
// Tables sliced from the TTF data. The different tables are documented
|
||||||
|
// at http://developer.apple.com/fonts/TTRefMan/RM06/Chap6.html
|
||||||
|
cmap, cvt, fpgm, glyf, hdmx, head, hhea, hmtx, kern, loca, maxp, name, os2, prep, vmtx []byte
|
||||||
|
|
||||||
|
cmapIndexes []byte
|
||||||
|
|
||||||
|
// Cached values derived from the raw ttf data.
|
||||||
|
cm []cm
|
||||||
|
locaOffsetFormat int
|
||||||
|
nGlyph, nHMetric, nKern int
|
||||||
|
fUnitsPerEm int32
|
||||||
|
ascent int32 // In FUnits.
|
||||||
|
descent int32 // In FUnits; typically negative.
|
||||||
|
bounds fixed.Rectangle26_6 // In FUnits.
|
||||||
|
// Values from the maxp section.
|
||||||
|
maxTwilightPoints, maxStorage, maxFunctionDefs, maxStackElements uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Font) parseCmap() error {
|
||||||
|
const (
|
||||||
|
cmapFormat4 = 4
|
||||||
|
cmapFormat12 = 12
|
||||||
|
languageIndependent = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
offset, _, err := parseSubtables(f.cmap, "cmap", 4, 8, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
offset = int(u32(f.cmap, offset+4))
|
||||||
|
if offset <= 0 || offset > len(f.cmap) {
|
||||||
|
return FormatError("bad cmap offset")
|
||||||
|
}
|
||||||
|
|
||||||
|
cmapFormat := u16(f.cmap, offset)
|
||||||
|
switch cmapFormat {
|
||||||
|
case cmapFormat4:
|
||||||
|
language := u16(f.cmap, offset+4)
|
||||||
|
if language != languageIndependent {
|
||||||
|
return UnsupportedError(fmt.Sprintf("language: %d", language))
|
||||||
|
}
|
||||||
|
segCountX2 := int(u16(f.cmap, offset+6))
|
||||||
|
if segCountX2%2 == 1 {
|
||||||
|
return FormatError(fmt.Sprintf("bad segCountX2: %d", segCountX2))
|
||||||
|
}
|
||||||
|
segCount := segCountX2 / 2
|
||||||
|
offset += 14
|
||||||
|
f.cm = make([]cm, segCount)
|
||||||
|
for i := 0; i < segCount; i++ {
|
||||||
|
f.cm[i].end = uint32(u16(f.cmap, offset))
|
||||||
|
offset += 2
|
||||||
|
}
|
||||||
|
offset += 2
|
||||||
|
for i := 0; i < segCount; i++ {
|
||||||
|
f.cm[i].start = uint32(u16(f.cmap, offset))
|
||||||
|
offset += 2
|
||||||
|
}
|
||||||
|
for i := 0; i < segCount; i++ {
|
||||||
|
f.cm[i].delta = uint32(u16(f.cmap, offset))
|
||||||
|
offset += 2
|
||||||
|
}
|
||||||
|
for i := 0; i < segCount; i++ {
|
||||||
|
f.cm[i].offset = uint32(u16(f.cmap, offset))
|
||||||
|
offset += 2
|
||||||
|
}
|
||||||
|
f.cmapIndexes = f.cmap[offset:]
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case cmapFormat12:
|
||||||
|
if u16(f.cmap, offset+2) != 0 {
|
||||||
|
return FormatError(fmt.Sprintf("cmap format: % x", f.cmap[offset:offset+4]))
|
||||||
|
}
|
||||||
|
length := u32(f.cmap, offset+4)
|
||||||
|
language := u32(f.cmap, offset+8)
|
||||||
|
if language != languageIndependent {
|
||||||
|
return UnsupportedError(fmt.Sprintf("language: %d", language))
|
||||||
|
}
|
||||||
|
nGroups := u32(f.cmap, offset+12)
|
||||||
|
if length != 12*nGroups+16 {
|
||||||
|
return FormatError("inconsistent cmap length")
|
||||||
|
}
|
||||||
|
offset += 16
|
||||||
|
f.cm = make([]cm, nGroups)
|
||||||
|
for i := uint32(0); i < nGroups; i++ {
|
||||||
|
f.cm[i].start = u32(f.cmap, offset+0)
|
||||||
|
f.cm[i].end = u32(f.cmap, offset+4)
|
||||||
|
f.cm[i].delta = u32(f.cmap, offset+8) - f.cm[i].start
|
||||||
|
offset += 12
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return UnsupportedError(fmt.Sprintf("cmap format: %d", cmapFormat))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Font) parseHead() error {
|
||||||
|
if len(f.head) != 54 {
|
||||||
|
return FormatError(fmt.Sprintf("bad head length: %d", len(f.head)))
|
||||||
|
}
|
||||||
|
f.fUnitsPerEm = int32(u16(f.head, 18))
|
||||||
|
f.bounds.Min.X = fixed.Int26_6(int16(u16(f.head, 36)))
|
||||||
|
f.bounds.Min.Y = fixed.Int26_6(int16(u16(f.head, 38)))
|
||||||
|
f.bounds.Max.X = fixed.Int26_6(int16(u16(f.head, 40)))
|
||||||
|
f.bounds.Max.Y = fixed.Int26_6(int16(u16(f.head, 42)))
|
||||||
|
switch i := u16(f.head, 50); i {
|
||||||
|
case 0:
|
||||||
|
f.locaOffsetFormat = locaOffsetFormatShort
|
||||||
|
case 1:
|
||||||
|
f.locaOffsetFormat = locaOffsetFormatLong
|
||||||
|
default:
|
||||||
|
return FormatError(fmt.Sprintf("bad indexToLocFormat: %d", i))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Font) parseHhea() error {
|
||||||
|
if len(f.hhea) != 36 {
|
||||||
|
return FormatError(fmt.Sprintf("bad hhea length: %d", len(f.hhea)))
|
||||||
|
}
|
||||||
|
f.ascent = int32(int16(u16(f.hhea, 4)))
|
||||||
|
f.descent = int32(int16(u16(f.hhea, 6)))
|
||||||
|
f.nHMetric = int(u16(f.hhea, 34))
|
||||||
|
if 4*f.nHMetric+2*(f.nGlyph-f.nHMetric) != len(f.hmtx) {
|
||||||
|
return FormatError(fmt.Sprintf("bad hmtx length: %d", len(f.hmtx)))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Font) parseKern() error {
|
||||||
|
// Apple's TrueType documentation (http://developer.apple.com/fonts/TTRefMan/RM06/Chap6kern.html) says:
|
||||||
|
// "Previous versions of the 'kern' table defined both the version and nTables fields in the header
|
||||||
|
// as UInt16 values and not UInt32 values. Use of the older format on the Mac OS is discouraged
|
||||||
|
// (although AAT can sense an old kerning table and still make correct use of it). Microsoft
|
||||||
|
// Windows still uses the older format for the 'kern' table and will not recognize the newer one.
|
||||||
|
// Fonts targeted for the Mac OS only should use the new format; fonts targeted for both the Mac OS
|
||||||
|
// and Windows should use the old format."
|
||||||
|
// Since we expect that almost all fonts aim to be Windows-compatible, we only parse the "older" format,
|
||||||
|
// just like the C Freetype implementation.
|
||||||
|
if len(f.kern) == 0 {
|
||||||
|
if f.nKern != 0 {
|
||||||
|
return FormatError("bad kern table length")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(f.kern) < 18 {
|
||||||
|
return FormatError("kern data too short")
|
||||||
|
}
|
||||||
|
version, offset := u16(f.kern, 0), 2
|
||||||
|
if version != 0 {
|
||||||
|
return UnsupportedError(fmt.Sprintf("kern version: %d", version))
|
||||||
|
}
|
||||||
|
n, offset := u16(f.kern, offset), offset+2
|
||||||
|
if n != 1 {
|
||||||
|
return UnsupportedError(fmt.Sprintf("kern nTables: %d", n))
|
||||||
|
}
|
||||||
|
offset += 2
|
||||||
|
length, offset := int(u16(f.kern, offset)), offset+2
|
||||||
|
coverage, offset := u16(f.kern, offset), offset+2
|
||||||
|
if coverage != 0x0001 {
|
||||||
|
// We only support horizontal kerning.
|
||||||
|
return UnsupportedError(fmt.Sprintf("kern coverage: 0x%04x", coverage))
|
||||||
|
}
|
||||||
|
f.nKern, offset = int(u16(f.kern, offset)), offset+2
|
||||||
|
if 6*f.nKern != length-14 {
|
||||||
|
return FormatError("bad kern table length")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Font) parseMaxp() error {
|
||||||
|
if len(f.maxp) != 32 {
|
||||||
|
return FormatError(fmt.Sprintf("bad maxp length: %d", len(f.maxp)))
|
||||||
|
}
|
||||||
|
f.nGlyph = int(u16(f.maxp, 4))
|
||||||
|
f.maxTwilightPoints = u16(f.maxp, 16)
|
||||||
|
f.maxStorage = u16(f.maxp, 18)
|
||||||
|
f.maxFunctionDefs = u16(f.maxp, 20)
|
||||||
|
f.maxStackElements = u16(f.maxp, 24)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scale returns x divided by f.fUnitsPerEm, rounded to the nearest integer.
|
||||||
|
func (f *Font) scale(x fixed.Int26_6) fixed.Int26_6 {
|
||||||
|
if x >= 0 {
|
||||||
|
x += fixed.Int26_6(f.fUnitsPerEm) / 2
|
||||||
|
} else {
|
||||||
|
x -= fixed.Int26_6(f.fUnitsPerEm) / 2
|
||||||
|
}
|
||||||
|
return x / fixed.Int26_6(f.fUnitsPerEm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bounds returns the union of a Font's glyphs' bounds.
|
||||||
|
func (f *Font) Bounds(scale fixed.Int26_6) fixed.Rectangle26_6 {
|
||||||
|
b := f.bounds
|
||||||
|
b.Min.X = f.scale(scale * b.Min.X)
|
||||||
|
b.Min.Y = f.scale(scale * b.Min.Y)
|
||||||
|
b.Max.X = f.scale(scale * b.Max.X)
|
||||||
|
b.Max.Y = f.scale(scale * b.Max.Y)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// FUnitsPerEm returns the number of FUnits in a Font's em-square's side.
|
||||||
|
func (f *Font) FUnitsPerEm() int32 {
|
||||||
|
return f.fUnitsPerEm
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index returns a Font's index for the given rune.
|
||||||
|
func (f *Font) Index(x rune) Index {
|
||||||
|
c := uint32(x)
|
||||||
|
for i, j := 0, len(f.cm); i < j; {
|
||||||
|
h := i + (j-i)/2
|
||||||
|
cm := &f.cm[h]
|
||||||
|
if c < cm.start {
|
||||||
|
j = h
|
||||||
|
} else if cm.end < c {
|
||||||
|
i = h + 1
|
||||||
|
} else if cm.offset == 0 {
|
||||||
|
return Index(c + cm.delta)
|
||||||
|
} else {
|
||||||
|
offset := int(cm.offset) + 2*(h-len(f.cm)+int(c-cm.start))
|
||||||
|
return Index(u16(f.cmapIndexes, offset))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the Font's name value for the given NameID. It returns "" if
|
||||||
|
// there was an error, or if that name was not found.
|
||||||
|
func (f *Font) Name(id NameID) string {
|
||||||
|
x, platformID, err := parseSubtables(f.name, "name", 6, 12, func(b []byte) bool {
|
||||||
|
return NameID(u16(b, 6)) == id
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
offset, length := u16(f.name, 4)+u16(f.name, x+10), u16(f.name, x+8)
|
||||||
|
// Return the ASCII value of the encoded string.
|
||||||
|
// The string is encoded as UTF-16 on non-Apple platformIDs; Apple is platformID 1.
|
||||||
|
src := f.name[offset : offset+length]
|
||||||
|
var dst []byte
|
||||||
|
if platformID != 1 { // UTF-16.
|
||||||
|
if len(src)&1 != 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
dst = make([]byte, len(src)/2)
|
||||||
|
for i := range dst {
|
||||||
|
dst[i] = printable(u16(src, 2*i))
|
||||||
|
}
|
||||||
|
} else { // ASCII.
|
||||||
|
dst = make([]byte, len(src))
|
||||||
|
for i, c := range src {
|
||||||
|
dst[i] = printable(uint16(c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printable(r uint16) byte {
|
||||||
|
if 0x20 <= r && r < 0x7f {
|
||||||
|
return byte(r)
|
||||||
|
}
|
||||||
|
return '?'
|
||||||
|
}
|
||||||
|
|
||||||
|
// unscaledHMetric returns the unscaled horizontal metrics for the glyph with
|
||||||
|
// the given index.
|
||||||
|
func (f *Font) unscaledHMetric(i Index) (h HMetric) {
|
||||||
|
j := int(i)
|
||||||
|
if j < 0 || f.nGlyph <= j {
|
||||||
|
return HMetric{}
|
||||||
|
}
|
||||||
|
if j >= f.nHMetric {
|
||||||
|
p := 4 * (f.nHMetric - 1)
|
||||||
|
return HMetric{
|
||||||
|
AdvanceWidth: fixed.Int26_6(u16(f.hmtx, p)),
|
||||||
|
LeftSideBearing: fixed.Int26_6(int16(u16(f.hmtx, p+2*(j-f.nHMetric)+4))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return HMetric{
|
||||||
|
AdvanceWidth: fixed.Int26_6(u16(f.hmtx, 4*j)),
|
||||||
|
LeftSideBearing: fixed.Int26_6(int16(u16(f.hmtx, 4*j+2))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMetric returns the horizontal metrics for the glyph with the given index.
|
||||||
|
func (f *Font) HMetric(scale fixed.Int26_6, i Index) HMetric {
|
||||||
|
h := f.unscaledHMetric(i)
|
||||||
|
h.AdvanceWidth = f.scale(scale * h.AdvanceWidth)
|
||||||
|
h.LeftSideBearing = f.scale(scale * h.LeftSideBearing)
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// unscaledVMetric returns the unscaled vertical metrics for the glyph with
|
||||||
|
// the given index. yMax is the top of the glyph's bounding box.
|
||||||
|
func (f *Font) unscaledVMetric(i Index, yMax fixed.Int26_6) (v VMetric) {
|
||||||
|
j := int(i)
|
||||||
|
if j < 0 || f.nGlyph <= j {
|
||||||
|
return VMetric{}
|
||||||
|
}
|
||||||
|
if 4*j+4 <= len(f.vmtx) {
|
||||||
|
return VMetric{
|
||||||
|
AdvanceHeight: fixed.Int26_6(u16(f.vmtx, 4*j)),
|
||||||
|
TopSideBearing: fixed.Int26_6(int16(u16(f.vmtx, 4*j+2))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The OS/2 table has grown over time.
|
||||||
|
// https://developer.apple.com/fonts/TTRefMan/RM06/Chap6OS2.html
|
||||||
|
// says that it was originally 68 bytes. Optional fields, including
|
||||||
|
// the ascender and descender, are described at
|
||||||
|
// http://www.microsoft.com/typography/otspec/os2.htm
|
||||||
|
if len(f.os2) >= 72 {
|
||||||
|
sTypoAscender := fixed.Int26_6(int16(u16(f.os2, 68)))
|
||||||
|
sTypoDescender := fixed.Int26_6(int16(u16(f.os2, 70)))
|
||||||
|
return VMetric{
|
||||||
|
AdvanceHeight: sTypoAscender - sTypoDescender,
|
||||||
|
TopSideBearing: sTypoAscender - yMax,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return VMetric{
|
||||||
|
AdvanceHeight: fixed.Int26_6(f.fUnitsPerEm),
|
||||||
|
TopSideBearing: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VMetric returns the vertical metrics for the glyph with the given index.
|
||||||
|
func (f *Font) VMetric(scale fixed.Int26_6, i Index) VMetric {
|
||||||
|
// TODO: should 0 be bounds.YMax?
|
||||||
|
v := f.unscaledVMetric(i, 0)
|
||||||
|
v.AdvanceHeight = f.scale(scale * v.AdvanceHeight)
|
||||||
|
v.TopSideBearing = f.scale(scale * v.TopSideBearing)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kern returns the horizontal adjustment for the given glyph pair. A positive
|
||||||
|
// kern means to move the glyphs further apart.
|
||||||
|
func (f *Font) Kern(scale fixed.Int26_6, i0, i1 Index) fixed.Int26_6 {
|
||||||
|
if f.nKern == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
g := uint32(i0)<<16 | uint32(i1)
|
||||||
|
lo, hi := 0, f.nKern
|
||||||
|
for lo < hi {
|
||||||
|
i := (lo + hi) / 2
|
||||||
|
ig := u32(f.kern, 18+6*i)
|
||||||
|
if ig < g {
|
||||||
|
lo = i + 1
|
||||||
|
} else if ig > g {
|
||||||
|
hi = i
|
||||||
|
} else {
|
||||||
|
return f.scale(scale * fixed.Int26_6(int16(u16(f.kern, 22+6*i))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse returns a new Font for the given TTF or TTC data.
|
||||||
|
//
|
||||||
|
// For TrueType Collections, the first font in the collection is parsed.
|
||||||
|
func Parse(ttf []byte) (font *Font, err error) {
|
||||||
|
return parse(ttf, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse(ttf []byte, offset int) (font *Font, err error) {
|
||||||
|
if len(ttf)-offset < 12 {
|
||||||
|
err = FormatError("TTF data is too short")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
originalOffset := offset
|
||||||
|
magic, offset := u32(ttf, offset), offset+4
|
||||||
|
switch magic {
|
||||||
|
case 0x00010000:
|
||||||
|
// No-op.
|
||||||
|
case 0x74746366: // "ttcf" as a big-endian uint32.
|
||||||
|
if originalOffset != 0 {
|
||||||
|
err = FormatError("recursive TTC")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ttcVersion, offset := u32(ttf, offset), offset+4
|
||||||
|
if ttcVersion != 0x00010000 {
|
||||||
|
// TODO: support TTC version 2.0, once I have such a .ttc file to test with.
|
||||||
|
err = FormatError("bad TTC version")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
numFonts, offset := int(u32(ttf, offset)), offset+4
|
||||||
|
if numFonts <= 0 {
|
||||||
|
err = FormatError("bad number of TTC fonts")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(ttf[offset:])/4 < numFonts {
|
||||||
|
err = FormatError("TTC offset table is too short")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO: provide an API to select which font in a TrueType collection to return,
|
||||||
|
// not just the first one. This may require an API to parse a TTC's name tables,
|
||||||
|
// so users of this package can select the font in a TTC by name.
|
||||||
|
offset = int(u32(ttf, offset))
|
||||||
|
if offset <= 0 || offset > len(ttf) {
|
||||||
|
err = FormatError("bad TTC offset")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return parse(ttf, offset)
|
||||||
|
default:
|
||||||
|
err = FormatError("bad TTF version")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n, offset := int(u16(ttf, offset)), offset+2
|
||||||
|
if len(ttf) < 16*n+12 {
|
||||||
|
err = FormatError("TTF data is too short")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f := new(Font)
|
||||||
|
// Assign the table slices.
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
x := 16*i + 12
|
||||||
|
switch string(ttf[x : x+4]) {
|
||||||
|
case "cmap":
|
||||||
|
f.cmap, err = readTable(ttf, ttf[x+8:x+16])
|
||||||
|
case "cvt ":
|
||||||
|
f.cvt, err = readTable(ttf, ttf[x+8:x+16])
|
||||||
|
case "fpgm":
|
||||||
|
f.fpgm, err = readTable(ttf, ttf[x+8:x+16])
|
||||||
|
case "glyf":
|
||||||
|
f.glyf, err = readTable(ttf, ttf[x+8:x+16])
|
||||||
|
case "hdmx":
|
||||||
|
f.hdmx, err = readTable(ttf, ttf[x+8:x+16])
|
||||||
|
case "head":
|
||||||
|
f.head, err = readTable(ttf, ttf[x+8:x+16])
|
||||||
|
case "hhea":
|
||||||
|
f.hhea, err = readTable(ttf, ttf[x+8:x+16])
|
||||||
|
case "hmtx":
|
||||||
|
f.hmtx, err = readTable(ttf, ttf[x+8:x+16])
|
||||||
|
case "kern":
|
||||||
|
f.kern, err = readTable(ttf, ttf[x+8:x+16])
|
||||||
|
case "loca":
|
||||||
|
f.loca, err = readTable(ttf, ttf[x+8:x+16])
|
||||||
|
case "maxp":
|
||||||
|
f.maxp, err = readTable(ttf, ttf[x+8:x+16])
|
||||||
|
case "name":
|
||||||
|
f.name, err = readTable(ttf, ttf[x+8:x+16])
|
||||||
|
case "OS/2":
|
||||||
|
f.os2, err = readTable(ttf, ttf[x+8:x+16])
|
||||||
|
case "prep":
|
||||||
|
f.prep, err = readTable(ttf, ttf[x+8:x+16])
|
||||||
|
case "vmtx":
|
||||||
|
f.vmtx, err = readTable(ttf, ttf[x+8:x+16])
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Parse and sanity-check the TTF data.
|
||||||
|
if err = f.parseHead(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = f.parseMaxp(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = f.parseCmap(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = f.parseKern(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = f.parseHhea(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
font = f
|
||||||
|
return
|
||||||
|
}
|
202
vendor/github.com/golang/geo/LICENSE
generated
vendored
Normal file
202
vendor/github.com/golang/geo/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
22
vendor/github.com/golang/geo/r1/doc.go
generated
vendored
Normal file
22
vendor/github.com/golang/geo/r1/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package r1 implements types and functions for working with geometry in ℝ¹.
|
||||||
|
|
||||||
|
See ../s2 for a more detailed overview.
|
||||||
|
*/
|
||||||
|
package r1
|
161
vendor/github.com/golang/geo/r1/interval.go
generated
vendored
Normal file
161
vendor/github.com/golang/geo/r1/interval.go
generated
vendored
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package r1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Interval represents a closed interval on ℝ.
|
||||||
|
// Zero-length intervals (where Lo == Hi) represent single points.
|
||||||
|
// If Lo > Hi then the interval is empty.
|
||||||
|
type Interval struct {
|
||||||
|
Lo, Hi float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmptyInterval returns an empty interval.
|
||||||
|
func EmptyInterval() Interval { return Interval{1, 0} }
|
||||||
|
|
||||||
|
// IntervalFromPoint returns an interval representing a single point.
|
||||||
|
func IntervalFromPoint(p float64) Interval { return Interval{p, p} }
|
||||||
|
|
||||||
|
// IsEmpty reports whether the interval is empty.
|
||||||
|
func (i Interval) IsEmpty() bool { return i.Lo > i.Hi }
|
||||||
|
|
||||||
|
// Equal returns true iff the interval contains the same points as oi.
|
||||||
|
func (i Interval) Equal(oi Interval) bool {
|
||||||
|
return i == oi || i.IsEmpty() && oi.IsEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center returns the midpoint of the interval.
|
||||||
|
// It is undefined for empty intervals.
|
||||||
|
func (i Interval) Center() float64 { return 0.5 * (i.Lo + i.Hi) }
|
||||||
|
|
||||||
|
// Length returns the length of the interval.
|
||||||
|
// The length of an empty interval is negative.
|
||||||
|
func (i Interval) Length() float64 { return i.Hi - i.Lo }
|
||||||
|
|
||||||
|
// Contains returns true iff the interval contains p.
|
||||||
|
func (i Interval) Contains(p float64) bool { return i.Lo <= p && p <= i.Hi }
|
||||||
|
|
||||||
|
// ContainsInterval returns true iff the interval contains oi.
|
||||||
|
func (i Interval) ContainsInterval(oi Interval) bool {
|
||||||
|
if oi.IsEmpty() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return i.Lo <= oi.Lo && oi.Hi <= i.Hi
|
||||||
|
}
|
||||||
|
|
||||||
|
// InteriorContains returns true iff the the interval strictly contains p.
|
||||||
|
func (i Interval) InteriorContains(p float64) bool {
|
||||||
|
return i.Lo < p && p < i.Hi
|
||||||
|
}
|
||||||
|
|
||||||
|
// InteriorContainsInterval returns true iff the interval strictly contains oi.
|
||||||
|
func (i Interval) InteriorContainsInterval(oi Interval) bool {
|
||||||
|
if oi.IsEmpty() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return i.Lo < oi.Lo && oi.Hi < i.Hi
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intersects returns true iff the interval contains any points in common with oi.
|
||||||
|
func (i Interval) Intersects(oi Interval) bool {
|
||||||
|
if i.Lo <= oi.Lo {
|
||||||
|
return oi.Lo <= i.Hi && oi.Lo <= oi.Hi // oi.Lo ∈ i and oi is not empty
|
||||||
|
}
|
||||||
|
return i.Lo <= oi.Hi && i.Lo <= i.Hi // i.Lo ∈ oi and i is not empty
|
||||||
|
}
|
||||||
|
|
||||||
|
// InteriorIntersects returns true iff the interior of the interval contains any points in common with oi, including the latter's boundary.
|
||||||
|
func (i Interval) InteriorIntersects(oi Interval) bool {
|
||||||
|
return oi.Lo < i.Hi && i.Lo < oi.Hi && i.Lo < i.Hi && oi.Lo <= oi.Hi
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intersection returns the interval containing all points common to i and j.
|
||||||
|
func (i Interval) Intersection(j Interval) Interval {
|
||||||
|
// Empty intervals do not need to be special-cased.
|
||||||
|
return Interval{
|
||||||
|
Lo: math.Max(i.Lo, j.Lo),
|
||||||
|
Hi: math.Min(i.Hi, j.Hi),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPoint returns the interval expanded so that it contains the given point.
|
||||||
|
func (i Interval) AddPoint(p float64) Interval {
|
||||||
|
if i.IsEmpty() {
|
||||||
|
return Interval{p, p}
|
||||||
|
}
|
||||||
|
if p < i.Lo {
|
||||||
|
return Interval{p, i.Hi}
|
||||||
|
}
|
||||||
|
if p > i.Hi {
|
||||||
|
return Interval{i.Lo, p}
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClampPoint returns the closest point in the interval to the given point "p".
|
||||||
|
// The interval must be non-empty.
|
||||||
|
func (i Interval) ClampPoint(p float64) float64 {
|
||||||
|
return math.Max(i.Lo, math.Min(i.Hi, p))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expanded returns an interval that has been expanded on each side by margin.
|
||||||
|
// If margin is negative, then the function shrinks the interval on
|
||||||
|
// each side by margin instead. The resulting interval may be empty. Any
|
||||||
|
// expansion of an empty interval remains empty.
|
||||||
|
func (i Interval) Expanded(margin float64) Interval {
|
||||||
|
if i.IsEmpty() {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
return Interval{i.Lo - margin, i.Hi + margin}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Union returns the smallest interval that contains this interval and the given interval.
|
||||||
|
func (i Interval) Union(other Interval) Interval {
|
||||||
|
if i.IsEmpty() {
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
if other.IsEmpty() {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
return Interval{math.Min(i.Lo, other.Lo), math.Max(i.Hi, other.Hi)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Interval) String() string { return fmt.Sprintf("[%.7f, %.7f]", i.Lo, i.Hi) }
|
||||||
|
|
||||||
|
// epsilon is a small number that represents a reasonable level of noise between two
|
||||||
|
// values that can be considered to be equal.
|
||||||
|
const epsilon = 1e-14
|
||||||
|
|
||||||
|
// ApproxEqual reports whether the interval can be transformed into the
|
||||||
|
// given interval by moving each endpoint a small distance.
|
||||||
|
// The empty interval is considered to be positioned arbitrarily on the
|
||||||
|
// real line, so any interval with a small enough length will match
|
||||||
|
// the empty interval.
|
||||||
|
func (i Interval) ApproxEqual(other Interval) bool {
|
||||||
|
if i.IsEmpty() {
|
||||||
|
return other.Length() <= 2*epsilon
|
||||||
|
}
|
||||||
|
if other.IsEmpty() {
|
||||||
|
return i.Length() <= 2*epsilon
|
||||||
|
}
|
||||||
|
return math.Abs(other.Lo-i.Lo) <= epsilon &&
|
||||||
|
math.Abs(other.Hi-i.Hi) <= epsilon
|
||||||
|
}
|
22
vendor/github.com/golang/geo/r2/doc.go
generated
vendored
Normal file
22
vendor/github.com/golang/geo/r2/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package r2 implements types and functions for working with geometry in ℝ².
|
||||||
|
|
||||||
|
See package s2 for a more detailed overview.
|
||||||
|
*/
|
||||||
|
package r2
|
257
vendor/github.com/golang/geo/r2/rect.go
generated
vendored
Normal file
257
vendor/github.com/golang/geo/r2/rect.go
generated
vendored
Normal file
|
@ -0,0 +1,257 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package r2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/golang/geo/r1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Point represents a point in ℝ².
|
||||||
|
type Point struct {
|
||||||
|
X, Y float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add returns the sum of p and op.
|
||||||
|
func (p Point) Add(op Point) Point { return Point{p.X + op.X, p.Y + op.Y} }
|
||||||
|
|
||||||
|
// Sub returns the difference of p and op.
|
||||||
|
func (p Point) Sub(op Point) Point { return Point{p.X - op.X, p.Y - op.Y} }
|
||||||
|
|
||||||
|
// Mul returns the scalar product of p and m.
|
||||||
|
func (p Point) Mul(m float64) Point { return Point{m * p.X, m * p.Y} }
|
||||||
|
|
||||||
|
// Ortho returns a counterclockwise orthogonal point with the same norm.
|
||||||
|
func (p Point) Ortho() Point { return Point{-p.Y, p.X} }
|
||||||
|
|
||||||
|
// Dot returns the dot product between p and op.
|
||||||
|
func (p Point) Dot(op Point) float64 { return p.X*op.X + p.Y*op.Y }
|
||||||
|
|
||||||
|
// Cross returns the cross product of p and op.
|
||||||
|
func (p Point) Cross(op Point) float64 { return p.X*op.Y - p.Y*op.X }
|
||||||
|
|
||||||
|
// Norm returns the vector's norm.
|
||||||
|
func (p Point) Norm() float64 { return math.Hypot(p.X, p.Y) }
|
||||||
|
|
||||||
|
// Normalize returns a unit point in the same direction as p.
|
||||||
|
func (p Point) Normalize() Point {
|
||||||
|
if p.X == 0 && p.Y == 0 {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
return p.Mul(1 / p.Norm())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Point) String() string { return fmt.Sprintf("(%.12f, %.12f)", p.X, p.Y) }
|
||||||
|
|
||||||
|
// Rect represents a closed axis-aligned rectangle in the (x,y) plane.
|
||||||
|
type Rect struct {
|
||||||
|
X, Y r1.Interval
|
||||||
|
}
|
||||||
|
|
||||||
|
// RectFromPoints constructs a rect that contains the given points.
|
||||||
|
func RectFromPoints(pts ...Point) Rect {
|
||||||
|
// Because the default value on interval is 0,0, we need to manually
|
||||||
|
// define the interval from the first point passed in as our starting
|
||||||
|
// interval, otherwise we end up with the case of passing in
|
||||||
|
// Point{0.2, 0.3} and getting the starting Rect of {0, 0.2}, {0, 0.3}
|
||||||
|
// instead of the Rect {0.2, 0.2}, {0.3, 0.3} which is not correct.
|
||||||
|
if len(pts) == 0 {
|
||||||
|
return Rect{}
|
||||||
|
}
|
||||||
|
|
||||||
|
r := Rect{
|
||||||
|
X: r1.Interval{Lo: pts[0].X, Hi: pts[0].X},
|
||||||
|
Y: r1.Interval{Lo: pts[0].Y, Hi: pts[0].Y},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range pts[1:] {
|
||||||
|
r = r.AddPoint(p)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// RectFromCenterSize constructs a rectangle with the given center and size.
|
||||||
|
// Both dimensions of size must be non-negative.
|
||||||
|
func RectFromCenterSize(center, size Point) Rect {
|
||||||
|
return Rect{
|
||||||
|
r1.Interval{Lo: center.X - size.X/2, Hi: center.X + size.X/2},
|
||||||
|
r1.Interval{Lo: center.Y - size.Y/2, Hi: center.Y + size.Y/2},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmptyRect constructs the canonical empty rectangle. Use IsEmpty() to test
|
||||||
|
// for empty rectangles, since they have more than one representation. A Rect{}
|
||||||
|
// is not the same as the EmptyRect.
|
||||||
|
func EmptyRect() Rect {
|
||||||
|
return Rect{r1.EmptyInterval(), r1.EmptyInterval()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid reports whether the rectangle is valid.
|
||||||
|
// This requires the width to be empty iff the height is empty.
|
||||||
|
func (r Rect) IsValid() bool {
|
||||||
|
return r.X.IsEmpty() == r.Y.IsEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty reports whether the rectangle is empty.
|
||||||
|
func (r Rect) IsEmpty() bool {
|
||||||
|
return r.X.IsEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertices returns all four vertices of the rectangle. Vertices are returned in
|
||||||
|
// CCW direction starting with the lower left corner.
|
||||||
|
func (r Rect) Vertices() [4]Point {
|
||||||
|
return [4]Point{
|
||||||
|
{r.X.Lo, r.Y.Lo},
|
||||||
|
{r.X.Hi, r.Y.Lo},
|
||||||
|
{r.X.Hi, r.Y.Hi},
|
||||||
|
{r.X.Lo, r.Y.Hi},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VertexIJ returns the vertex in direction i along the X-axis (0=left, 1=right) and
|
||||||
|
// direction j along the Y-axis (0=down, 1=up).
|
||||||
|
func (r Rect) VertexIJ(i, j int) Point {
|
||||||
|
x := r.X.Lo
|
||||||
|
if i == 1 {
|
||||||
|
x = r.X.Hi
|
||||||
|
}
|
||||||
|
y := r.Y.Lo
|
||||||
|
if j == 1 {
|
||||||
|
y = r.Y.Hi
|
||||||
|
}
|
||||||
|
return Point{x, y}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lo returns the low corner of the rect.
|
||||||
|
func (r Rect) Lo() Point {
|
||||||
|
return Point{r.X.Lo, r.Y.Lo}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hi returns the high corner of the rect.
|
||||||
|
func (r Rect) Hi() Point {
|
||||||
|
return Point{r.X.Hi, r.Y.Hi}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center returns the center of the rectangle in (x,y)-space
|
||||||
|
func (r Rect) Center() Point {
|
||||||
|
return Point{r.X.Center(), r.Y.Center()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the width and height of this rectangle in (x,y)-space. Empty
|
||||||
|
// rectangles have a negative width and height.
|
||||||
|
func (r Rect) Size() Point {
|
||||||
|
return Point{r.X.Length(), r.Y.Length()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainsPoint reports whether the rectangle contains the given point.
|
||||||
|
// Rectangles are closed regions, i.e. they contain their boundary.
|
||||||
|
func (r Rect) ContainsPoint(p Point) bool {
|
||||||
|
return r.X.Contains(p.X) && r.Y.Contains(p.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InteriorContainsPoint returns true iff the given point is contained in the interior
|
||||||
|
// of the region (i.e. the region excluding its boundary).
|
||||||
|
func (r Rect) InteriorContainsPoint(p Point) bool {
|
||||||
|
return r.X.InteriorContains(p.X) && r.Y.InteriorContains(p.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains reports whether the rectangle contains the given rectangle.
|
||||||
|
func (r Rect) Contains(other Rect) bool {
|
||||||
|
return r.X.ContainsInterval(other.X) && r.Y.ContainsInterval(other.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InteriorContains reports whether the interior of this rectangle contains all of the
|
||||||
|
// points of the given other rectangle (including its boundary).
|
||||||
|
func (r Rect) InteriorContains(other Rect) bool {
|
||||||
|
return r.X.InteriorContainsInterval(other.X) && r.Y.InteriorContainsInterval(other.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intersects reports whether this rectangle and the other rectangle have any points in common.
|
||||||
|
func (r Rect) Intersects(other Rect) bool {
|
||||||
|
return r.X.Intersects(other.X) && r.Y.Intersects(other.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InteriorIntersects reports whether the interior of this rectangle intersects
|
||||||
|
// any point (including the boundary) of the given other rectangle.
|
||||||
|
func (r Rect) InteriorIntersects(other Rect) bool {
|
||||||
|
return r.X.InteriorIntersects(other.X) && r.Y.InteriorIntersects(other.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPoint expands the rectangle to include the given point. The rectangle is
|
||||||
|
// expanded by the minimum amount possible.
|
||||||
|
func (r Rect) AddPoint(p Point) Rect {
|
||||||
|
return Rect{r.X.AddPoint(p.X), r.Y.AddPoint(p.Y)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRect expands the rectangle to include the given rectangle. This is the
|
||||||
|
// same as replacing the rectangle by the union of the two rectangles, but
|
||||||
|
// is more efficient.
|
||||||
|
func (r Rect) AddRect(other Rect) Rect {
|
||||||
|
return Rect{r.X.Union(other.X), r.Y.Union(other.Y)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClampPoint returns the closest point in the rectangle to the given point.
|
||||||
|
// The rectangle must be non-empty.
|
||||||
|
func (r Rect) ClampPoint(p Point) Point {
|
||||||
|
return Point{r.X.ClampPoint(p.X), r.Y.ClampPoint(p.Y)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expanded returns a rectangle that has been expanded in the x-direction
|
||||||
|
// by margin.X, and in y-direction by margin.Y. If either margin is empty,
|
||||||
|
// then shrink the interval on the corresponding sides instead. The resulting
|
||||||
|
// rectangle may be empty. Any expansion of an empty rectangle remains empty.
|
||||||
|
func (r Rect) Expanded(margin Point) Rect {
|
||||||
|
xx := r.X.Expanded(margin.X)
|
||||||
|
yy := r.Y.Expanded(margin.Y)
|
||||||
|
if xx.IsEmpty() || yy.IsEmpty() {
|
||||||
|
return EmptyRect()
|
||||||
|
}
|
||||||
|
return Rect{xx, yy}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpandedByMargin returns a Rect that has been expanded by the amount on all sides.
|
||||||
|
func (r Rect) ExpandedByMargin(margin float64) Rect {
|
||||||
|
return r.Expanded(Point{margin, margin})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Union returns the smallest rectangle containing the union of this rectangle and
|
||||||
|
// the given rectangle.
|
||||||
|
func (r Rect) Union(other Rect) Rect {
|
||||||
|
return Rect{r.X.Union(other.X), r.Y.Union(other.Y)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intersection returns the smallest rectangle containing the intersection of this
|
||||||
|
// rectangle and the given rectangle.
|
||||||
|
func (r Rect) Intersection(other Rect) Rect {
|
||||||
|
xx := r.X.Intersection(other.X)
|
||||||
|
yy := r.Y.Intersection(other.Y)
|
||||||
|
if xx.IsEmpty() || yy.IsEmpty() {
|
||||||
|
return EmptyRect()
|
||||||
|
}
|
||||||
|
|
||||||
|
return Rect{xx, yy}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApproxEquals returns true if the x- and y-intervals of the two rectangles are
|
||||||
|
// the same up to the given tolerance.
|
||||||
|
func (r Rect) ApproxEquals(r2 Rect) bool {
|
||||||
|
return r.X.ApproxEqual(r2.X) && r.Y.ApproxEqual(r2.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Rect) String() string { return fmt.Sprintf("[Lo%s, Hi%s]", r.Lo(), r.Hi()) }
|
22
vendor/github.com/golang/geo/r3/doc.go
generated
vendored
Normal file
22
vendor/github.com/golang/geo/r3/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package r3 implements types and functions for working with geometry in ℝ³.
|
||||||
|
|
||||||
|
See ../s2 for a more detailed overview.
|
||||||
|
*/
|
||||||
|
package r3
|
200
vendor/github.com/golang/geo/r3/precisevector.go
generated
vendored
Normal file
200
vendor/github.com/golang/geo/r3/precisevector.go
generated
vendored
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package r3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// prec is the number of bits of precision to use for the Float values.
|
||||||
|
// To keep things simple, we use the maximum allowable precision on big
|
||||||
|
// values. This allows us to handle all values we expect in the s2 library.
|
||||||
|
prec = big.MaxPrec
|
||||||
|
)
|
||||||
|
|
||||||
|
// define some commonly referenced values.
|
||||||
|
var (
|
||||||
|
precise0 = precInt(0)
|
||||||
|
precise1 = precInt(1)
|
||||||
|
)
|
||||||
|
|
||||||
|
// precStr wraps the conversion from a string into a big.Float. For results that
|
||||||
|
// actually can be represented exactly, this should only be used on values that
|
||||||
|
// are integer multiples of integer powers of 2.
|
||||||
|
func precStr(s string) *big.Float {
|
||||||
|
// Explicitly ignoring the bool return for this usage.
|
||||||
|
f, _ := new(big.Float).SetPrec(prec).SetString(s)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func precInt(i int64) *big.Float {
|
||||||
|
return new(big.Float).SetPrec(prec).SetInt64(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func precFloat(f float64) *big.Float {
|
||||||
|
return new(big.Float).SetPrec(prec).SetFloat64(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func precAdd(a, b *big.Float) *big.Float {
|
||||||
|
return new(big.Float).SetPrec(prec).Add(a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func precSub(a, b *big.Float) *big.Float {
|
||||||
|
return new(big.Float).SetPrec(prec).Sub(a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func precMul(a, b *big.Float) *big.Float {
|
||||||
|
return new(big.Float).SetPrec(prec).Mul(a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreciseVector represents a point in ℝ³ using high-precision values.
|
||||||
|
// Note that this is NOT a complete implementation because there are some
|
||||||
|
// operations that Vector supports that are not feasible with arbitrary precision
|
||||||
|
// math. (e.g., methods that need divison like Normalize, or methods needing a
|
||||||
|
// square root operation such as Norm)
|
||||||
|
type PreciseVector struct {
|
||||||
|
X, Y, Z *big.Float
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreciseVectorFromVector creates a high precision vector from the given Vector.
|
||||||
|
func PreciseVectorFromVector(v Vector) PreciseVector {
|
||||||
|
return NewPreciseVector(v.X, v.Y, v.Z)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPreciseVector creates a high precision vector from the given floating point values.
|
||||||
|
func NewPreciseVector(x, y, z float64) PreciseVector {
|
||||||
|
return PreciseVector{
|
||||||
|
X: precFloat(x),
|
||||||
|
Y: precFloat(y),
|
||||||
|
Z: precFloat(z),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vector returns this precise vector converted to a Vector.
|
||||||
|
func (v PreciseVector) Vector() Vector {
|
||||||
|
// The accuracy flag is ignored on these conversions back to float64.
|
||||||
|
x, _ := v.X.Float64()
|
||||||
|
y, _ := v.Y.Float64()
|
||||||
|
z, _ := v.Z.Float64()
|
||||||
|
return Vector{x, y, z}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals reports whether v and ov are equal.
|
||||||
|
func (v PreciseVector) Equals(ov PreciseVector) bool {
|
||||||
|
return v.X.Cmp(ov.X) == 0 && v.Y.Cmp(ov.Y) == 0 && v.Z.Cmp(ov.Z) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v PreciseVector) String() string {
|
||||||
|
return fmt.Sprintf("(%v, %v, %v)", v.X, v.Y, v.Z)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Norm2 returns the square of the norm.
|
||||||
|
func (v PreciseVector) Norm2() *big.Float { return v.Dot(v) }
|
||||||
|
|
||||||
|
// IsUnit reports whether this vector is of unit length.
|
||||||
|
func (v PreciseVector) IsUnit() bool {
|
||||||
|
return v.Norm2().Cmp(precise1) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abs returns the vector with nonnegative components.
|
||||||
|
func (v PreciseVector) Abs() PreciseVector {
|
||||||
|
return PreciseVector{
|
||||||
|
X: new(big.Float).Abs(v.X),
|
||||||
|
Y: new(big.Float).Abs(v.Y),
|
||||||
|
Z: new(big.Float).Abs(v.Z),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add returns the standard vector sum of v and ov.
|
||||||
|
func (v PreciseVector) Add(ov PreciseVector) PreciseVector {
|
||||||
|
return PreciseVector{
|
||||||
|
X: precAdd(v.X, ov.X),
|
||||||
|
Y: precAdd(v.Y, ov.Y),
|
||||||
|
Z: precAdd(v.Z, ov.Z),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub returns the standard vector difference of v and ov.
|
||||||
|
func (v PreciseVector) Sub(ov PreciseVector) PreciseVector {
|
||||||
|
return PreciseVector{
|
||||||
|
X: precSub(v.X, ov.X),
|
||||||
|
Y: precSub(v.Y, ov.Y),
|
||||||
|
Z: precSub(v.Z, ov.Z),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mul returns the standard scalar product of v and f.
|
||||||
|
func (v PreciseVector) Mul(f *big.Float) PreciseVector {
|
||||||
|
return PreciseVector{
|
||||||
|
X: precMul(v.X, f),
|
||||||
|
Y: precMul(v.Y, f),
|
||||||
|
Z: precMul(v.Z, f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MulByFloat64 returns the standard scalar product of v and f.
|
||||||
|
func (v PreciseVector) MulByFloat64(f float64) PreciseVector {
|
||||||
|
return v.Mul(precFloat(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dot returns the standard dot product of v and ov.
|
||||||
|
func (v PreciseVector) Dot(ov PreciseVector) *big.Float {
|
||||||
|
return precAdd(precMul(v.X, ov.X), precAdd(precMul(v.Y, ov.Y), precMul(v.Z, ov.Z)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cross returns the standard cross product of v and ov.
|
||||||
|
func (v PreciseVector) Cross(ov PreciseVector) PreciseVector {
|
||||||
|
return PreciseVector{
|
||||||
|
X: precSub(precMul(v.Y, ov.Z), precMul(v.Z, ov.Y)),
|
||||||
|
Y: precSub(precMul(v.Z, ov.X), precMul(v.X, ov.Z)),
|
||||||
|
Z: precSub(precMul(v.X, ov.Y), precMul(v.Y, ov.X)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LargestComponent returns the axis that represents the largest component in this vector.
|
||||||
|
func (v PreciseVector) LargestComponent() Axis {
|
||||||
|
t := v.Abs()
|
||||||
|
|
||||||
|
if t.X.Cmp(t.Y) > 0 {
|
||||||
|
if t.X.Cmp(t.Z) > 0 {
|
||||||
|
return XAxis
|
||||||
|
}
|
||||||
|
return ZAxis
|
||||||
|
}
|
||||||
|
if t.Y.Cmp(t.Z) > 0 {
|
||||||
|
return YAxis
|
||||||
|
}
|
||||||
|
return ZAxis
|
||||||
|
}
|
||||||
|
|
||||||
|
// SmallestComponent returns the axis that represents the smallest component in this vector.
|
||||||
|
func (v PreciseVector) SmallestComponent() Axis {
|
||||||
|
t := v.Abs()
|
||||||
|
|
||||||
|
if t.X.Cmp(t.Y) < 0 {
|
||||||
|
if t.X.Cmp(t.Z) < 0 {
|
||||||
|
return XAxis
|
||||||
|
}
|
||||||
|
return ZAxis
|
||||||
|
}
|
||||||
|
if t.Y.Cmp(t.Z) < 0 {
|
||||||
|
return YAxis
|
||||||
|
}
|
||||||
|
return ZAxis
|
||||||
|
}
|
184
vendor/github.com/golang/geo/r3/vector.go
generated
vendored
Normal file
184
vendor/github.com/golang/geo/r3/vector.go
generated
vendored
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package r3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/golang/geo/s1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Vector represents a point in ℝ³.
|
||||||
|
type Vector struct {
|
||||||
|
X, Y, Z float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApproxEqual reports whether v and ov are equal within a small epsilon.
|
||||||
|
func (v Vector) ApproxEqual(ov Vector) bool {
|
||||||
|
const epsilon = 1e-16
|
||||||
|
return math.Abs(v.X-ov.X) < epsilon && math.Abs(v.Y-ov.Y) < epsilon && math.Abs(v.Z-ov.Z) < epsilon
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Vector) String() string { return fmt.Sprintf("(%0.24f, %0.24f, %0.24f)", v.X, v.Y, v.Z) }
|
||||||
|
|
||||||
|
// Norm returns the vector's norm.
|
||||||
|
func (v Vector) Norm() float64 { return math.Sqrt(v.Dot(v)) }
|
||||||
|
|
||||||
|
// Norm2 returns the square of the norm.
|
||||||
|
func (v Vector) Norm2() float64 { return v.Dot(v) }
|
||||||
|
|
||||||
|
// Normalize returns a unit vector in the same direction as v.
|
||||||
|
func (v Vector) Normalize() Vector {
|
||||||
|
if v == (Vector{0, 0, 0}) {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return v.Mul(1 / v.Norm())
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsUnit returns whether this vector is of approximately unit length.
|
||||||
|
func (v Vector) IsUnit() bool {
|
||||||
|
const epsilon = 5e-14
|
||||||
|
return math.Abs(v.Norm2()-1) <= epsilon
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abs returns the vector with nonnegative components.
|
||||||
|
func (v Vector) Abs() Vector { return Vector{math.Abs(v.X), math.Abs(v.Y), math.Abs(v.Z)} }
|
||||||
|
|
||||||
|
// Add returns the standard vector sum of v and ov.
|
||||||
|
func (v Vector) Add(ov Vector) Vector { return Vector{v.X + ov.X, v.Y + ov.Y, v.Z + ov.Z} }
|
||||||
|
|
||||||
|
// Sub returns the standard vector difference of v and ov.
|
||||||
|
func (v Vector) Sub(ov Vector) Vector { return Vector{v.X - ov.X, v.Y - ov.Y, v.Z - ov.Z} }
|
||||||
|
|
||||||
|
// Mul returns the standard scalar product of v and m.
|
||||||
|
func (v Vector) Mul(m float64) Vector { return Vector{m * v.X, m * v.Y, m * v.Z} }
|
||||||
|
|
||||||
|
// Dot returns the standard dot product of v and ov.
|
||||||
|
func (v Vector) Dot(ov Vector) float64 { return v.X*ov.X + v.Y*ov.Y + v.Z*ov.Z }
|
||||||
|
|
||||||
|
// Cross returns the standard cross product of v and ov.
|
||||||
|
func (v Vector) Cross(ov Vector) Vector {
|
||||||
|
return Vector{
|
||||||
|
v.Y*ov.Z - v.Z*ov.Y,
|
||||||
|
v.Z*ov.X - v.X*ov.Z,
|
||||||
|
v.X*ov.Y - v.Y*ov.X,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distance returns the Euclidean distance between v and ov.
|
||||||
|
func (v Vector) Distance(ov Vector) float64 { return v.Sub(ov).Norm() }
|
||||||
|
|
||||||
|
// Angle returns the angle between v and ov.
|
||||||
|
func (v Vector) Angle(ov Vector) s1.Angle {
|
||||||
|
return s1.Angle(math.Atan2(v.Cross(ov).Norm(), v.Dot(ov))) * s1.Radian
|
||||||
|
}
|
||||||
|
|
||||||
|
// Axis enumerates the 3 axes of ℝ³.
|
||||||
|
type Axis int
|
||||||
|
|
||||||
|
// The three axes of ℝ³.
|
||||||
|
const (
|
||||||
|
XAxis Axis = iota
|
||||||
|
YAxis
|
||||||
|
ZAxis
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ortho returns a unit vector that is orthogonal to v.
|
||||||
|
// Ortho(-v) = -Ortho(v) for all v.
|
||||||
|
func (v Vector) Ortho() Vector {
|
||||||
|
ov := Vector{0.012, 0.0053, 0.00457}
|
||||||
|
switch v.LargestComponent() {
|
||||||
|
case XAxis:
|
||||||
|
ov.Z = 1
|
||||||
|
case YAxis:
|
||||||
|
ov.X = 1
|
||||||
|
default:
|
||||||
|
ov.Y = 1
|
||||||
|
}
|
||||||
|
return v.Cross(ov).Normalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LargestComponent returns the axis that represents the largest component in this vector.
|
||||||
|
func (v Vector) LargestComponent() Axis {
|
||||||
|
t := v.Abs()
|
||||||
|
|
||||||
|
if t.X > t.Y {
|
||||||
|
if t.X > t.Z {
|
||||||
|
return XAxis
|
||||||
|
}
|
||||||
|
return ZAxis
|
||||||
|
}
|
||||||
|
if t.Y > t.Z {
|
||||||
|
return YAxis
|
||||||
|
}
|
||||||
|
return ZAxis
|
||||||
|
}
|
||||||
|
|
||||||
|
// SmallestComponent returns the axis that represents the smallest component in this vector.
|
||||||
|
func (v Vector) SmallestComponent() Axis {
|
||||||
|
t := v.Abs()
|
||||||
|
|
||||||
|
if t.X < t.Y {
|
||||||
|
if t.X < t.Z {
|
||||||
|
return XAxis
|
||||||
|
}
|
||||||
|
return ZAxis
|
||||||
|
}
|
||||||
|
if t.Y < t.Z {
|
||||||
|
return YAxis
|
||||||
|
}
|
||||||
|
return ZAxis
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cmp compares v and ov lexicographically and returns:
|
||||||
|
//
|
||||||
|
// -1 if v < ov
|
||||||
|
// 0 if v == ov
|
||||||
|
// +1 if v > ov
|
||||||
|
//
|
||||||
|
// This method is based on C++'s std::lexicographical_compare. Two entities
|
||||||
|
// are compared element by element with the given operator. The first mismatch
|
||||||
|
// defines which is less (or greater) than the other. If both have equivalent
|
||||||
|
// values they are lexicographically equal.
|
||||||
|
func (v Vector) Cmp(ov Vector) int {
|
||||||
|
if v.X < ov.X {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if v.X > ov.X {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// First elements were the same, try the next.
|
||||||
|
if v.Y < ov.Y {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if v.Y > ov.Y {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second elements were the same return the final compare.
|
||||||
|
if v.Z < ov.Z {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if v.Z > ov.Z {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both are equal
|
||||||
|
return 0
|
||||||
|
}
|
119
vendor/github.com/golang/geo/s1/angle.go
generated
vendored
Normal file
119
vendor/github.com/golang/geo/s1/angle.go
generated
vendored
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package s1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Angle represents a 1D angle. The internal representation is a double precision
|
||||||
|
// value in radians, so conversion to and from radians is exact.
|
||||||
|
// Conversions between E5, E6, E7, and Degrees are not always
|
||||||
|
// exact. For example, Degrees(3.1) is different from E6(3100000) or E7(310000000).
|
||||||
|
//
|
||||||
|
// The following conversions between degrees and radians are exact:
|
||||||
|
//
|
||||||
|
// Degree*180 == Radian*math.Pi
|
||||||
|
// Degree*(180/n) == Radian*(math.Pi/n) for n == 0..8
|
||||||
|
//
|
||||||
|
// These identities hold when the arguments are scaled up or down by any power
|
||||||
|
// of 2. Some similar identities are also true, for example,
|
||||||
|
//
|
||||||
|
// Degree*60 == Radian*(math.Pi/3)
|
||||||
|
//
|
||||||
|
// But be aware that this type of identity does not hold in general. For example,
|
||||||
|
//
|
||||||
|
// Degree*3 != Radian*(math.Pi/60)
|
||||||
|
//
|
||||||
|
// Similarly, the conversion to radians means that (Angle(x)*Degree).Degrees()
|
||||||
|
// does not always equal x. For example,
|
||||||
|
//
|
||||||
|
// (Angle(45*n)*Degree).Degrees() == 45*n for n == 0..8
|
||||||
|
//
|
||||||
|
// but
|
||||||
|
//
|
||||||
|
// (60*Degree).Degrees() != 60
|
||||||
|
//
|
||||||
|
// When testing for equality, you should allow for numerical errors (floatApproxEq)
|
||||||
|
// or convert to discrete E5/E6/E7 values first.
|
||||||
|
type Angle float64
|
||||||
|
|
||||||
|
// Angle units.
|
||||||
|
const (
|
||||||
|
Radian Angle = 1
|
||||||
|
Degree = (math.Pi / 180) * Radian
|
||||||
|
|
||||||
|
E5 = 1e-5 * Degree
|
||||||
|
E6 = 1e-6 * Degree
|
||||||
|
E7 = 1e-7 * Degree
|
||||||
|
)
|
||||||
|
|
||||||
|
// Radians returns the angle in radians.
|
||||||
|
func (a Angle) Radians() float64 { return float64(a) }
|
||||||
|
|
||||||
|
// Degrees returns the angle in degrees.
|
||||||
|
func (a Angle) Degrees() float64 { return float64(a / Degree) }
|
||||||
|
|
||||||
|
// round returns the value rounded to nearest as an int32.
|
||||||
|
// This does not match C++ exactly for the case of x.5.
|
||||||
|
func round(val float64) int32 {
|
||||||
|
if val < 0 {
|
||||||
|
return int32(val - 0.5)
|
||||||
|
}
|
||||||
|
return int32(val + 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InfAngle returns an angle larger than any finite angle.
|
||||||
|
func InfAngle() Angle {
|
||||||
|
return Angle(math.Inf(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// isInf reports whether this Angle is infinite.
|
||||||
|
func (a Angle) isInf() bool {
|
||||||
|
return math.IsInf(float64(a), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// E5 returns the angle in hundred thousandths of degrees.
|
||||||
|
func (a Angle) E5() int32 { return round(a.Degrees() * 1e5) }
|
||||||
|
|
||||||
|
// E6 returns the angle in millionths of degrees.
|
||||||
|
func (a Angle) E6() int32 { return round(a.Degrees() * 1e6) }
|
||||||
|
|
||||||
|
// E7 returns the angle in ten millionths of degrees.
|
||||||
|
func (a Angle) E7() int32 { return round(a.Degrees() * 1e7) }
|
||||||
|
|
||||||
|
// Abs returns the absolute value of the angle.
|
||||||
|
func (a Angle) Abs() Angle { return Angle(math.Abs(float64(a))) }
|
||||||
|
|
||||||
|
// Normalized returns an equivalent angle in [0, 2π).
|
||||||
|
func (a Angle) Normalized() Angle {
|
||||||
|
rad := math.Mod(float64(a), 2*math.Pi)
|
||||||
|
if rad < 0 {
|
||||||
|
rad += 2 * math.Pi
|
||||||
|
}
|
||||||
|
return Angle(rad)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Angle) String() string {
|
||||||
|
return strconv.FormatFloat(a.Degrees(), 'f', 7, 64) // like "%.7f"
|
||||||
|
}
|
||||||
|
|
||||||
|
// BUG(dsymonds): The major differences from the C++ version are:
|
||||||
|
// - no unsigned E5/E6/E7 methods
|
||||||
|
// - no S2Point or S2LatLng constructors
|
||||||
|
// - no comparison or arithmetic operators
|
208
vendor/github.com/golang/geo/s1/chordangle.go
generated
vendored
Normal file
208
vendor/github.com/golang/geo/s1/chordangle.go
generated
vendored
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package s1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ChordAngle represents the angle subtended by a chord (i.e., the straight
|
||||||
|
// line segment connecting two points on the sphere). Its representation
|
||||||
|
// makes it very efficient for computing and comparing distances, but unlike
|
||||||
|
// Angle it is only capable of representing angles between 0 and π radians.
|
||||||
|
// Generally, ChordAngle should only be used in loops where many angles need
|
||||||
|
// to be calculated and compared. Otherwise it is simpler to use Angle.
|
||||||
|
//
|
||||||
|
// ChordAngle loses some accuracy as the angle approaches π radians.
|
||||||
|
// Specifically, the representation of (π - x) radians has an error of about
|
||||||
|
// (1e-15 / x), with a maximum error of about 2e-8 radians (about 13cm on the
|
||||||
|
// Earth's surface). For comparison, for angles up to π/2 radians (10000km)
|
||||||
|
// the worst-case representation error is about 2e-16 radians (1 nanonmeter),
|
||||||
|
// which is about the same as Angle.
|
||||||
|
//
|
||||||
|
// ChordAngles are represented by the squared chord length, which can
|
||||||
|
// range from 0 to 4. Positive infinity represents an infinite squared length.
|
||||||
|
type ChordAngle float64
|
||||||
|
|
||||||
|
const (
|
||||||
|
// NegativeChordAngle represents a chord angle smaller than the zero angle.
|
||||||
|
// The only valid operations on a NegativeChordAngle are comparisons and
|
||||||
|
// Angle conversions.
|
||||||
|
NegativeChordAngle = ChordAngle(-1)
|
||||||
|
|
||||||
|
// RightChordAngle represents a chord angle of 90 degrees (a "right angle").
|
||||||
|
RightChordAngle = ChordAngle(2)
|
||||||
|
|
||||||
|
// StraightChordAngle represents a chord angle of 180 degrees (a "straight angle").
|
||||||
|
// This is the maximum finite chord angle.
|
||||||
|
StraightChordAngle = ChordAngle(4)
|
||||||
|
)
|
||||||
|
|
||||||
|
// ChordAngleFromAngle returns a ChordAngle from the given Angle.
|
||||||
|
func ChordAngleFromAngle(a Angle) ChordAngle {
|
||||||
|
if a < 0 {
|
||||||
|
return NegativeChordAngle
|
||||||
|
}
|
||||||
|
if a.isInf() {
|
||||||
|
return InfChordAngle()
|
||||||
|
}
|
||||||
|
l := 2 * math.Sin(0.5*math.Min(math.Pi, a.Radians()))
|
||||||
|
return ChordAngle(l * l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChordAngleFromSquaredLength returns a ChordAngle from the squared chord length.
|
||||||
|
// Note that the argument is automatically clamped to a maximum of 4.0 to
|
||||||
|
// handle possible roundoff errors. The argument must be non-negative.
|
||||||
|
func ChordAngleFromSquaredLength(length2 float64) ChordAngle {
|
||||||
|
if length2 > 4 {
|
||||||
|
return StraightChordAngle
|
||||||
|
}
|
||||||
|
return ChordAngle(length2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expanded returns a new ChordAngle that has been adjusted by the given error
|
||||||
|
// bound (which can be positive or negative). Error should be the value
|
||||||
|
// returned by either MaxPointError or MaxAngleError. For example:
|
||||||
|
// a := ChordAngleFromPoints(x, y)
|
||||||
|
// a1 := a.Expanded(a.MaxPointError())
|
||||||
|
func (c ChordAngle) Expanded(e float64) ChordAngle {
|
||||||
|
// If the angle is special, don't change it. Otherwise clamp it to the valid range.
|
||||||
|
if c.isSpecial() {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return ChordAngle(math.Max(0.0, math.Min(4.0, float64(c)+e)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Angle converts this ChordAngle to an Angle.
|
||||||
|
func (c ChordAngle) Angle() Angle {
|
||||||
|
if c < 0 {
|
||||||
|
return -1 * Radian
|
||||||
|
}
|
||||||
|
if c.isInf() {
|
||||||
|
return InfAngle()
|
||||||
|
}
|
||||||
|
return Angle(2 * math.Asin(0.5*math.Sqrt(float64(c))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// InfChordAngle returns a chord angle larger than any finite chord angle.
|
||||||
|
// The only valid operations on an InfChordAngle are comparisons and Angle conversions.
|
||||||
|
func InfChordAngle() ChordAngle {
|
||||||
|
return ChordAngle(math.Inf(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// isInf reports whether this ChordAngle is infinite.
|
||||||
|
func (c ChordAngle) isInf() bool {
|
||||||
|
return math.IsInf(float64(c), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSpecial reports whether this ChordAngle is one of the special cases.
|
||||||
|
func (c ChordAngle) isSpecial() bool {
|
||||||
|
return c < 0 || c.isInf()
|
||||||
|
}
|
||||||
|
|
||||||
|
// isValid reports whether this ChordAngle is valid or not.
|
||||||
|
func (c ChordAngle) isValid() bool {
|
||||||
|
return (c >= 0 && c <= 4) || c.isSpecial()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPointError returns the maximum error size for a ChordAngle constructed
|
||||||
|
// from 2 Points x and y, assuming that x and y are normalized to within the
|
||||||
|
// bounds guaranteed by s2.Point.Normalize. The error is defined with respect to
|
||||||
|
// the true distance after the points are projected to lie exactly on the sphere.
|
||||||
|
func (c ChordAngle) MaxPointError() float64 {
|
||||||
|
// There is a relative error of (2.5*dblEpsilon) when computing the squared
|
||||||
|
// distance, plus an absolute error of (16 * dblEpsilon**2) because the
|
||||||
|
// lengths of the input points may differ from 1 by up to (2*dblEpsilon) each.
|
||||||
|
return 2.5*dblEpsilon*float64(c) + 16*dblEpsilon*dblEpsilon
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxAngleError returns the maximum error for a ChordAngle constructed
|
||||||
|
// as an Angle distance.
|
||||||
|
func (c ChordAngle) MaxAngleError() float64 {
|
||||||
|
return dblEpsilon * float64(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds the other ChordAngle to this one and returns the resulting value.
|
||||||
|
// This method assumes the ChordAngles are not special.
|
||||||
|
func (c ChordAngle) Add(other ChordAngle) ChordAngle {
|
||||||
|
// Note that this method (and Sub) is much more efficient than converting
|
||||||
|
// the ChordAngle to an Angle and adding those and converting back. It
|
||||||
|
// requires only one square root plus a few additions and multiplications.
|
||||||
|
|
||||||
|
// Optimization for the common case where b is an error tolerance
|
||||||
|
// parameter that happens to be set to zero.
|
||||||
|
if other == 0 {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamp the angle sum to at most 180 degrees.
|
||||||
|
if c+other >= 4 {
|
||||||
|
return StraightChordAngle
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let a and b be the (non-squared) chord lengths, and let c = a+b.
|
||||||
|
// Let A, B, and C be the corresponding half-angles (a = 2*sin(A), etc).
|
||||||
|
// Then the formula below can be derived from c = 2 * sin(A+B) and the
|
||||||
|
// relationships sin(A+B) = sin(A)*cos(B) + sin(B)*cos(A)
|
||||||
|
// cos(X) = sqrt(1 - sin^2(X))
|
||||||
|
x := float64(c * (1 - 0.25*other))
|
||||||
|
y := float64(other * (1 - 0.25*c))
|
||||||
|
return ChordAngle(math.Min(4.0, x+y+2*math.Sqrt(x*y)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub subtracts the other ChordAngle from this one and returns the resulting
|
||||||
|
// value. This method assumes the ChordAngles are not special.
|
||||||
|
func (c ChordAngle) Sub(other ChordAngle) ChordAngle {
|
||||||
|
if other == 0 {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
if c <= other {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
x := float64(c * (1 - 0.25*other))
|
||||||
|
y := float64(other * (1 - 0.25*c))
|
||||||
|
return ChordAngle(math.Max(0.0, x+y-2*math.Sqrt(x*y)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sin returns the sine of this chord angle. This method is more efficient
|
||||||
|
// than converting to Angle and performing the computation.
|
||||||
|
func (c ChordAngle) Sin() float64 {
|
||||||
|
return math.Sqrt(c.Sin2())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sin2 returns the square of the sine of this chord angle.
|
||||||
|
// It is more efficient than Sin.
|
||||||
|
func (c ChordAngle) Sin2() float64 {
|
||||||
|
// Let a be the (non-squared) chord length, and let A be the corresponding
|
||||||
|
// half-angle (a = 2*sin(A)). The formula below can be derived from:
|
||||||
|
// sin(2*A) = 2 * sin(A) * cos(A)
|
||||||
|
// cos^2(A) = 1 - sin^2(A)
|
||||||
|
// This is much faster than converting to an angle and computing its sine.
|
||||||
|
return float64(c * (1 - 0.25*c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cos returns the cosine of this chord angle. This method is more efficient
|
||||||
|
// than converting to Angle and performing the computation.
|
||||||
|
func (c ChordAngle) Cos() float64 {
|
||||||
|
// cos(2*A) = cos^2(A) - sin^2(A) = 1 - 2*sin^2(A)
|
||||||
|
return float64(1 - 0.5*c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tan returns the tangent of this chord angle.
|
||||||
|
func (c ChordAngle) Tan() float64 {
|
||||||
|
return c.Sin() / c.Cos()
|
||||||
|
}
|
22
vendor/github.com/golang/geo/s1/doc.go
generated
vendored
Normal file
22
vendor/github.com/golang/geo/s1/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package s1 implements types and functions for working with geometry in S¹ (circular geometry).
|
||||||
|
|
||||||
|
See ../s2 for a more detailed overview.
|
||||||
|
*/
|
||||||
|
package s1
|
350
vendor/github.com/golang/geo/s1/interval.go
generated
vendored
Normal file
350
vendor/github.com/golang/geo/s1/interval.go
generated
vendored
Normal file
|
@ -0,0 +1,350 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package s1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Interval represents a closed interval on a unit circle.
|
||||||
|
// Zero-length intervals (where Lo == Hi) represent single points.
|
||||||
|
// If Lo > Hi then the interval is "inverted".
|
||||||
|
// The point at (-1, 0) on the unit circle has two valid representations,
|
||||||
|
// [π,π] and [-π,-π]. We normalize the latter to the former in IntervalFromEndpoints.
|
||||||
|
// There are two special intervals that take advantage of that:
|
||||||
|
// - the full interval, [-π,π], and
|
||||||
|
// - the empty interval, [π,-π].
|
||||||
|
// Treat the exported fields as read-only.
|
||||||
|
type Interval struct {
|
||||||
|
Lo, Hi float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntervalFromEndpoints constructs a new interval from endpoints.
|
||||||
|
// Both arguments must be in the range [-π,π]. This function allows inverted intervals
|
||||||
|
// to be created.
|
||||||
|
func IntervalFromEndpoints(lo, hi float64) Interval {
|
||||||
|
i := Interval{lo, hi}
|
||||||
|
if lo == -math.Pi && hi != math.Pi {
|
||||||
|
i.Lo = math.Pi
|
||||||
|
}
|
||||||
|
if hi == -math.Pi && lo != math.Pi {
|
||||||
|
i.Hi = math.Pi
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntervalFromPointPair returns the minimal interval containing the two given points.
|
||||||
|
// Both arguments must be in [-π,π].
|
||||||
|
func IntervalFromPointPair(a, b float64) Interval {
|
||||||
|
if a == -math.Pi {
|
||||||
|
a = math.Pi
|
||||||
|
}
|
||||||
|
if b == -math.Pi {
|
||||||
|
b = math.Pi
|
||||||
|
}
|
||||||
|
if positiveDistance(a, b) <= math.Pi {
|
||||||
|
return Interval{a, b}
|
||||||
|
}
|
||||||
|
return Interval{b, a}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmptyInterval returns an empty interval.
|
||||||
|
func EmptyInterval() Interval { return Interval{math.Pi, -math.Pi} }
|
||||||
|
|
||||||
|
// FullInterval returns a full interval.
|
||||||
|
func FullInterval() Interval { return Interval{-math.Pi, math.Pi} }
|
||||||
|
|
||||||
|
// IsValid reports whether the interval is valid.
|
||||||
|
func (i Interval) IsValid() bool {
|
||||||
|
return (math.Abs(i.Lo) <= math.Pi && math.Abs(i.Hi) <= math.Pi &&
|
||||||
|
!(i.Lo == -math.Pi && i.Hi != math.Pi) &&
|
||||||
|
!(i.Hi == -math.Pi && i.Lo != math.Pi))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsFull reports whether the interval is full.
|
||||||
|
func (i Interval) IsFull() bool { return i.Lo == -math.Pi && i.Hi == math.Pi }
|
||||||
|
|
||||||
|
// IsEmpty reports whether the interval is empty.
|
||||||
|
func (i Interval) IsEmpty() bool { return i.Lo == math.Pi && i.Hi == -math.Pi }
|
||||||
|
|
||||||
|
// IsInverted reports whether the interval is inverted; that is, whether Lo > Hi.
|
||||||
|
func (i Interval) IsInverted() bool { return i.Lo > i.Hi }
|
||||||
|
|
||||||
|
// Invert returns the interval with endpoints swapped.
|
||||||
|
func (i Interval) Invert() Interval {
|
||||||
|
return Interval{i.Hi, i.Lo}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center returns the midpoint of the interval.
|
||||||
|
// It is undefined for full and empty intervals.
|
||||||
|
func (i Interval) Center() float64 {
|
||||||
|
c := 0.5 * (i.Lo + i.Hi)
|
||||||
|
if !i.IsInverted() {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
if c <= 0 {
|
||||||
|
return c + math.Pi
|
||||||
|
}
|
||||||
|
return c - math.Pi
|
||||||
|
}
|
||||||
|
|
||||||
|
// Length returns the length of the interval.
|
||||||
|
// The length of an empty interval is negative.
|
||||||
|
func (i Interval) Length() float64 {
|
||||||
|
l := i.Hi - i.Lo
|
||||||
|
if l >= 0 {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
l += 2 * math.Pi
|
||||||
|
if l > 0 {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assumes p ∈ (-π,π].
|
||||||
|
func (i Interval) fastContains(p float64) bool {
|
||||||
|
if i.IsInverted() {
|
||||||
|
return (p >= i.Lo || p <= i.Hi) && !i.IsEmpty()
|
||||||
|
}
|
||||||
|
return p >= i.Lo && p <= i.Hi
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains returns true iff the interval contains p.
|
||||||
|
// Assumes p ∈ [-π,π].
|
||||||
|
func (i Interval) Contains(p float64) bool {
|
||||||
|
if p == -math.Pi {
|
||||||
|
p = math.Pi
|
||||||
|
}
|
||||||
|
return i.fastContains(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainsInterval returns true iff the interval contains oi.
|
||||||
|
func (i Interval) ContainsInterval(oi Interval) bool {
|
||||||
|
if i.IsInverted() {
|
||||||
|
if oi.IsInverted() {
|
||||||
|
return oi.Lo >= i.Lo && oi.Hi <= i.Hi
|
||||||
|
}
|
||||||
|
return (oi.Lo >= i.Lo || oi.Hi <= i.Hi) && !i.IsEmpty()
|
||||||
|
}
|
||||||
|
if oi.IsInverted() {
|
||||||
|
return i.IsFull() || oi.IsEmpty()
|
||||||
|
}
|
||||||
|
return oi.Lo >= i.Lo && oi.Hi <= i.Hi
|
||||||
|
}
|
||||||
|
|
||||||
|
// InteriorContains returns true iff the interior of the interval contains p.
|
||||||
|
// Assumes p ∈ [-π,π].
|
||||||
|
func (i Interval) InteriorContains(p float64) bool {
|
||||||
|
if p == -math.Pi {
|
||||||
|
p = math.Pi
|
||||||
|
}
|
||||||
|
if i.IsInverted() {
|
||||||
|
return p > i.Lo || p < i.Hi
|
||||||
|
}
|
||||||
|
return (p > i.Lo && p < i.Hi) || i.IsFull()
|
||||||
|
}
|
||||||
|
|
||||||
|
// InteriorContainsInterval returns true iff the interior of the interval contains oi.
|
||||||
|
func (i Interval) InteriorContainsInterval(oi Interval) bool {
|
||||||
|
if i.IsInverted() {
|
||||||
|
if oi.IsInverted() {
|
||||||
|
return (oi.Lo > i.Lo && oi.Hi < i.Hi) || oi.IsEmpty()
|
||||||
|
}
|
||||||
|
return oi.Lo > i.Lo || oi.Hi < i.Hi
|
||||||
|
}
|
||||||
|
if oi.IsInverted() {
|
||||||
|
return i.IsFull() || oi.IsEmpty()
|
||||||
|
}
|
||||||
|
return (oi.Lo > i.Lo && oi.Hi < i.Hi) || i.IsFull()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intersects returns true iff the interval contains any points in common with oi.
|
||||||
|
func (i Interval) Intersects(oi Interval) bool {
|
||||||
|
if i.IsEmpty() || oi.IsEmpty() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if i.IsInverted() {
|
||||||
|
return oi.IsInverted() || oi.Lo <= i.Hi || oi.Hi >= i.Lo
|
||||||
|
}
|
||||||
|
if oi.IsInverted() {
|
||||||
|
return oi.Lo <= i.Hi || oi.Hi >= i.Lo
|
||||||
|
}
|
||||||
|
return oi.Lo <= i.Hi && oi.Hi >= i.Lo
|
||||||
|
}
|
||||||
|
|
||||||
|
// InteriorIntersects returns true iff the interior of the interval contains any points in common with oi, including the latter's boundary.
|
||||||
|
func (i Interval) InteriorIntersects(oi Interval) bool {
|
||||||
|
if i.IsEmpty() || oi.IsEmpty() || i.Lo == i.Hi {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if i.IsInverted() {
|
||||||
|
return oi.IsInverted() || oi.Lo < i.Hi || oi.Hi > i.Lo
|
||||||
|
}
|
||||||
|
if oi.IsInverted() {
|
||||||
|
return oi.Lo < i.Hi || oi.Hi > i.Lo
|
||||||
|
}
|
||||||
|
return (oi.Lo < i.Hi && oi.Hi > i.Lo) || i.IsFull()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute distance from a to b in [0,2π], in a numerically stable way.
|
||||||
|
func positiveDistance(a, b float64) float64 {
|
||||||
|
d := b - a
|
||||||
|
if d >= 0 {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
return (b + math.Pi) - (a - math.Pi)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Union returns the smallest interval that contains both the interval and oi.
|
||||||
|
func (i Interval) Union(oi Interval) Interval {
|
||||||
|
if oi.IsEmpty() {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
if i.fastContains(oi.Lo) {
|
||||||
|
if i.fastContains(oi.Hi) {
|
||||||
|
// Either oi ⊂ i, or i ∪ oi is the full interval.
|
||||||
|
if i.ContainsInterval(oi) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
return FullInterval()
|
||||||
|
}
|
||||||
|
return Interval{i.Lo, oi.Hi}
|
||||||
|
}
|
||||||
|
if i.fastContains(oi.Hi) {
|
||||||
|
return Interval{oi.Lo, i.Hi}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neither endpoint of oi is in i. Either i ⊂ oi, or i and oi are disjoint.
|
||||||
|
if i.IsEmpty() || oi.fastContains(i.Lo) {
|
||||||
|
return oi
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the only hard case where we need to find the closest pair of endpoints.
|
||||||
|
if positiveDistance(oi.Hi, i.Lo) < positiveDistance(i.Hi, oi.Lo) {
|
||||||
|
return Interval{oi.Lo, i.Hi}
|
||||||
|
}
|
||||||
|
return Interval{i.Lo, oi.Hi}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intersection returns the smallest interval that contains the intersection of the interval and oi.
|
||||||
|
func (i Interval) Intersection(oi Interval) Interval {
|
||||||
|
if oi.IsEmpty() {
|
||||||
|
return EmptyInterval()
|
||||||
|
}
|
||||||
|
if i.fastContains(oi.Lo) {
|
||||||
|
if i.fastContains(oi.Hi) {
|
||||||
|
// Either oi ⊂ i, or i and oi intersect twice. Neither are empty.
|
||||||
|
// In the first case we want to return i (which is shorter than oi).
|
||||||
|
// In the second case one of them is inverted, and the smallest interval
|
||||||
|
// that covers the two disjoint pieces is the shorter of i and oi.
|
||||||
|
// We thus want to pick the shorter of i and oi in both cases.
|
||||||
|
if oi.Length() < i.Length() {
|
||||||
|
return oi
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
return Interval{oi.Lo, i.Hi}
|
||||||
|
}
|
||||||
|
if i.fastContains(oi.Hi) {
|
||||||
|
return Interval{i.Lo, oi.Hi}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neither endpoint of oi is in i. Either i ⊂ oi, or i and oi are disjoint.
|
||||||
|
if oi.fastContains(i.Lo) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
return EmptyInterval()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPoint returns the interval expanded by the minimum amount necessary such
|
||||||
|
// that it contains the given point "p" (an angle in the range [-Pi, Pi]).
|
||||||
|
func (i Interval) AddPoint(p float64) Interval {
|
||||||
|
if math.Abs(p) > math.Pi {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
if p == -math.Pi {
|
||||||
|
p = math.Pi
|
||||||
|
}
|
||||||
|
if i.fastContains(p) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
if i.IsEmpty() {
|
||||||
|
return Interval{p, p}
|
||||||
|
}
|
||||||
|
if positiveDistance(p, i.Lo) < positiveDistance(i.Hi, p) {
|
||||||
|
return Interval{p, i.Hi}
|
||||||
|
}
|
||||||
|
return Interval{i.Lo, p}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the maximum rounding error for arithmetic operations. Depending on the
|
||||||
|
// platform the mantissa precision may be different than others, so we choose to
|
||||||
|
// use specific values to be consistent across all.
|
||||||
|
// The values come from the C++ implementation.
|
||||||
|
var (
|
||||||
|
// epsilon is a small number that represents a reasonable level of noise between two
|
||||||
|
// values that can be considered to be equal.
|
||||||
|
epsilon = 1e-15
|
||||||
|
// dblEpsilon is a smaller number for values that require more precision.
|
||||||
|
dblEpsilon = 2.220446049e-16
|
||||||
|
)
|
||||||
|
|
||||||
|
// Expanded returns an interval that has been expanded on each side by margin.
|
||||||
|
// If margin is negative, then the function shrinks the interval on
|
||||||
|
// each side by margin instead. The resulting interval may be empty or
|
||||||
|
// full. Any expansion (positive or negative) of a full interval remains
|
||||||
|
// full, and any expansion of an empty interval remains empty.
|
||||||
|
func (i Interval) Expanded(margin float64) Interval {
|
||||||
|
if margin >= 0 {
|
||||||
|
if i.IsEmpty() {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
// Check whether this interval will be full after expansion, allowing
|
||||||
|
// for a rounding error when computing each endpoint.
|
||||||
|
if i.Length()+2*margin+2*dblEpsilon >= 2*math.Pi {
|
||||||
|
return FullInterval()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if i.IsFull() {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
// Check whether this interval will be empty after expansion, allowing
|
||||||
|
// for a rounding error when computing each endpoint.
|
||||||
|
if i.Length()+2*margin-2*dblEpsilon <= 0 {
|
||||||
|
return EmptyInterval()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result := IntervalFromEndpoints(
|
||||||
|
math.Remainder(i.Lo-margin, 2*math.Pi),
|
||||||
|
math.Remainder(i.Hi+margin, 2*math.Pi),
|
||||||
|
)
|
||||||
|
if result.Lo <= -math.Pi {
|
||||||
|
result.Lo = math.Pi
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Interval) String() string {
|
||||||
|
// like "[%.7f, %.7f]"
|
||||||
|
return "[" + strconv.FormatFloat(i.Lo, 'f', 7, 64) + ", " + strconv.FormatFloat(i.Hi, 'f', 7, 64) + "]"
|
||||||
|
}
|
||||||
|
|
||||||
|
// BUG(dsymonds): The major differences from the C++ version are:
|
||||||
|
// - no validity checking on construction, etc. (not a bug?)
|
||||||
|
// - a few operations
|
468
vendor/github.com/golang/geo/s2/cap.go
generated
vendored
Normal file
468
vendor/github.com/golang/geo/s2/cap.go
generated
vendored
Normal file
|
@ -0,0 +1,468 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package s2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/golang/geo/r1"
|
||||||
|
"github.com/golang/geo/s1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// centerPoint is the default center for Caps
|
||||||
|
centerPoint = PointFromCoords(1.0, 0, 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cap represents a disc-shaped region defined by a center and radius.
|
||||||
|
// Technically this shape is called a "spherical cap" (rather than disc)
|
||||||
|
// because it is not planar; the cap represents a portion of the sphere that
|
||||||
|
// has been cut off by a plane. The boundary of the cap is the circle defined
|
||||||
|
// by the intersection of the sphere and the plane. For containment purposes,
|
||||||
|
// the cap is a closed set, i.e. it contains its boundary.
|
||||||
|
//
|
||||||
|
// For the most part, you can use a spherical cap wherever you would use a
|
||||||
|
// disc in planar geometry. The radius of the cap is measured along the
|
||||||
|
// surface of the sphere (rather than the straight-line distance through the
|
||||||
|
// interior). Thus a cap of radius π/2 is a hemisphere, and a cap of radius
|
||||||
|
// π covers the entire sphere.
|
||||||
|
//
|
||||||
|
// The center is a point on the surface of the unit sphere. (Hence the need for
|
||||||
|
// it to be of unit length.)
|
||||||
|
//
|
||||||
|
// A cap can also be defined by its center point and height. The height is the
|
||||||
|
// distance from the center point to the cutoff plane. There is also support for
|
||||||
|
// "empty" and "full" caps, which contain no points and all points respectively.
|
||||||
|
//
|
||||||
|
// Here are some useful relationships between the cap height (h), the cap
|
||||||
|
// radius (r), the maximum chord length from the cap's center (d), and the
|
||||||
|
// radius of cap's base (a).
|
||||||
|
//
|
||||||
|
// h = 1 - cos(r)
|
||||||
|
// = 2 * sin^2(r/2)
|
||||||
|
// d^2 = 2 * h
|
||||||
|
// = a^2 + h^2
|
||||||
|
//
|
||||||
|
// The zero value of Cap is an invalid cap. Use EmptyCap to get a valid empty cap.
|
||||||
|
type Cap struct {
|
||||||
|
center Point
|
||||||
|
radius s1.ChordAngle
|
||||||
|
}
|
||||||
|
|
||||||
|
// CapFromPoint constructs a cap containing a single point.
|
||||||
|
func CapFromPoint(p Point) Cap {
|
||||||
|
return CapFromCenterChordAngle(p, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CapFromCenterAngle constructs a cap with the given center and angle.
|
||||||
|
func CapFromCenterAngle(center Point, angle s1.Angle) Cap {
|
||||||
|
return CapFromCenterChordAngle(center, s1.ChordAngleFromAngle(angle))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CapFromCenterChordAngle constructs a cap where the angle is expressed as an
|
||||||
|
// s1.ChordAngle. This constructor is more efficient than using an s1.Angle.
|
||||||
|
func CapFromCenterChordAngle(center Point, radius s1.ChordAngle) Cap {
|
||||||
|
return Cap{
|
||||||
|
center: center,
|
||||||
|
radius: radius,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CapFromCenterHeight constructs a cap with the given center and height. A
|
||||||
|
// negative height yields an empty cap; a height of 2 or more yields a full cap.
|
||||||
|
// The center should be unit length.
|
||||||
|
func CapFromCenterHeight(center Point, height float64) Cap {
|
||||||
|
return CapFromCenterChordAngle(center, s1.ChordAngleFromSquaredLength(2*height))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CapFromCenterArea constructs a cap with the given center and surface area.
|
||||||
|
// Note that the area can also be interpreted as the solid angle subtended by the
|
||||||
|
// cap (because the sphere has unit radius). A negative area yields an empty cap;
|
||||||
|
// an area of 4*π or more yields a full cap.
|
||||||
|
func CapFromCenterArea(center Point, area float64) Cap {
|
||||||
|
return CapFromCenterChordAngle(center, s1.ChordAngleFromSquaredLength(area/math.Pi))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmptyCap returns a cap that contains no points.
|
||||||
|
func EmptyCap() Cap {
|
||||||
|
return CapFromCenterChordAngle(centerPoint, s1.NegativeChordAngle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FullCap returns a cap that contains all points.
|
||||||
|
func FullCap() Cap {
|
||||||
|
return CapFromCenterChordAngle(centerPoint, s1.StraightChordAngle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid reports whether the Cap is considered valid.
|
||||||
|
func (c Cap) IsValid() bool {
|
||||||
|
return c.center.Vector.IsUnit() && c.radius <= s1.StraightChordAngle
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty reports whether the cap is empty, i.e. it contains no points.
|
||||||
|
func (c Cap) IsEmpty() bool {
|
||||||
|
return c.radius < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsFull reports whether the cap is full, i.e. it contains all points.
|
||||||
|
func (c Cap) IsFull() bool {
|
||||||
|
return c.radius == s1.StraightChordAngle
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center returns the cap's center point.
|
||||||
|
func (c Cap) Center() Point {
|
||||||
|
return c.center
|
||||||
|
}
|
||||||
|
|
||||||
|
// Height returns the height of the cap. This is the distance from the center
|
||||||
|
// point to the cutoff plane.
|
||||||
|
func (c Cap) Height() float64 {
|
||||||
|
return float64(0.5 * c.radius)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Radius returns the cap radius as an s1.Angle. (Note that the cap angle
|
||||||
|
// is stored internally as a ChordAngle, so this method requires a trigonometric
|
||||||
|
// operation and may yield a slightly different result than the value passed
|
||||||
|
// to CapFromCenterAngle).
|
||||||
|
func (c Cap) Radius() s1.Angle {
|
||||||
|
return c.radius.Angle()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Area returns the surface area of the Cap on the unit sphere.
|
||||||
|
func (c Cap) Area() float64 {
|
||||||
|
return 2.0 * math.Pi * math.Max(0, c.Height())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains reports whether this cap contains the other.
|
||||||
|
func (c Cap) Contains(other Cap) bool {
|
||||||
|
// In a set containment sense, every cap contains the empty cap.
|
||||||
|
if c.IsFull() || other.IsEmpty() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return c.radius >= ChordAngleBetweenPoints(c.center, other.center).Add(other.radius)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intersects reports whether this cap intersects the other cap.
|
||||||
|
// i.e. whether they have any points in common.
|
||||||
|
func (c Cap) Intersects(other Cap) bool {
|
||||||
|
if c.IsEmpty() || other.IsEmpty() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.radius.Add(other.radius) >= ChordAngleBetweenPoints(c.center, other.center)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InteriorIntersects reports whether this caps interior intersects the other cap.
|
||||||
|
func (c Cap) InteriorIntersects(other Cap) bool {
|
||||||
|
// Make sure this cap has an interior and the other cap is non-empty.
|
||||||
|
if c.radius <= 0 || other.IsEmpty() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.radius.Add(other.radius) > ChordAngleBetweenPoints(c.center, other.center)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainsPoint reports whether this cap contains the point.
|
||||||
|
func (c Cap) ContainsPoint(p Point) bool {
|
||||||
|
return ChordAngleBetweenPoints(c.center, p) <= c.radius
|
||||||
|
}
|
||||||
|
|
||||||
|
// InteriorContainsPoint reports whether the point is within the interior of this cap.
|
||||||
|
func (c Cap) InteriorContainsPoint(p Point) bool {
|
||||||
|
return c.IsFull() || ChordAngleBetweenPoints(c.center, p) < c.radius
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complement returns the complement of the interior of the cap. A cap and its
|
||||||
|
// complement have the same boundary but do not share any interior points.
|
||||||
|
// The complement operator is not a bijection because the complement of a
|
||||||
|
// singleton cap (containing a single point) is the same as the complement
|
||||||
|
// of an empty cap.
|
||||||
|
func (c Cap) Complement() Cap {
|
||||||
|
if c.IsFull() {
|
||||||
|
return EmptyCap()
|
||||||
|
}
|
||||||
|
if c.IsEmpty() {
|
||||||
|
return FullCap()
|
||||||
|
}
|
||||||
|
|
||||||
|
return CapFromCenterChordAngle(Point{c.center.Mul(-1)}, s1.StraightChordAngle.Sub(c.radius))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CapBound returns a bounding spherical cap. This is not guaranteed to be exact.
|
||||||
|
func (c Cap) CapBound() Cap {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// RectBound returns a bounding latitude-longitude rectangle.
|
||||||
|
// The bounds are not guaranteed to be tight.
|
||||||
|
func (c Cap) RectBound() Rect {
|
||||||
|
if c.IsEmpty() {
|
||||||
|
return EmptyRect()
|
||||||
|
}
|
||||||
|
|
||||||
|
capAngle := c.Radius().Radians()
|
||||||
|
allLongitudes := false
|
||||||
|
lat := r1.Interval{
|
||||||
|
Lo: latitude(c.center).Radians() - capAngle,
|
||||||
|
Hi: latitude(c.center).Radians() + capAngle,
|
||||||
|
}
|
||||||
|
lng := s1.FullInterval()
|
||||||
|
|
||||||
|
// Check whether cap includes the south pole.
|
||||||
|
if lat.Lo <= -math.Pi/2 {
|
||||||
|
lat.Lo = -math.Pi / 2
|
||||||
|
allLongitudes = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether cap includes the north pole.
|
||||||
|
if lat.Hi >= math.Pi/2 {
|
||||||
|
lat.Hi = math.Pi / 2
|
||||||
|
allLongitudes = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allLongitudes {
|
||||||
|
// Compute the range of longitudes covered by the cap. We use the law
|
||||||
|
// of sines for spherical triangles. Consider the triangle ABC where
|
||||||
|
// A is the north pole, B is the center of the cap, and C is the point
|
||||||
|
// of tangency between the cap boundary and a line of longitude. Then
|
||||||
|
// C is a right angle, and letting a,b,c denote the sides opposite A,B,C,
|
||||||
|
// we have sin(a)/sin(A) = sin(c)/sin(C), or sin(A) = sin(a)/sin(c).
|
||||||
|
// Here "a" is the cap angle, and "c" is the colatitude (90 degrees
|
||||||
|
// minus the latitude). This formula also works for negative latitudes.
|
||||||
|
//
|
||||||
|
// The formula for sin(a) follows from the relationship h = 1 - cos(a).
|
||||||
|
sinA := c.radius.Sin()
|
||||||
|
sinC := math.Cos(latitude(c.center).Radians())
|
||||||
|
if sinA <= sinC {
|
||||||
|
angleA := math.Asin(sinA / sinC)
|
||||||
|
lng.Lo = math.Remainder(longitude(c.center).Radians()-angleA, math.Pi*2)
|
||||||
|
lng.Hi = math.Remainder(longitude(c.center).Radians()+angleA, math.Pi*2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Rect{lat, lng}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal reports whether this cap is equal to the other cap.
|
||||||
|
func (c Cap) Equal(other Cap) bool {
|
||||||
|
return (c.radius == other.radius && c.center == other.center) ||
|
||||||
|
(c.IsEmpty() && other.IsEmpty()) ||
|
||||||
|
(c.IsFull() && other.IsFull())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApproxEqual reports whether this cap is equal to the other cap within the given tolerance.
|
||||||
|
func (c Cap) ApproxEqual(other Cap) bool {
|
||||||
|
const epsilon = 1e-14
|
||||||
|
r2 := float64(c.radius)
|
||||||
|
otherR2 := float64(other.radius)
|
||||||
|
return c.center.ApproxEqual(other.center) &&
|
||||||
|
math.Abs(r2-otherR2) <= epsilon ||
|
||||||
|
c.IsEmpty() && otherR2 <= epsilon ||
|
||||||
|
other.IsEmpty() && r2 <= epsilon ||
|
||||||
|
c.IsFull() && otherR2 >= 2-epsilon ||
|
||||||
|
other.IsFull() && r2 >= 2-epsilon
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPoint increases the cap if necessary to include the given point. If this cap is empty,
|
||||||
|
// then the center is set to the point with a zero height. p must be unit-length.
|
||||||
|
func (c Cap) AddPoint(p Point) Cap {
|
||||||
|
if c.IsEmpty() {
|
||||||
|
c.center = p
|
||||||
|
c.radius = 0
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// After calling cap.AddPoint(p), cap.Contains(p) must be true. However
|
||||||
|
// we don't need to do anything special to achieve this because Contains()
|
||||||
|
// does exactly the same distance calculation that we do here.
|
||||||
|
if newRad := ChordAngleBetweenPoints(c.center, p); newRad > c.radius {
|
||||||
|
c.radius = newRad
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCap increases the cap height if necessary to include the other cap. If this cap is empty,
|
||||||
|
// it is set to the other cap.
|
||||||
|
func (c Cap) AddCap(other Cap) Cap {
|
||||||
|
if c.IsEmpty() {
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
if other.IsEmpty() {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// We round up the distance to ensure that the cap is actually contained.
|
||||||
|
// TODO(roberts): Do some error analysis in order to guarantee this.
|
||||||
|
dist := ChordAngleBetweenPoints(c.center, other.center).Add(other.radius)
|
||||||
|
if newRad := dist.Expanded(dblEpsilon * float64(dist)); newRad > c.radius {
|
||||||
|
c.radius = newRad
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expanded returns a new cap expanded by the given angle. If the cap is empty,
|
||||||
|
// it returns an empty cap.
|
||||||
|
func (c Cap) Expanded(distance s1.Angle) Cap {
|
||||||
|
if c.IsEmpty() {
|
||||||
|
return EmptyCap()
|
||||||
|
}
|
||||||
|
return CapFromCenterChordAngle(c.center, c.radius.Add(s1.ChordAngleFromAngle(distance)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Cap) String() string {
|
||||||
|
return fmt.Sprintf("[Center=%v, Radius=%f]", c.center.Vector, c.Radius().Degrees())
|
||||||
|
}
|
||||||
|
|
||||||
|
// radiusToHeight converts an s1.Angle into the height of the cap.
|
||||||
|
func radiusToHeight(r s1.Angle) float64 {
|
||||||
|
if r.Radians() < 0 {
|
||||||
|
return float64(s1.NegativeChordAngle)
|
||||||
|
}
|
||||||
|
if r.Radians() >= math.Pi {
|
||||||
|
return float64(s1.RightChordAngle)
|
||||||
|
}
|
||||||
|
return float64(0.5 * s1.ChordAngleFromAngle(r))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainsCell reports whether the cap contains the given cell.
|
||||||
|
func (c Cap) ContainsCell(cell Cell) bool {
|
||||||
|
// If the cap does not contain all cell vertices, return false.
|
||||||
|
var vertices [4]Point
|
||||||
|
for k := 0; k < 4; k++ {
|
||||||
|
vertices[k] = cell.Vertex(k)
|
||||||
|
if !c.ContainsPoint(vertices[k]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Otherwise, return true if the complement of the cap does not intersect the cell.
|
||||||
|
return !c.Complement().intersects(cell, vertices)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntersectsCell reports whether the cap intersects the cell.
|
||||||
|
func (c Cap) IntersectsCell(cell Cell) bool {
|
||||||
|
// If the cap contains any cell vertex, return true.
|
||||||
|
var vertices [4]Point
|
||||||
|
for k := 0; k < 4; k++ {
|
||||||
|
vertices[k] = cell.Vertex(k)
|
||||||
|
if c.ContainsPoint(vertices[k]) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.intersects(cell, vertices)
|
||||||
|
}
|
||||||
|
|
||||||
|
// intersects reports whether the cap intersects any point of the cell excluding
|
||||||
|
// its vertices (which are assumed to already have been checked).
|
||||||
|
func (c Cap) intersects(cell Cell, vertices [4]Point) bool {
|
||||||
|
// If the cap is a hemisphere or larger, the cell and the complement of the cap
|
||||||
|
// are both convex. Therefore since no vertex of the cell is contained, no other
|
||||||
|
// interior point of the cell is contained either.
|
||||||
|
if c.radius >= s1.RightChordAngle {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to check for empty caps due to the center check just below.
|
||||||
|
if c.IsEmpty() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimization: return true if the cell contains the cap center. This allows half
|
||||||
|
// of the edge checks below to be skipped.
|
||||||
|
if cell.ContainsPoint(c.center) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point we know that the cell does not contain the cap center, and the cap
|
||||||
|
// does not contain any cell vertex. The only way that they can intersect is if the
|
||||||
|
// cap intersects the interior of some edge.
|
||||||
|
sin2Angle := c.radius.Sin2()
|
||||||
|
for k := 0; k < 4; k++ {
|
||||||
|
edge := cell.Edge(k).Vector
|
||||||
|
dot := c.center.Vector.Dot(edge)
|
||||||
|
if dot > 0 {
|
||||||
|
// The center is in the interior half-space defined by the edge. We do not need
|
||||||
|
// to consider these edges, since if the cap intersects this edge then it also
|
||||||
|
// intersects the edge on the opposite side of the cell, because the center is
|
||||||
|
// not contained with the cell.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Norm2() factor is necessary because "edge" is not normalized.
|
||||||
|
if dot*dot > sin2Angle*edge.Norm2() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, the great circle containing this edge intersects the interior of the cap. We just
|
||||||
|
// need to check whether the point of closest approach occurs between the two edge endpoints.
|
||||||
|
dir := edge.Cross(c.center.Vector)
|
||||||
|
if dir.Dot(vertices[k].Vector) < 0 && dir.Dot(vertices[(k+1)&3].Vector) > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Centroid returns the true centroid of the cap multiplied by its surface area
|
||||||
|
// The result lies on the ray from the origin through the cap's center, but it
|
||||||
|
// is not unit length. Note that if you just want the "surface centroid", i.e.
|
||||||
|
// the normalized result, then it is simpler to call Center.
|
||||||
|
//
|
||||||
|
// The reason for multiplying the result by the cap area is to make it
|
||||||
|
// easier to compute the centroid of more complicated shapes. The centroid
|
||||||
|
// of a union of disjoint regions can be computed simply by adding their
|
||||||
|
// Centroid() results. Caveat: for caps that contain a single point
|
||||||
|
// (i.e., zero radius), this method always returns the origin (0, 0, 0).
|
||||||
|
// This is because shapes with no area don't affect the centroid of a
|
||||||
|
// union whose total area is positive.
|
||||||
|
func (c Cap) Centroid() Point {
|
||||||
|
// From symmetry, the centroid of the cap must be somewhere on the line
|
||||||
|
// from the origin to the center of the cap on the surface of the sphere.
|
||||||
|
// When a sphere is divided into slices of constant thickness by a set of
|
||||||
|
// parallel planes, all slices have the same surface area. This implies
|
||||||
|
// that the radial component of the centroid is simply the midpoint of the
|
||||||
|
// range of radial distances spanned by the cap. That is easily computed
|
||||||
|
// from the cap height.
|
||||||
|
if c.IsEmpty() {
|
||||||
|
return Point{}
|
||||||
|
}
|
||||||
|
r := 1 - 0.5*c.Height()
|
||||||
|
return Point{c.center.Mul(r * c.Area())}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Union returns the smallest cap which encloses this cap and other.
|
||||||
|
func (c Cap) Union(other Cap) Cap {
|
||||||
|
// If the other cap is larger, swap c and other for the rest of the computations.
|
||||||
|
if c.radius < other.radius {
|
||||||
|
c, other = other, c
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.IsFull() || other.IsEmpty() {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This calculation would be more efficient using s1.ChordAngles.
|
||||||
|
cRadius := c.Radius()
|
||||||
|
otherRadius := other.Radius()
|
||||||
|
distance := c.center.Distance(other.center)
|
||||||
|
if cRadius >= distance+otherRadius {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
resRadius := 0.5 * (distance + cRadius + otherRadius)
|
||||||
|
resCenter := InterpolateAtDistance(0.5*(distance-cRadius+otherRadius), c.center, other.center)
|
||||||
|
return CapFromCenterAngle(resCenter, resRadius)
|
||||||
|
}
|
385
vendor/github.com/golang/geo/s2/cell.go
generated
vendored
Normal file
385
vendor/github.com/golang/geo/s2/cell.go
generated
vendored
Normal file
|
@ -0,0 +1,385 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package s2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/golang/geo/r1"
|
||||||
|
"github.com/golang/geo/r2"
|
||||||
|
"github.com/golang/geo/s1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cell is an S2 region object that represents a cell. Unlike CellIDs,
|
||||||
|
// it supports efficient containment and intersection tests. However, it is
|
||||||
|
// also a more expensive representation.
|
||||||
|
type Cell struct {
|
||||||
|
face int8
|
||||||
|
level int8
|
||||||
|
orientation int8
|
||||||
|
id CellID
|
||||||
|
uv r2.Rect
|
||||||
|
}
|
||||||
|
|
||||||
|
// CellFromCellID constructs a Cell corresponding to the given CellID.
|
||||||
|
func CellFromCellID(id CellID) Cell {
|
||||||
|
c := Cell{}
|
||||||
|
c.id = id
|
||||||
|
f, i, j, o := c.id.faceIJOrientation()
|
||||||
|
c.face = int8(f)
|
||||||
|
c.level = int8(c.id.Level())
|
||||||
|
c.orientation = int8(o)
|
||||||
|
c.uv = ijLevelToBoundUV(i, j, int(c.level))
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// CellFromPoint constructs a cell for the given Point.
|
||||||
|
func CellFromPoint(p Point) Cell {
|
||||||
|
return CellFromCellID(cellIDFromPoint(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CellFromLatLng constructs a cell for the given LatLng.
|
||||||
|
func CellFromLatLng(ll LatLng) Cell {
|
||||||
|
return CellFromCellID(CellIDFromLatLng(ll))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Face returns the face this cell is on.
|
||||||
|
func (c Cell) Face() int {
|
||||||
|
return int(c.face)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Level returns the level of this cell.
|
||||||
|
func (c Cell) Level() int {
|
||||||
|
return int(c.level)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID returns the CellID this cell represents.
|
||||||
|
func (c Cell) ID() CellID {
|
||||||
|
return c.id
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLeaf returns whether this Cell is a leaf or not.
|
||||||
|
func (c Cell) IsLeaf() bool {
|
||||||
|
return c.level == maxLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
// SizeIJ returns the CellID value for the cells level.
|
||||||
|
func (c Cell) SizeIJ() int {
|
||||||
|
return sizeIJ(int(c.level))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertex returns the k-th vertex of the cell (k = 0,1,2,3) in CCW order
|
||||||
|
// (lower left, lower right, upper right, upper left in the UV plane).
|
||||||
|
func (c Cell) Vertex(k int) Point {
|
||||||
|
return Point{faceUVToXYZ(int(c.face), c.uv.Vertices()[k].X, c.uv.Vertices()[k].Y).Normalize()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edge returns the inward-facing normal of the great circle passing through
|
||||||
|
// the CCW ordered edge from vertex k to vertex k+1 (mod 4) (for k = 0,1,2,3).
|
||||||
|
func (c Cell) Edge(k int) Point {
|
||||||
|
switch k {
|
||||||
|
case 0:
|
||||||
|
return Point{vNorm(int(c.face), c.uv.Y.Lo).Normalize()} // Bottom
|
||||||
|
case 1:
|
||||||
|
return Point{uNorm(int(c.face), c.uv.X.Hi).Normalize()} // Right
|
||||||
|
case 2:
|
||||||
|
return Point{vNorm(int(c.face), c.uv.Y.Hi).Mul(-1.0).Normalize()} // Top
|
||||||
|
default:
|
||||||
|
return Point{uNorm(int(c.face), c.uv.X.Lo).Mul(-1.0).Normalize()} // Left
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoundUV returns the bounds of this cell in (u,v)-space.
|
||||||
|
func (c Cell) BoundUV() r2.Rect {
|
||||||
|
return c.uv
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center returns the direction vector corresponding to the center in
|
||||||
|
// (s,t)-space of the given cell. This is the point at which the cell is
|
||||||
|
// divided into four subcells; it is not necessarily the centroid of the
|
||||||
|
// cell in (u,v)-space or (x,y,z)-space
|
||||||
|
func (c Cell) Center() Point {
|
||||||
|
return Point{c.id.rawPoint().Normalize()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Children returns the four direct children of this cell in traversal order
|
||||||
|
// and returns true. If this is a leaf cell, or the children could not be created,
|
||||||
|
// false is returned.
|
||||||
|
// The C++ method is called Subdivide.
|
||||||
|
func (c Cell) Children() ([4]Cell, bool) {
|
||||||
|
var children [4]Cell
|
||||||
|
|
||||||
|
if c.id.IsLeaf() {
|
||||||
|
return children, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the cell midpoint in uv-space.
|
||||||
|
uvMid := c.id.centerUV()
|
||||||
|
|
||||||
|
// Create four children with the appropriate bounds.
|
||||||
|
cid := c.id.ChildBegin()
|
||||||
|
for pos := 0; pos < 4; pos++ {
|
||||||
|
children[pos] = Cell{
|
||||||
|
face: c.face,
|
||||||
|
level: c.level + 1,
|
||||||
|
orientation: c.orientation ^ int8(posToOrientation[pos]),
|
||||||
|
id: cid,
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want to split the cell in half in u and v. To decide which
|
||||||
|
// side to set equal to the midpoint value, we look at cell's (i,j)
|
||||||
|
// position within its parent. The index for i is in bit 1 of ij.
|
||||||
|
ij := posToIJ[c.orientation][pos]
|
||||||
|
i := ij >> 1
|
||||||
|
j := ij & 1
|
||||||
|
if i == 1 {
|
||||||
|
children[pos].uv.X.Hi = c.uv.X.Hi
|
||||||
|
children[pos].uv.X.Lo = uvMid.X
|
||||||
|
} else {
|
||||||
|
children[pos].uv.X.Lo = c.uv.X.Lo
|
||||||
|
children[pos].uv.X.Hi = uvMid.X
|
||||||
|
}
|
||||||
|
if j == 1 {
|
||||||
|
children[pos].uv.Y.Hi = c.uv.Y.Hi
|
||||||
|
children[pos].uv.Y.Lo = uvMid.Y
|
||||||
|
} else {
|
||||||
|
children[pos].uv.Y.Lo = c.uv.Y.Lo
|
||||||
|
children[pos].uv.Y.Hi = uvMid.Y
|
||||||
|
}
|
||||||
|
cid = cid.Next()
|
||||||
|
}
|
||||||
|
return children, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExactArea returns the area of this cell as accurately as possible.
|
||||||
|
func (c Cell) ExactArea() float64 {
|
||||||
|
v0, v1, v2, v3 := c.Vertex(0), c.Vertex(1), c.Vertex(2), c.Vertex(3)
|
||||||
|
return PointArea(v0, v1, v2) + PointArea(v0, v2, v3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApproxArea returns the approximate area of this cell. This method is accurate
|
||||||
|
// to within 3% percent for all cell sizes and accurate to within 0.1% for cells
|
||||||
|
// at level 5 or higher (i.e. squares 350km to a side or smaller on the Earth's
|
||||||
|
// surface). It is moderately cheap to compute.
|
||||||
|
func (c Cell) ApproxArea() float64 {
|
||||||
|
// All cells at the first two levels have the same area.
|
||||||
|
if c.level < 2 {
|
||||||
|
return c.AverageArea()
|
||||||
|
}
|
||||||
|
|
||||||
|
// First, compute the approximate area of the cell when projected
|
||||||
|
// perpendicular to its normal. The cross product of its diagonals gives
|
||||||
|
// the normal, and the length of the normal is twice the projected area.
|
||||||
|
flatArea := 0.5 * (c.Vertex(2).Sub(c.Vertex(0).Vector).
|
||||||
|
Cross(c.Vertex(3).Sub(c.Vertex(1).Vector)).Norm())
|
||||||
|
|
||||||
|
// Now, compensate for the curvature of the cell surface by pretending
|
||||||
|
// that the cell is shaped like a spherical cap. The ratio of the
|
||||||
|
// area of a spherical cap to the area of its projected disc turns out
|
||||||
|
// to be 2 / (1 + sqrt(1 - r*r)) where r is the radius of the disc.
|
||||||
|
// For example, when r=0 the ratio is 1, and when r=1 the ratio is 2.
|
||||||
|
// Here we set Pi*r*r == flatArea to find the equivalent disc.
|
||||||
|
return flatArea * 2 / (1 + math.Sqrt(1-math.Min(1/math.Pi*flatArea, 1)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AverageArea returns the average area of cells at the level of this cell.
|
||||||
|
// This is accurate to within a factor of 1.7.
|
||||||
|
func (c Cell) AverageArea() float64 {
|
||||||
|
return AvgAreaMetric.Value(int(c.level))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntersectsCell reports whether the intersection of this cell and the other cell is not nil.
|
||||||
|
func (c Cell) IntersectsCell(oc Cell) bool {
|
||||||
|
return c.id.Intersects(oc.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainsCell reports whether this cell contains the other cell.
|
||||||
|
func (c Cell) ContainsCell(oc Cell) bool {
|
||||||
|
return c.id.Contains(oc.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// latitude returns the latitude of the cell vertex given by (i,j), where "i" and "j" are either 0 or 1.
|
||||||
|
func (c Cell) latitude(i, j int) float64 {
|
||||||
|
var u, v float64
|
||||||
|
switch {
|
||||||
|
case i == 0 && j == 0:
|
||||||
|
u = c.uv.X.Lo
|
||||||
|
v = c.uv.Y.Lo
|
||||||
|
case i == 0 && j == 1:
|
||||||
|
u = c.uv.X.Lo
|
||||||
|
v = c.uv.Y.Hi
|
||||||
|
case i == 1 && j == 0:
|
||||||
|
u = c.uv.X.Hi
|
||||||
|
v = c.uv.Y.Lo
|
||||||
|
case i == 1 && j == 1:
|
||||||
|
u = c.uv.X.Hi
|
||||||
|
v = c.uv.Y.Hi
|
||||||
|
default:
|
||||||
|
panic("i and/or j is out of bound")
|
||||||
|
}
|
||||||
|
return latitude(Point{faceUVToXYZ(int(c.face), u, v)}).Radians()
|
||||||
|
}
|
||||||
|
|
||||||
|
// longitude returns the longitude of the cell vertex given by (i,j), where "i" and "j" are either 0 or 1.
|
||||||
|
func (c Cell) longitude(i, j int) float64 {
|
||||||
|
var u, v float64
|
||||||
|
switch {
|
||||||
|
case i == 0 && j == 0:
|
||||||
|
u = c.uv.X.Lo
|
||||||
|
v = c.uv.Y.Lo
|
||||||
|
case i == 0 && j == 1:
|
||||||
|
u = c.uv.X.Lo
|
||||||
|
v = c.uv.Y.Hi
|
||||||
|
case i == 1 && j == 0:
|
||||||
|
u = c.uv.X.Hi
|
||||||
|
v = c.uv.Y.Lo
|
||||||
|
case i == 1 && j == 1:
|
||||||
|
u = c.uv.X.Hi
|
||||||
|
v = c.uv.Y.Hi
|
||||||
|
default:
|
||||||
|
panic("i and/or j is out of bound")
|
||||||
|
}
|
||||||
|
return longitude(Point{faceUVToXYZ(int(c.face), u, v)}).Radians()
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
poleMinLat = math.Asin(math.Sqrt(1.0/3)) - 0.5*dblEpsilon
|
||||||
|
)
|
||||||
|
|
||||||
|
// RectBound returns the bounding rectangle of this cell.
|
||||||
|
func (c Cell) RectBound() Rect {
|
||||||
|
if c.level > 0 {
|
||||||
|
// Except for cells at level 0, the latitude and longitude extremes are
|
||||||
|
// attained at the vertices. Furthermore, the latitude range is
|
||||||
|
// determined by one pair of diagonally opposite vertices and the
|
||||||
|
// longitude range is determined by the other pair.
|
||||||
|
//
|
||||||
|
// We first determine which corner (i,j) of the cell has the largest
|
||||||
|
// absolute latitude. To maximize latitude, we want to find the point in
|
||||||
|
// the cell that has the largest absolute z-coordinate and the smallest
|
||||||
|
// absolute x- and y-coordinates. To do this we look at each coordinate
|
||||||
|
// (u and v), and determine whether we want to minimize or maximize that
|
||||||
|
// coordinate based on the axis direction and the cell's (u,v) quadrant.
|
||||||
|
u := c.uv.X.Lo + c.uv.X.Hi
|
||||||
|
v := c.uv.Y.Lo + c.uv.Y.Hi
|
||||||
|
var i, j int
|
||||||
|
if uAxis(int(c.face)).Z == 0 {
|
||||||
|
if u < 0 {
|
||||||
|
i = 1
|
||||||
|
}
|
||||||
|
} else if u > 0 {
|
||||||
|
i = 1
|
||||||
|
}
|
||||||
|
if vAxis(int(c.face)).Z == 0 {
|
||||||
|
if v < 0 {
|
||||||
|
j = 1
|
||||||
|
}
|
||||||
|
} else if v > 0 {
|
||||||
|
j = 1
|
||||||
|
}
|
||||||
|
lat := r1.IntervalFromPoint(c.latitude(i, j)).AddPoint(c.latitude(1-i, 1-j))
|
||||||
|
lng := s1.EmptyInterval().AddPoint(c.longitude(i, 1-j)).AddPoint(c.longitude(1-i, j))
|
||||||
|
|
||||||
|
// We grow the bounds slightly to make sure that the bounding rectangle
|
||||||
|
// contains LatLngFromPoint(P) for any point P inside the loop L defined by the
|
||||||
|
// four *normalized* vertices. Note that normalization of a vector can
|
||||||
|
// change its direction by up to 0.5 * dblEpsilon radians, and it is not
|
||||||
|
// enough just to add Normalize calls to the code above because the
|
||||||
|
// latitude/longitude ranges are not necessarily determined by diagonally
|
||||||
|
// opposite vertex pairs after normalization.
|
||||||
|
//
|
||||||
|
// We would like to bound the amount by which the latitude/longitude of a
|
||||||
|
// contained point P can exceed the bounds computed above. In the case of
|
||||||
|
// longitude, the normalization error can change the direction of rounding
|
||||||
|
// leading to a maximum difference in longitude of 2 * dblEpsilon. In
|
||||||
|
// the case of latitude, the normalization error can shift the latitude by
|
||||||
|
// up to 0.5 * dblEpsilon and the other sources of error can cause the
|
||||||
|
// two latitudes to differ by up to another 1.5 * dblEpsilon, which also
|
||||||
|
// leads to a maximum difference of 2 * dblEpsilon.
|
||||||
|
return Rect{lat, lng}.expanded(LatLng{s1.Angle(2 * dblEpsilon), s1.Angle(2 * dblEpsilon)}).PolarClosure()
|
||||||
|
}
|
||||||
|
|
||||||
|
// The 4 cells around the equator extend to +/-45 degrees latitude at the
|
||||||
|
// midpoints of their top and bottom edges. The two cells covering the
|
||||||
|
// poles extend down to +/-35.26 degrees at their vertices. The maximum
|
||||||
|
// error in this calculation is 0.5 * dblEpsilon.
|
||||||
|
var bound Rect
|
||||||
|
switch c.face {
|
||||||
|
case 0:
|
||||||
|
bound = Rect{r1.Interval{-math.Pi / 4, math.Pi / 4}, s1.Interval{-math.Pi / 4, math.Pi / 4}}
|
||||||
|
case 1:
|
||||||
|
bound = Rect{r1.Interval{-math.Pi / 4, math.Pi / 4}, s1.Interval{math.Pi / 4, 3 * math.Pi / 4}}
|
||||||
|
case 2:
|
||||||
|
bound = Rect{r1.Interval{poleMinLat, math.Pi / 2}, s1.FullInterval()}
|
||||||
|
case 3:
|
||||||
|
bound = Rect{r1.Interval{-math.Pi / 4, math.Pi / 4}, s1.Interval{3 * math.Pi / 4, -3 * math.Pi / 4}}
|
||||||
|
case 4:
|
||||||
|
bound = Rect{r1.Interval{-math.Pi / 4, math.Pi / 4}, s1.Interval{-3 * math.Pi / 4, -math.Pi / 4}}
|
||||||
|
default:
|
||||||
|
bound = Rect{r1.Interval{-math.Pi / 2, -poleMinLat}, s1.FullInterval()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, we expand the bound to account for the error when a point P is
|
||||||
|
// converted to an LatLng to test for containment. (The bound should be
|
||||||
|
// large enough so that it contains the computed LatLng of any contained
|
||||||
|
// point, not just the infinite-precision version.) We don't need to expand
|
||||||
|
// longitude because longitude is calculated via a single call to math.Atan2,
|
||||||
|
// which is guaranteed to be semi-monotonic.
|
||||||
|
return bound.expanded(LatLng{s1.Angle(dblEpsilon), s1.Angle(0)})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CapBound returns the bounding cap of this cell.
|
||||||
|
func (c Cell) CapBound() Cap {
|
||||||
|
// We use the cell center in (u,v)-space as the cap axis. This vector is very close
|
||||||
|
// to GetCenter() and faster to compute. Neither one of these vectors yields the
|
||||||
|
// bounding cap with minimal surface area, but they are both pretty close.
|
||||||
|
cap := CapFromPoint(Point{faceUVToXYZ(int(c.face), c.uv.Center().X, c.uv.Center().Y).Normalize()})
|
||||||
|
for k := 0; k < 4; k++ {
|
||||||
|
cap = cap.AddPoint(c.Vertex(k))
|
||||||
|
}
|
||||||
|
return cap
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainsPoint reports whether this cell contains the given point. Note that
|
||||||
|
// unlike Loop/Polygon, a Cell is considered to be a closed set. This means
|
||||||
|
// that a point on a Cell's edge or vertex belong to the Cell and the relevant
|
||||||
|
// adjacent Cells too.
|
||||||
|
//
|
||||||
|
// If you want every point to be contained by exactly one Cell,
|
||||||
|
// you will need to convert the Cell to a Loop.
|
||||||
|
func (c Cell) ContainsPoint(p Point) bool {
|
||||||
|
var uv r2.Point
|
||||||
|
var ok bool
|
||||||
|
if uv.X, uv.Y, ok = faceXYZToUV(int(c.face), p); !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expand the (u,v) bound to ensure that
|
||||||
|
//
|
||||||
|
// CellFromPoint(p).ContainsPoint(p)
|
||||||
|
//
|
||||||
|
// is always true. To do this, we need to account for the error when
|
||||||
|
// converting from (u,v) coordinates to (s,t) coordinates. In the
|
||||||
|
// normal case the total error is at most dblEpsilon.
|
||||||
|
return c.uv.ExpandedByMargin(dblEpsilon).ContainsPoint(uv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BUG(roberts): Differences from C++:
|
||||||
|
// Subdivide
|
||||||
|
// BoundUV
|
||||||
|
// Distance/DistanceToEdge
|
||||||
|
// VertexChordDistance
|
889
vendor/github.com/golang/geo/s2/cellid.go
generated
vendored
Normal file
889
vendor/github.com/golang/geo/s2/cellid.go
generated
vendored
Normal file
|
@ -0,0 +1,889 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package s2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golang/geo/r1"
|
||||||
|
"github.com/golang/geo/r2"
|
||||||
|
"github.com/golang/geo/r3"
|
||||||
|
"github.com/golang/geo/s1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CellID uniquely identifies a cell in the S2 cell decomposition.
|
||||||
|
// The most significant 3 bits encode the face number (0-5). The
|
||||||
|
// remaining 61 bits encode the position of the center of this cell
|
||||||
|
// along the Hilbert curve on that face. The zero value and the value
|
||||||
|
// (1<<64)-1 are invalid cell IDs. The first compares less than any
|
||||||
|
// valid cell ID, the second as greater than any valid cell ID.
|
||||||
|
//
|
||||||
|
// Sequentially increasing cell IDs follow a continuous space-filling curve
|
||||||
|
// over the entire sphere. They have the following properties:
|
||||||
|
//
|
||||||
|
// - The ID of a cell at level k consists of a 3-bit face number followed
|
||||||
|
// by k bit pairs that recursively select one of the four children of
|
||||||
|
// each cell. The next bit is always 1, and all other bits are 0.
|
||||||
|
// Therefore, the level of a cell is determined by the position of its
|
||||||
|
// lowest-numbered bit that is turned on (for a cell at level k, this
|
||||||
|
// position is 2 * (maxLevel - k)).
|
||||||
|
//
|
||||||
|
// - The ID of a parent cell is at the midpoint of the range of IDs spanned
|
||||||
|
// by its children (or by its descendants at any level).
|
||||||
|
//
|
||||||
|
// Leaf cells are often used to represent points on the unit sphere, and
|
||||||
|
// this type provides methods for converting directly between these two
|
||||||
|
// representations. For cells that represent 2D regions rather than
|
||||||
|
// discrete point, it is better to use Cells.
|
||||||
|
type CellID uint64
|
||||||
|
|
||||||
|
// TODO(dsymonds): Some of these constants should probably be exported.
|
||||||
|
const (
|
||||||
|
faceBits = 3
|
||||||
|
numFaces = 6
|
||||||
|
maxLevel = 30
|
||||||
|
// The extra position bit (61 rather than 60) lets us encode each cell as its
|
||||||
|
// Hilbert curve position at the cell center (which is halfway along the
|
||||||
|
// portion of the Hilbert curve that fills that cell).
|
||||||
|
posBits = 2*maxLevel + 1
|
||||||
|
maxSize = 1 << maxLevel
|
||||||
|
wrapOffset = uint64(numFaces) << posBits
|
||||||
|
)
|
||||||
|
|
||||||
|
// CellIDFromFacePosLevel returns a cell given its face in the range
|
||||||
|
// [0,5], the 61-bit Hilbert curve position pos within that face, and
|
||||||
|
// the level in the range [0,maxLevel]. The position in the cell ID
|
||||||
|
// will be truncated to correspond to the Hilbert curve position at
|
||||||
|
// the center of the returned cell.
|
||||||
|
func CellIDFromFacePosLevel(face int, pos uint64, level int) CellID {
|
||||||
|
return CellID(uint64(face)<<posBits + pos | 1).Parent(level)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CellIDFromFace returns the cell corresponding to a given S2 cube face.
|
||||||
|
func CellIDFromFace(face int) CellID {
|
||||||
|
return CellID((uint64(face) << posBits) + lsbForLevel(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CellIDFromLatLng returns the leaf cell containing ll.
|
||||||
|
func CellIDFromLatLng(ll LatLng) CellID {
|
||||||
|
return cellIDFromPoint(PointFromLatLng(ll))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CellIDFromToken returns a cell given a hex-encoded string of its uint64 ID.
|
||||||
|
func CellIDFromToken(s string) CellID {
|
||||||
|
if len(s) > 16 {
|
||||||
|
return CellID(0)
|
||||||
|
}
|
||||||
|
n, err := strconv.ParseUint(s, 16, 64)
|
||||||
|
if err != nil {
|
||||||
|
return CellID(0)
|
||||||
|
}
|
||||||
|
// Equivalent to right-padding string with zeros to 16 characters.
|
||||||
|
if len(s) < 16 {
|
||||||
|
n = n << (4 * uint(16-len(s)))
|
||||||
|
}
|
||||||
|
return CellID(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToToken returns a hex-encoded string of the uint64 cell id, with leading
|
||||||
|
// zeros included but trailing zeros stripped.
|
||||||
|
func (ci CellID) ToToken() string {
|
||||||
|
s := strings.TrimRight(fmt.Sprintf("%016x", uint64(ci)), "0")
|
||||||
|
if len(s) == 0 {
|
||||||
|
return "X"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid reports whether ci represents a valid cell.
|
||||||
|
func (ci CellID) IsValid() bool {
|
||||||
|
return ci.Face() < numFaces && (ci.lsb()&0x1555555555555555 != 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Face returns the cube face for this cell ID, in the range [0,5].
|
||||||
|
func (ci CellID) Face() int { return int(uint64(ci) >> posBits) }
|
||||||
|
|
||||||
|
// Pos returns the position along the Hilbert curve of this cell ID, in the range [0,2^posBits-1].
|
||||||
|
func (ci CellID) Pos() uint64 { return uint64(ci) & (^uint64(0) >> faceBits) }
|
||||||
|
|
||||||
|
// Level returns the subdivision level of this cell ID, in the range [0, maxLevel].
|
||||||
|
func (ci CellID) Level() int {
|
||||||
|
return maxLevel - findLSBSetNonZero64(uint64(ci))>>1
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLeaf returns whether this cell ID is at the deepest level;
|
||||||
|
// that is, the level at which the cells are smallest.
|
||||||
|
func (ci CellID) IsLeaf() bool { return uint64(ci)&1 != 0 }
|
||||||
|
|
||||||
|
// ChildPosition returns the child position (0..3) of this cell's
|
||||||
|
// ancestor at the given level, relative to its parent. The argument
|
||||||
|
// should be in the range 1..kMaxLevel. For example,
|
||||||
|
// ChildPosition(1) returns the position of this cell's level-1
|
||||||
|
// ancestor within its top-level face cell.
|
||||||
|
func (ci CellID) ChildPosition(level int) int {
|
||||||
|
return int(uint64(ci)>>uint64(2*(maxLevel-level)+1)) & 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// lsbForLevel returns the lowest-numbered bit that is on for cells at the given level.
|
||||||
|
func lsbForLevel(level int) uint64 { return 1 << uint64(2*(maxLevel-level)) }
|
||||||
|
|
||||||
|
// Parent returns the cell at the given level, which must be no greater than the current level.
|
||||||
|
func (ci CellID) Parent(level int) CellID {
|
||||||
|
lsb := lsbForLevel(level)
|
||||||
|
return CellID((uint64(ci) & -lsb) | lsb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// immediateParent is cheaper than Parent, but assumes !ci.isFace().
|
||||||
|
func (ci CellID) immediateParent() CellID {
|
||||||
|
nlsb := CellID(ci.lsb() << 2)
|
||||||
|
return (ci & -nlsb) | nlsb
|
||||||
|
}
|
||||||
|
|
||||||
|
// isFace returns whether this is a top-level (face) cell.
|
||||||
|
func (ci CellID) isFace() bool { return uint64(ci)&(lsbForLevel(0)-1) == 0 }
|
||||||
|
|
||||||
|
// lsb returns the least significant bit that is set.
|
||||||
|
func (ci CellID) lsb() uint64 { return uint64(ci) & -uint64(ci) }
|
||||||
|
|
||||||
|
// Children returns the four immediate children of this cell.
|
||||||
|
// If ci is a leaf cell, it returns four identical cells that are not the children.
|
||||||
|
func (ci CellID) Children() [4]CellID {
|
||||||
|
var ch [4]CellID
|
||||||
|
lsb := CellID(ci.lsb())
|
||||||
|
ch[0] = ci - lsb + lsb>>2
|
||||||
|
lsb >>= 1
|
||||||
|
ch[1] = ch[0] + lsb
|
||||||
|
ch[2] = ch[1] + lsb
|
||||||
|
ch[3] = ch[2] + lsb
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func sizeIJ(level int) int {
|
||||||
|
return 1 << uint(maxLevel-level)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EdgeNeighbors returns the four cells that are adjacent across the cell's four edges.
|
||||||
|
// Edges 0, 1, 2, 3 are in the down, right, up, left directions in the face space.
|
||||||
|
// All neighbors are guaranteed to be distinct.
|
||||||
|
func (ci CellID) EdgeNeighbors() [4]CellID {
|
||||||
|
level := ci.Level()
|
||||||
|
size := sizeIJ(level)
|
||||||
|
f, i, j, _ := ci.faceIJOrientation()
|
||||||
|
return [4]CellID{
|
||||||
|
cellIDFromFaceIJWrap(f, i, j-size).Parent(level),
|
||||||
|
cellIDFromFaceIJWrap(f, i+size, j).Parent(level),
|
||||||
|
cellIDFromFaceIJWrap(f, i, j+size).Parent(level),
|
||||||
|
cellIDFromFaceIJWrap(f, i-size, j).Parent(level),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VertexNeighbors returns the neighboring cellIDs with vertex closest to this cell at the given level.
|
||||||
|
// (Normally there are four neighbors, but the closest vertex may only have three neighbors if it is one of
|
||||||
|
// the 8 cube vertices.)
|
||||||
|
func (ci CellID) VertexNeighbors(level int) []CellID {
|
||||||
|
halfSize := sizeIJ(level + 1)
|
||||||
|
size := halfSize << 1
|
||||||
|
f, i, j, _ := ci.faceIJOrientation()
|
||||||
|
|
||||||
|
var isame, jsame bool
|
||||||
|
var ioffset, joffset int
|
||||||
|
if i&halfSize != 0 {
|
||||||
|
ioffset = size
|
||||||
|
isame = (i + size) < maxSize
|
||||||
|
} else {
|
||||||
|
ioffset = -size
|
||||||
|
isame = (i - size) >= 0
|
||||||
|
}
|
||||||
|
if j&halfSize != 0 {
|
||||||
|
joffset = size
|
||||||
|
jsame = (j + size) < maxSize
|
||||||
|
} else {
|
||||||
|
joffset = -size
|
||||||
|
jsame = (j - size) >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
results := []CellID{
|
||||||
|
ci.Parent(level),
|
||||||
|
cellIDFromFaceIJSame(f, i+ioffset, j, isame).Parent(level),
|
||||||
|
cellIDFromFaceIJSame(f, i, j+joffset, jsame).Parent(level),
|
||||||
|
}
|
||||||
|
|
||||||
|
if isame || jsame {
|
||||||
|
results = append(results, cellIDFromFaceIJSame(f, i+ioffset, j+joffset, isame && jsame).Parent(level))
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllNeighbors returns all neighbors of this cell at the given level. Two
|
||||||
|
// cells X and Y are neighbors if their boundaries intersect but their
|
||||||
|
// interiors do not. In particular, two cells that intersect at a single
|
||||||
|
// point are neighbors. Note that for cells adjacent to a face vertex, the
|
||||||
|
// same neighbor may be returned more than once. There could be up to eight
|
||||||
|
// neighbors including the diagonal ones that share the vertex.
|
||||||
|
//
|
||||||
|
// This requires level >= ci.Level().
|
||||||
|
func (ci CellID) AllNeighbors(level int) []CellID {
|
||||||
|
var neighbors []CellID
|
||||||
|
|
||||||
|
face, i, j, _ := ci.faceIJOrientation()
|
||||||
|
|
||||||
|
// Find the coordinates of the lower left-hand leaf cell. We need to
|
||||||
|
// normalize (i,j) to a known position within the cell because level
|
||||||
|
// may be larger than this cell's level.
|
||||||
|
size := sizeIJ(ci.Level())
|
||||||
|
i &= -size
|
||||||
|
j &= -size
|
||||||
|
|
||||||
|
nbrSize := sizeIJ(level)
|
||||||
|
|
||||||
|
// We compute the top-bottom, left-right, and diagonal neighbors in one
|
||||||
|
// pass. The loop test is at the end of the loop to avoid 32-bit overflow.
|
||||||
|
for k := -nbrSize; ; k += nbrSize {
|
||||||
|
var sameFace bool
|
||||||
|
if k < 0 {
|
||||||
|
sameFace = (j+k >= 0)
|
||||||
|
} else if k >= size {
|
||||||
|
sameFace = (j+k < maxSize)
|
||||||
|
} else {
|
||||||
|
sameFace = true
|
||||||
|
// Top and bottom neighbors.
|
||||||
|
neighbors = append(neighbors, cellIDFromFaceIJSame(face, i+k, j-nbrSize,
|
||||||
|
j-size >= 0).Parent(level))
|
||||||
|
neighbors = append(neighbors, cellIDFromFaceIJSame(face, i+k, j+size,
|
||||||
|
j+size < maxSize).Parent(level))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Left, right, and diagonal neighbors.
|
||||||
|
neighbors = append(neighbors, cellIDFromFaceIJSame(face, i-nbrSize, j+k,
|
||||||
|
sameFace && i-size >= 0).Parent(level))
|
||||||
|
neighbors = append(neighbors, cellIDFromFaceIJSame(face, i+size, j+k,
|
||||||
|
sameFace && i+size < maxSize).Parent(level))
|
||||||
|
|
||||||
|
if k >= size {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return neighbors
|
||||||
|
}
|
||||||
|
|
||||||
|
// RangeMin returns the minimum CellID that is contained within this cell.
|
||||||
|
func (ci CellID) RangeMin() CellID { return CellID(uint64(ci) - (ci.lsb() - 1)) }
|
||||||
|
|
||||||
|
// RangeMax returns the maximum CellID that is contained within this cell.
|
||||||
|
func (ci CellID) RangeMax() CellID { return CellID(uint64(ci) + (ci.lsb() - 1)) }
|
||||||
|
|
||||||
|
// Contains returns true iff the CellID contains oci.
|
||||||
|
func (ci CellID) Contains(oci CellID) bool {
|
||||||
|
return uint64(ci.RangeMin()) <= uint64(oci) && uint64(oci) <= uint64(ci.RangeMax())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intersects returns true iff the CellID intersects oci.
|
||||||
|
func (ci CellID) Intersects(oci CellID) bool {
|
||||||
|
return uint64(oci.RangeMin()) <= uint64(ci.RangeMax()) && uint64(oci.RangeMax()) >= uint64(ci.RangeMin())
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of the cell ID in the form "1/3210".
|
||||||
|
func (ci CellID) String() string {
|
||||||
|
if !ci.IsValid() {
|
||||||
|
return "Invalid: " + strconv.FormatInt(int64(ci), 16)
|
||||||
|
}
|
||||||
|
var b bytes.Buffer
|
||||||
|
b.WriteByte("012345"[ci.Face()]) // values > 5 will have been picked off by !IsValid above
|
||||||
|
b.WriteByte('/')
|
||||||
|
for level := 1; level <= ci.Level(); level++ {
|
||||||
|
b.WriteByte("0123"[ci.ChildPosition(level)])
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point returns the center of the s2 cell on the sphere as a Point.
|
||||||
|
// The maximum directional error in Point (compared to the exact
|
||||||
|
// mathematical result) is 1.5 * dblEpsilon radians, and the maximum length
|
||||||
|
// error is 2 * dblEpsilon (the same as Normalize).
|
||||||
|
func (ci CellID) Point() Point { return Point{ci.rawPoint().Normalize()} }
|
||||||
|
|
||||||
|
// LatLng returns the center of the s2 cell on the sphere as a LatLng.
|
||||||
|
func (ci CellID) LatLng() LatLng { return LatLngFromPoint(Point{ci.rawPoint()}) }
|
||||||
|
|
||||||
|
// ChildBegin returns the first child in a traversal of the children of this cell, in Hilbert curve order.
|
||||||
|
//
|
||||||
|
// for ci := c.ChildBegin(); ci != c.ChildEnd(); ci = ci.Next() {
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
func (ci CellID) ChildBegin() CellID {
|
||||||
|
ol := ci.lsb()
|
||||||
|
return CellID(uint64(ci) - ol + ol>>2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChildBeginAtLevel returns the first cell in a traversal of children a given level deeper than this cell, in
|
||||||
|
// Hilbert curve order. The given level must be no smaller than the cell's level.
|
||||||
|
// See ChildBegin for example use.
|
||||||
|
func (ci CellID) ChildBeginAtLevel(level int) CellID {
|
||||||
|
return CellID(uint64(ci) - ci.lsb() + lsbForLevel(level))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChildEnd returns the first cell after a traversal of the children of this cell in Hilbert curve order.
|
||||||
|
// The returned cell may be invalid.
|
||||||
|
func (ci CellID) ChildEnd() CellID {
|
||||||
|
ol := ci.lsb()
|
||||||
|
return CellID(uint64(ci) + ol + ol>>2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChildEndAtLevel returns the first cell after the last child in a traversal of children a given level deeper
|
||||||
|
// than this cell, in Hilbert curve order.
|
||||||
|
// The given level must be no smaller than the cell's level.
|
||||||
|
// The returned cell may be invalid.
|
||||||
|
func (ci CellID) ChildEndAtLevel(level int) CellID {
|
||||||
|
return CellID(uint64(ci) + ci.lsb() + lsbForLevel(level))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns the next cell along the Hilbert curve.
|
||||||
|
// This is expected to be used with ChildBegin and ChildEnd,
|
||||||
|
// or ChildBeginAtLevel and ChildEndAtLevel.
|
||||||
|
func (ci CellID) Next() CellID {
|
||||||
|
return CellID(uint64(ci) + ci.lsb()<<1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prev returns the previous cell along the Hilbert curve.
|
||||||
|
func (ci CellID) Prev() CellID {
|
||||||
|
return CellID(uint64(ci) - ci.lsb()<<1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextWrap returns the next cell along the Hilbert curve, wrapping from last to
|
||||||
|
// first as necessary. This should not be used with ChildBegin and ChildEnd.
|
||||||
|
func (ci CellID) NextWrap() CellID {
|
||||||
|
n := ci.Next()
|
||||||
|
if uint64(n) < wrapOffset {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
return CellID(uint64(n) - wrapOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrevWrap returns the previous cell along the Hilbert curve, wrapping around from
|
||||||
|
// first to last as necessary. This should not be used with ChildBegin and ChildEnd.
|
||||||
|
func (ci CellID) PrevWrap() CellID {
|
||||||
|
p := ci.Prev()
|
||||||
|
if uint64(p) < wrapOffset {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
return CellID(uint64(p) + wrapOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdvanceWrap advances or retreats the indicated number of steps along the
|
||||||
|
// Hilbert curve at the current level and returns the new position. The
|
||||||
|
// position wraps between the first and last faces as necessary.
|
||||||
|
func (ci CellID) AdvanceWrap(steps int64) CellID {
|
||||||
|
if steps == 0 {
|
||||||
|
return ci
|
||||||
|
}
|
||||||
|
|
||||||
|
// We clamp the number of steps if necessary to ensure that we do not
|
||||||
|
// advance past the End() or before the Begin() of this level.
|
||||||
|
shift := uint(2*(maxLevel-ci.Level()) + 1)
|
||||||
|
if steps < 0 {
|
||||||
|
if min := -int64(uint64(ci) >> shift); steps < min {
|
||||||
|
wrap := int64(wrapOffset >> shift)
|
||||||
|
steps %= wrap
|
||||||
|
if steps < min {
|
||||||
|
steps += wrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Unlike Advance(), we don't want to return End(level).
|
||||||
|
if max := int64((wrapOffset - uint64(ci)) >> shift); steps > max {
|
||||||
|
wrap := int64(wrapOffset >> shift)
|
||||||
|
steps %= wrap
|
||||||
|
if steps > max {
|
||||||
|
steps -= wrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If steps is negative, then shifting it left has undefined behavior.
|
||||||
|
// Cast to uint64 for a 2's complement answer.
|
||||||
|
return CellID(uint64(ci) + (uint64(steps) << shift))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: the methods below are not exported yet. Settle on the entire API design
|
||||||
|
// before doing this. Do we want to mirror the C++ one as closely as possible?
|
||||||
|
|
||||||
|
// distanceFromBegin returns the number of steps that this cell is from the first
|
||||||
|
// node in the S2 heirarchy at our level. (i.e., FromFace(0).ChildBeginAtLevel(ci.Level())).
|
||||||
|
// The return value is always non-negative.
|
||||||
|
func (ci CellID) distanceFromBegin() int64 {
|
||||||
|
return int64(ci >> uint64(2*(maxLevel-ci.Level())+1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// rawPoint returns an unnormalized r3 vector from the origin through the center
|
||||||
|
// of the s2 cell on the sphere.
|
||||||
|
func (ci CellID) rawPoint() r3.Vector {
|
||||||
|
face, si, ti := ci.faceSiTi()
|
||||||
|
return faceUVToXYZ(face, stToUV((0.5/maxSize)*float64(si)), stToUV((0.5/maxSize)*float64(ti)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// faceSiTi returns the Face/Si/Ti coordinates of the center of the cell.
|
||||||
|
func (ci CellID) faceSiTi() (face, si, ti int) {
|
||||||
|
face, i, j, _ := ci.faceIJOrientation()
|
||||||
|
delta := 0
|
||||||
|
if ci.IsLeaf() {
|
||||||
|
delta = 1
|
||||||
|
} else {
|
||||||
|
if (i^(int(ci)>>2))&1 != 0 {
|
||||||
|
delta = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return face, 2*i + delta, 2*j + delta
|
||||||
|
}
|
||||||
|
|
||||||
|
// faceIJOrientation uses the global lookupIJ table to unfiddle the bits of ci.
|
||||||
|
func (ci CellID) faceIJOrientation() (f, i, j, orientation int) {
|
||||||
|
f = ci.Face()
|
||||||
|
orientation = f & swapMask
|
||||||
|
nbits := maxLevel - 7*lookupBits // first iteration
|
||||||
|
|
||||||
|
for k := 7; k >= 0; k-- {
|
||||||
|
orientation += (int(uint64(ci)>>uint64(k*2*lookupBits+1)) & ((1 << uint((2 * nbits))) - 1)) << 2
|
||||||
|
orientation = lookupIJ[orientation]
|
||||||
|
i += (orientation >> (lookupBits + 2)) << uint(k*lookupBits)
|
||||||
|
j += ((orientation >> 2) & ((1 << lookupBits) - 1)) << uint(k*lookupBits)
|
||||||
|
orientation &= (swapMask | invertMask)
|
||||||
|
nbits = lookupBits // following iterations
|
||||||
|
}
|
||||||
|
|
||||||
|
if ci.lsb()&0x1111111111111110 != 0 {
|
||||||
|
orientation ^= swapMask
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// cellIDFromFaceIJ returns a leaf cell given its cube face (range 0..5) and IJ coordinates.
|
||||||
|
func cellIDFromFaceIJ(f, i, j int) CellID {
|
||||||
|
// Note that this value gets shifted one bit to the left at the end
|
||||||
|
// of the function.
|
||||||
|
n := uint64(f) << (posBits - 1)
|
||||||
|
// Alternating faces have opposite Hilbert curve orientations; this
|
||||||
|
// is necessary in order for all faces to have a right-handed
|
||||||
|
// coordinate system.
|
||||||
|
bits := f & swapMask
|
||||||
|
// Each iteration maps 4 bits of "i" and "j" into 8 bits of the Hilbert
|
||||||
|
// curve position. The lookup table transforms a 10-bit key of the form
|
||||||
|
// "iiiijjjjoo" to a 10-bit value of the form "ppppppppoo", where the
|
||||||
|
// letters [ijpo] denote bits of "i", "j", Hilbert curve position, and
|
||||||
|
// Hilbert curve orientation respectively.
|
||||||
|
for k := 7; k >= 0; k-- {
|
||||||
|
mask := (1 << lookupBits) - 1
|
||||||
|
bits += int((i>>uint(k*lookupBits))&mask) << (lookupBits + 2)
|
||||||
|
bits += int((j>>uint(k*lookupBits))&mask) << 2
|
||||||
|
bits = lookupPos[bits]
|
||||||
|
n |= uint64(bits>>2) << (uint(k) * 2 * lookupBits)
|
||||||
|
bits &= (swapMask | invertMask)
|
||||||
|
}
|
||||||
|
return CellID(n*2 + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cellIDFromFaceIJWrap(f, i, j int) CellID {
|
||||||
|
// Convert i and j to the coordinates of a leaf cell just beyond the
|
||||||
|
// boundary of this face. This prevents 32-bit overflow in the case
|
||||||
|
// of finding the neighbors of a face cell.
|
||||||
|
i = clamp(i, -1, maxSize)
|
||||||
|
j = clamp(j, -1, maxSize)
|
||||||
|
|
||||||
|
// We want to wrap these coordinates onto the appropriate adjacent face.
|
||||||
|
// The easiest way to do this is to convert the (i,j) coordinates to (x,y,z)
|
||||||
|
// (which yields a point outside the normal face boundary), and then call
|
||||||
|
// xyzToFaceUV to project back onto the correct face.
|
||||||
|
//
|
||||||
|
// The code below converts (i,j) to (si,ti), and then (si,ti) to (u,v) using
|
||||||
|
// the linear projection (u=2*s-1 and v=2*t-1). (The code further below
|
||||||
|
// converts back using the inverse projection, s=0.5*(u+1) and t=0.5*(v+1).
|
||||||
|
// Any projection would work here, so we use the simplest.) We also clamp
|
||||||
|
// the (u,v) coordinates so that the point is barely outside the
|
||||||
|
// [-1,1]x[-1,1] face rectangle, since otherwise the reprojection step
|
||||||
|
// (which divides by the new z coordinate) might change the other
|
||||||
|
// coordinates enough so that we end up in the wrong leaf cell.
|
||||||
|
const scale = 1.0 / maxSize
|
||||||
|
limit := math.Nextafter(1, 2)
|
||||||
|
u := math.Max(-limit, math.Min(limit, scale*float64((i<<1)+1-maxSize)))
|
||||||
|
v := math.Max(-limit, math.Min(limit, scale*float64((j<<1)+1-maxSize)))
|
||||||
|
|
||||||
|
// Find the leaf cell coordinates on the adjacent face, and convert
|
||||||
|
// them to a cell id at the appropriate level.
|
||||||
|
f, u, v = xyzToFaceUV(faceUVToXYZ(f, u, v))
|
||||||
|
return cellIDFromFaceIJ(f, stToIJ(0.5*(u+1)), stToIJ(0.5*(v+1)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func cellIDFromFaceIJSame(f, i, j int, sameFace bool) CellID {
|
||||||
|
if sameFace {
|
||||||
|
return cellIDFromFaceIJ(f, i, j)
|
||||||
|
}
|
||||||
|
return cellIDFromFaceIJWrap(f, i, j)
|
||||||
|
}
|
||||||
|
|
||||||
|
// clamp returns number closest to x within the range min..max.
|
||||||
|
func clamp(x, min, max int) int {
|
||||||
|
if x < min {
|
||||||
|
return min
|
||||||
|
}
|
||||||
|
if x > max {
|
||||||
|
return max
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// ijToSTMin converts the i- or j-index of a leaf cell to the minimum corresponding
|
||||||
|
// s- or t-value contained by that cell. The argument must be in the range
|
||||||
|
// [0..2**30], i.e. up to one position beyond the normal range of valid leaf
|
||||||
|
// cell indices.
|
||||||
|
func ijToSTMin(i int) float64 {
|
||||||
|
return float64(i) / float64(maxSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stToIJ converts value in ST coordinates to a value in IJ coordinates.
|
||||||
|
func stToIJ(s float64) int {
|
||||||
|
return clamp(int(math.Floor(maxSize*s)), 0, maxSize-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cellIDFromPoint returns a leaf cell containing point p. Usually there is
|
||||||
|
// exactly one such cell, but for points along the edge of a cell, any
|
||||||
|
// adjacent cell may be (deterministically) chosen. This is because
|
||||||
|
// s2.CellIDs are considered to be closed sets. The returned cell will
|
||||||
|
// always contain the given point, i.e.
|
||||||
|
//
|
||||||
|
// CellFromPoint(p).ContainsPoint(p)
|
||||||
|
//
|
||||||
|
// is always true.
|
||||||
|
func cellIDFromPoint(p Point) CellID {
|
||||||
|
f, u, v := xyzToFaceUV(r3.Vector{p.X, p.Y, p.Z})
|
||||||
|
i := stToIJ(uvToST(u))
|
||||||
|
j := stToIJ(uvToST(v))
|
||||||
|
return cellIDFromFaceIJ(f, i, j)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ijLevelToBoundUV returns the bounds in (u,v)-space for the cell at the given
|
||||||
|
// level containing the leaf cell with the given (i,j)-coordinates.
|
||||||
|
func ijLevelToBoundUV(i, j, level int) r2.Rect {
|
||||||
|
cellSize := sizeIJ(level)
|
||||||
|
xLo := i & -cellSize
|
||||||
|
yLo := j & -cellSize
|
||||||
|
|
||||||
|
return r2.Rect{
|
||||||
|
X: r1.Interval{
|
||||||
|
Lo: stToUV(ijToSTMin(xLo)),
|
||||||
|
Hi: stToUV(ijToSTMin(xLo + cellSize)),
|
||||||
|
},
|
||||||
|
Y: r1.Interval{
|
||||||
|
Lo: stToUV(ijToSTMin(yLo)),
|
||||||
|
Hi: stToUV(ijToSTMin(yLo + cellSize)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constants related to the bit mangling in the Cell ID.
|
||||||
|
const (
|
||||||
|
lookupBits = 4
|
||||||
|
swapMask = 0x01
|
||||||
|
invertMask = 0x02
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ijToPos = [4][4]int{
|
||||||
|
{0, 1, 3, 2}, // canonical order
|
||||||
|
{0, 3, 1, 2}, // axes swapped
|
||||||
|
{2, 3, 1, 0}, // bits inverted
|
||||||
|
{2, 1, 3, 0}, // swapped & inverted
|
||||||
|
}
|
||||||
|
posToIJ = [4][4]int{
|
||||||
|
{0, 1, 3, 2}, // canonical order: (0,0), (0,1), (1,1), (1,0)
|
||||||
|
{0, 2, 3, 1}, // axes swapped: (0,0), (1,0), (1,1), (0,1)
|
||||||
|
{3, 2, 0, 1}, // bits inverted: (1,1), (1,0), (0,0), (0,1)
|
||||||
|
{3, 1, 0, 2}, // swapped & inverted: (1,1), (0,1), (0,0), (1,0)
|
||||||
|
}
|
||||||
|
posToOrientation = [4]int{swapMask, 0, 0, invertMask | swapMask}
|
||||||
|
lookupIJ [1 << (2*lookupBits + 2)]int
|
||||||
|
lookupPos [1 << (2*lookupBits + 2)]int
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
initLookupCell(0, 0, 0, 0, 0, 0)
|
||||||
|
initLookupCell(0, 0, 0, swapMask, 0, swapMask)
|
||||||
|
initLookupCell(0, 0, 0, invertMask, 0, invertMask)
|
||||||
|
initLookupCell(0, 0, 0, swapMask|invertMask, 0, swapMask|invertMask)
|
||||||
|
}
|
||||||
|
|
||||||
|
// initLookupCell initializes the lookupIJ table at init time.
|
||||||
|
func initLookupCell(level, i, j, origOrientation, pos, orientation int) {
|
||||||
|
if level == lookupBits {
|
||||||
|
ij := (i << lookupBits) + j
|
||||||
|
lookupPos[(ij<<2)+origOrientation] = (pos << 2) + orientation
|
||||||
|
lookupIJ[(pos<<2)+origOrientation] = (ij << 2) + orientation
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
level++
|
||||||
|
i <<= 1
|
||||||
|
j <<= 1
|
||||||
|
pos <<= 2
|
||||||
|
r := posToIJ[orientation]
|
||||||
|
initLookupCell(level, i+(r[0]>>1), j+(r[0]&1), origOrientation, pos, orientation^posToOrientation[0])
|
||||||
|
initLookupCell(level, i+(r[1]>>1), j+(r[1]&1), origOrientation, pos+1, orientation^posToOrientation[1])
|
||||||
|
initLookupCell(level, i+(r[2]>>1), j+(r[2]&1), origOrientation, pos+2, orientation^posToOrientation[2])
|
||||||
|
initLookupCell(level, i+(r[3]>>1), j+(r[3]&1), origOrientation, pos+3, orientation^posToOrientation[3])
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommonAncestorLevel returns the level of the common ancestor of the two S2 CellIDs.
|
||||||
|
func (ci CellID) CommonAncestorLevel(other CellID) (level int, ok bool) {
|
||||||
|
bits := uint64(ci ^ other)
|
||||||
|
if bits < ci.lsb() {
|
||||||
|
bits = ci.lsb()
|
||||||
|
}
|
||||||
|
if bits < other.lsb() {
|
||||||
|
bits = other.lsb()
|
||||||
|
}
|
||||||
|
|
||||||
|
msbPos := findMSBSetNonZero64(bits)
|
||||||
|
if msbPos > 60 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return (60 - msbPos) >> 1, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// findMSBSetNonZero64 returns the index (between 0 and 63) of the most
|
||||||
|
// significant set bit. Passing zero to this function has undefined behavior.
|
||||||
|
func findMSBSetNonZero64(bits uint64) int {
|
||||||
|
val := []uint64{0x2, 0xC, 0xF0, 0xFF00, 0xFFFF0000, 0xFFFFFFFF00000000}
|
||||||
|
shift := []uint64{1, 2, 4, 8, 16, 32}
|
||||||
|
var msbPos uint64
|
||||||
|
for i := 5; i >= 0; i-- {
|
||||||
|
if bits&val[i] != 0 {
|
||||||
|
bits >>= shift[i]
|
||||||
|
msbPos |= shift[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return int(msbPos)
|
||||||
|
}
|
||||||
|
|
||||||
|
const deBruijn64 = 0x03f79d71b4ca8b09
|
||||||
|
const digitMask = uint64(1<<64 - 1)
|
||||||
|
|
||||||
|
var deBruijn64Lookup = []byte{
|
||||||
|
0, 1, 56, 2, 57, 49, 28, 3, 61, 58, 42, 50, 38, 29, 17, 4,
|
||||||
|
62, 47, 59, 36, 45, 43, 51, 22, 53, 39, 33, 30, 24, 18, 12, 5,
|
||||||
|
63, 55, 48, 27, 60, 41, 37, 16, 46, 35, 44, 21, 52, 32, 23, 11,
|
||||||
|
54, 26, 40, 15, 34, 20, 31, 10, 25, 14, 19, 9, 13, 8, 7, 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
// findLSBSetNonZero64 returns the index (between 0 and 63) of the least
|
||||||
|
// significant set bit. Passing zero to this function has undefined behavior.
|
||||||
|
//
|
||||||
|
// This code comes from trailingZeroBits in https://golang.org/src/math/big/nat.go
|
||||||
|
// which references (Knuth, volume 4, section 7.3.1).
|
||||||
|
func findLSBSetNonZero64(bits uint64) int {
|
||||||
|
return int(deBruijn64Lookup[((bits&-bits)*(deBruijn64&digitMask))>>58])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance advances or retreats the indicated number of steps along the
|
||||||
|
// Hilbert curve at the current level, and returns the new position. The
|
||||||
|
// position is never advanced past End() or before Begin().
|
||||||
|
func (ci CellID) Advance(steps int64) CellID {
|
||||||
|
if steps == 0 {
|
||||||
|
return ci
|
||||||
|
}
|
||||||
|
|
||||||
|
// We clamp the number of steps if necessary to ensure that we do not
|
||||||
|
// advance past the End() or before the Begin() of this level. Note that
|
||||||
|
// minSteps and maxSteps always fit in a signed 64-bit integer.
|
||||||
|
stepShift := uint(2*(maxLevel-ci.Level()) + 1)
|
||||||
|
if steps < 0 {
|
||||||
|
minSteps := -int64(uint64(ci) >> stepShift)
|
||||||
|
if steps < minSteps {
|
||||||
|
steps = minSteps
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
maxSteps := int64((wrapOffset + ci.lsb() - uint64(ci)) >> stepShift)
|
||||||
|
if steps > maxSteps {
|
||||||
|
steps = maxSteps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ci + CellID(steps)<<stepShift
|
||||||
|
}
|
||||||
|
|
||||||
|
// centerST return the center of the CellID in (s,t)-space.
|
||||||
|
func (ci CellID) centerST() r2.Point {
|
||||||
|
_, si, ti := ci.faceSiTi()
|
||||||
|
return r2.Point{siTiToST(uint64(si)), siTiToST(uint64(ti))}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sizeST returns the edge length of this CellID in (s,t)-space at the given level.
|
||||||
|
func (ci CellID) sizeST(level int) float64 {
|
||||||
|
return ijToSTMin(sizeIJ(level))
|
||||||
|
}
|
||||||
|
|
||||||
|
// boundST returns the bound of this CellID in (s,t)-space.
|
||||||
|
func (ci CellID) boundST() r2.Rect {
|
||||||
|
s := ci.sizeST(ci.Level())
|
||||||
|
return r2.RectFromCenterSize(ci.centerST(), r2.Point{s, s})
|
||||||
|
}
|
||||||
|
|
||||||
|
// centerUV returns the center of this CellID in (u,v)-space. Note that
|
||||||
|
// the center of the cell is defined as the point at which it is recursively
|
||||||
|
// subdivided into four children; in general, it is not at the midpoint of
|
||||||
|
// the (u,v) rectangle covered by the cell.
|
||||||
|
func (ci CellID) centerUV() r2.Point {
|
||||||
|
_, si, ti := ci.faceSiTi()
|
||||||
|
return r2.Point{stToUV(siTiToST(uint64(si))), stToUV(siTiToST(uint64(ti)))}
|
||||||
|
}
|
||||||
|
|
||||||
|
// boundUV returns the bound of this CellID in (u,v)-space.
|
||||||
|
func (ci CellID) boundUV() r2.Rect {
|
||||||
|
_, i, j, _ := ci.faceIJOrientation()
|
||||||
|
return ijLevelToBoundUV(i, j, ci.Level())
|
||||||
|
}
|
||||||
|
|
||||||
|
// expandEndpoint returns a new u-coordinate u' such that the distance from the
|
||||||
|
// line u=u' to the given edge (u,v0)-(u,v1) is exactly the given distance
|
||||||
|
// (which is specified as the sine of the angle corresponding to the distance).
|
||||||
|
func expandEndpoint(u, maxV, sinDist float64) float64 {
|
||||||
|
// This is based on solving a spherical right triangle, similar to the
|
||||||
|
// calculation in Cap.RectBound.
|
||||||
|
// Given an edge of the form (u,v0)-(u,v1), let maxV = max(abs(v0), abs(v1)).
|
||||||
|
sinUShift := sinDist * math.Sqrt((1+u*u+maxV*maxV)/(1+u*u))
|
||||||
|
cosUShift := math.Sqrt(1 - sinUShift*sinUShift)
|
||||||
|
// The following is an expansion of tan(atan(u) + asin(sinUShift)).
|
||||||
|
return (cosUShift*u + sinUShift) / (cosUShift - sinUShift*u)
|
||||||
|
}
|
||||||
|
|
||||||
|
// expandedByDistanceUV returns a rectangle expanded in (u,v)-space so that it
|
||||||
|
// contains all points within the given distance of the boundary, and return the
|
||||||
|
// smallest such rectangle. If the distance is negative, then instead shrink this
|
||||||
|
// rectangle so that it excludes all points within the given absolute distance
|
||||||
|
// of the boundary.
|
||||||
|
//
|
||||||
|
// Distances are measured *on the sphere*, not in (u,v)-space. For example,
|
||||||
|
// you can use this method to expand the (u,v)-bound of an CellID so that
|
||||||
|
// it contains all points within 5km of the original cell. You can then
|
||||||
|
// test whether a point lies within the expanded bounds like this:
|
||||||
|
//
|
||||||
|
// if u, v, ok := faceXYZtoUV(face, point); ok && bound.ContainsPoint(r2.Point{u,v}) { ... }
|
||||||
|
//
|
||||||
|
// Limitations:
|
||||||
|
//
|
||||||
|
// - Because the rectangle is drawn on one of the six cube-face planes
|
||||||
|
// (i.e., {x,y,z} = +/-1), it can cover at most one hemisphere. This
|
||||||
|
// limits the maximum amount that a rectangle can be expanded. For
|
||||||
|
// example, CellID bounds can be expanded safely by at most 45 degrees
|
||||||
|
// (about 5000 km on the Earth's surface).
|
||||||
|
//
|
||||||
|
// - The implementation is not exact for negative distances. The resulting
|
||||||
|
// rectangle will exclude all points within the given distance of the
|
||||||
|
// boundary but may be slightly smaller than necessary.
|
||||||
|
func expandedByDistanceUV(uv r2.Rect, distance s1.Angle) r2.Rect {
|
||||||
|
// Expand each of the four sides of the rectangle just enough to include all
|
||||||
|
// points within the given distance of that side. (The rectangle may be
|
||||||
|
// expanded by a different amount in (u,v)-space on each side.)
|
||||||
|
maxU := math.Max(math.Abs(uv.X.Lo), math.Abs(uv.X.Hi))
|
||||||
|
maxV := math.Max(math.Abs(uv.Y.Lo), math.Abs(uv.Y.Hi))
|
||||||
|
sinDist := math.Sin(float64(distance))
|
||||||
|
return r2.Rect{
|
||||||
|
X: r1.Interval{expandEndpoint(uv.X.Lo, maxV, -sinDist),
|
||||||
|
expandEndpoint(uv.X.Hi, maxV, sinDist)},
|
||||||
|
Y: r1.Interval{expandEndpoint(uv.Y.Lo, maxU, -sinDist),
|
||||||
|
expandEndpoint(uv.Y.Hi, maxU, sinDist)}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxTile returns the largest cell with the same RangeMin such that
|
||||||
|
// RangeMax < limit.RangeMin. It returns limit if no such cell exists.
|
||||||
|
// This method can be used to generate a small set of CellIDs that covers
|
||||||
|
// a given range (a tiling). This example shows how to generate a tiling
|
||||||
|
// for a semi-open range of leaf cells [start, limit):
|
||||||
|
//
|
||||||
|
// for id := start.MaxTile(limit); id != limit; id = id.Next().MaxTile(limit)) { ... }
|
||||||
|
//
|
||||||
|
// Note that in general the cells in the tiling will be of different sizes;
|
||||||
|
// they gradually get larger (near the middle of the range) and then
|
||||||
|
// gradually get smaller as limit is approached.
|
||||||
|
func (ci CellID) MaxTile(limit CellID) CellID {
|
||||||
|
start := ci.RangeMin()
|
||||||
|
if start >= limit.RangeMin() {
|
||||||
|
return limit
|
||||||
|
}
|
||||||
|
|
||||||
|
if ci.RangeMax() >= limit {
|
||||||
|
// The cell is too large, shrink it. Note that when generating coverings
|
||||||
|
// of CellID ranges, this loop usually executes only once. Also because
|
||||||
|
// ci.RangeMin() < limit.RangeMin(), we will always exit the loop by the
|
||||||
|
// time we reach a leaf cell.
|
||||||
|
for {
|
||||||
|
ci = ci.Children()[0]
|
||||||
|
if ci.RangeMax() < limit {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ci
|
||||||
|
}
|
||||||
|
|
||||||
|
// The cell may be too small. Grow it if necessary. Note that generally
|
||||||
|
// this loop only iterates once.
|
||||||
|
for !ci.isFace() {
|
||||||
|
parent := ci.immediateParent()
|
||||||
|
if parent.RangeMin() != start || parent.RangeMax() >= limit {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ci = parent
|
||||||
|
}
|
||||||
|
return ci
|
||||||
|
}
|
||||||
|
|
||||||
|
// centerFaceSiTi returns the (face, si, ti) coordinates of the center of the cell.
|
||||||
|
// Note that although (si,ti) coordinates span the range [0,2**31] in general,
|
||||||
|
// the cell center coordinates are always in the range [1,2**31-1] and
|
||||||
|
// therefore can be represented using a signed 32-bit integer.
|
||||||
|
func (ci CellID) centerFaceSiTi() (face, si, ti int) {
|
||||||
|
// First we compute the discrete (i,j) coordinates of a leaf cell contained
|
||||||
|
// within the given cell. Given that cells are represented by the Hilbert
|
||||||
|
// curve position corresponding at their center, it turns out that the cell
|
||||||
|
// returned by faceIJOrientation is always one of two leaf cells closest
|
||||||
|
// to the center of the cell (unless the given cell is a leaf cell itself,
|
||||||
|
// in which case there is only one possibility).
|
||||||
|
//
|
||||||
|
// Given a cell of size s >= 2 (i.e. not a leaf cell), and letting (imin,
|
||||||
|
// jmin) be the coordinates of its lower left-hand corner, the leaf cell
|
||||||
|
// returned by faceIJOrientation is either (imin + s/2, jmin + s/2)
|
||||||
|
// (imin + s/2 - 1, jmin + s/2 - 1). The first case is the one we want.
|
||||||
|
// We can distinguish these two cases by looking at the low bit of i or
|
||||||
|
// j. In the second case the low bit is one, unless s == 2 (i.e. the
|
||||||
|
// level just above leaf cells) in which case the low bit is zero.
|
||||||
|
//
|
||||||
|
// In the code below, the expression ((i ^ (int(id) >> 2)) & 1) is true
|
||||||
|
// if we are in the second case described above.
|
||||||
|
face, i, j, _ := ci.faceIJOrientation()
|
||||||
|
delta := 0
|
||||||
|
if ci.IsLeaf() {
|
||||||
|
delta = 1
|
||||||
|
} else if (int64(i)^(int64(ci)>>2))&1 == 1 {
|
||||||
|
delta = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that (2 * {i,j} + delta) will never overflow a 32-bit integer.
|
||||||
|
return face, 2*i + delta, 2*j + delta
|
||||||
|
}
|
241
vendor/github.com/golang/geo/s2/cellunion.go
generated
vendored
Normal file
241
vendor/github.com/golang/geo/s2/cellunion.go
generated
vendored
Normal file
|
@ -0,0 +1,241 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package s2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A CellUnion is a collection of CellIDs.
|
||||||
|
//
|
||||||
|
// It is normalized if it is sorted, and does not contain redundancy.
|
||||||
|
// Specifically, it may not contain the same CellID twice, nor a CellID that
|
||||||
|
// is contained by another, nor the four sibling CellIDs that are children of
|
||||||
|
// a single higher level CellID.
|
||||||
|
type CellUnion []CellID
|
||||||
|
|
||||||
|
// CellUnionFromRange creates a CellUnion that covers the half-open range
|
||||||
|
// of leaf cells [begin, end). If begin == end the resulting union is empty.
|
||||||
|
// This requires that begin and end are both leaves, and begin <= end.
|
||||||
|
// To create a closed-ended range, pass in end.Next().
|
||||||
|
func CellUnionFromRange(begin, end CellID) CellUnion {
|
||||||
|
// We repeatedly add the largest cell we can.
|
||||||
|
var cu CellUnion
|
||||||
|
for id := begin.MaxTile(end); id != end; id = id.Next().MaxTile(end) {
|
||||||
|
cu = append(cu, id)
|
||||||
|
}
|
||||||
|
return cu
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize normalizes the CellUnion.
|
||||||
|
func (cu *CellUnion) Normalize() {
|
||||||
|
sort.Sort(byID(*cu))
|
||||||
|
|
||||||
|
output := make([]CellID, 0, len(*cu)) // the list of accepted cells
|
||||||
|
// Loop invariant: output is a sorted list of cells with no redundancy.
|
||||||
|
for _, ci := range *cu {
|
||||||
|
// The first two passes here either ignore this new candidate,
|
||||||
|
// or remove previously accepted cells that are covered by this candidate.
|
||||||
|
|
||||||
|
// Ignore this cell if it is contained by the previous one.
|
||||||
|
// We only need to check the last accepted cell. The ordering of the
|
||||||
|
// cells implies containment (but not the converse), and output has no redundancy,
|
||||||
|
// so if this candidate is not contained by the last accepted cell
|
||||||
|
// then it cannot be contained by any previously accepted cell.
|
||||||
|
if len(output) > 0 && output[len(output)-1].Contains(ci) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discard any previously accepted cells contained by this one.
|
||||||
|
// This could be any contiguous trailing subsequence, but it can't be
|
||||||
|
// a discontiguous subsequence because of the containment property of
|
||||||
|
// sorted S2 cells mentioned above.
|
||||||
|
j := len(output) - 1 // last index to keep
|
||||||
|
for j >= 0 {
|
||||||
|
if !ci.Contains(output[j]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
output = output[:j+1]
|
||||||
|
|
||||||
|
// See if the last three cells plus this one can be collapsed.
|
||||||
|
// We loop because collapsing three accepted cells and adding a higher level cell
|
||||||
|
// could cascade into previously accepted cells.
|
||||||
|
for len(output) >= 3 {
|
||||||
|
fin := output[len(output)-3:]
|
||||||
|
|
||||||
|
// fast XOR test; a necessary but not sufficient condition
|
||||||
|
if fin[0]^fin[1]^fin[2]^ci != 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// more expensive test; exact.
|
||||||
|
// Compute the two bit mask for the encoded child position,
|
||||||
|
// then see if they all agree.
|
||||||
|
mask := CellID(ci.lsb() << 1)
|
||||||
|
mask = ^(mask + mask<<1)
|
||||||
|
should := ci & mask
|
||||||
|
if (fin[0]&mask != should) || (fin[1]&mask != should) || (fin[2]&mask != should) || ci.isFace() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
output = output[:len(output)-3]
|
||||||
|
ci = ci.immediateParent() // checked !ci.isFace above
|
||||||
|
}
|
||||||
|
output = append(output, ci)
|
||||||
|
}
|
||||||
|
*cu = output
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntersectsCellID reports whether this cell union intersects the given cell ID.
|
||||||
|
//
|
||||||
|
// This method assumes that the CellUnion has been normalized.
|
||||||
|
func (cu *CellUnion) IntersectsCellID(id CellID) bool {
|
||||||
|
// Find index of array item that occurs directly after our probe cell:
|
||||||
|
i := sort.Search(len(*cu), func(i int) bool { return id < (*cu)[i] })
|
||||||
|
|
||||||
|
if i != len(*cu) && (*cu)[i].RangeMin() <= id.RangeMax() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return i != 0 && (*cu)[i-1].RangeMax() >= id.RangeMin()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainsCellID reports whether the cell union contains the given cell ID.
|
||||||
|
// Containment is defined with respect to regions, e.g. a cell contains its 4 children.
|
||||||
|
//
|
||||||
|
// This method assumes that the CellUnion has been normalized.
|
||||||
|
func (cu *CellUnion) ContainsCellID(id CellID) bool {
|
||||||
|
// Find index of array item that occurs directly after our probe cell:
|
||||||
|
i := sort.Search(len(*cu), func(i int) bool { return id < (*cu)[i] })
|
||||||
|
|
||||||
|
if i != len(*cu) && (*cu)[i].RangeMin() <= id {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return i != 0 && (*cu)[i-1].RangeMax() >= id
|
||||||
|
}
|
||||||
|
|
||||||
|
type byID []CellID
|
||||||
|
|
||||||
|
func (cu byID) Len() int { return len(cu) }
|
||||||
|
func (cu byID) Less(i, j int) bool { return cu[i] < cu[j] }
|
||||||
|
func (cu byID) Swap(i, j int) { cu[i], cu[j] = cu[j], cu[i] }
|
||||||
|
|
||||||
|
// Denormalize replaces this CellUnion with an expanded version of the
|
||||||
|
// CellUnion where any cell whose level is less than minLevel or where
|
||||||
|
// (level - minLevel) is not a multiple of levelMod is replaced by its
|
||||||
|
// children, until either both of these conditions are satisfied or the
|
||||||
|
// maximum level is reached.
|
||||||
|
func (cu *CellUnion) Denormalize(minLevel, levelMod int) {
|
||||||
|
var denorm CellUnion
|
||||||
|
for _, id := range *cu {
|
||||||
|
level := id.Level()
|
||||||
|
newLevel := level
|
||||||
|
if newLevel < minLevel {
|
||||||
|
newLevel = minLevel
|
||||||
|
}
|
||||||
|
if levelMod > 1 {
|
||||||
|
newLevel += (maxLevel - (newLevel - minLevel)) % levelMod
|
||||||
|
if newLevel > maxLevel {
|
||||||
|
newLevel = maxLevel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if newLevel == level {
|
||||||
|
denorm = append(denorm, id)
|
||||||
|
} else {
|
||||||
|
end := id.ChildEndAtLevel(newLevel)
|
||||||
|
for ci := id.ChildBeginAtLevel(newLevel); ci != end; ci = ci.Next() {
|
||||||
|
denorm = append(denorm, ci)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*cu = denorm
|
||||||
|
}
|
||||||
|
|
||||||
|
// RectBound returns a Rect that bounds this entity.
|
||||||
|
func (cu *CellUnion) RectBound() Rect {
|
||||||
|
bound := EmptyRect()
|
||||||
|
for _, c := range *cu {
|
||||||
|
bound = bound.Union(CellFromCellID(c).RectBound())
|
||||||
|
}
|
||||||
|
return bound
|
||||||
|
}
|
||||||
|
|
||||||
|
// CapBound returns a Cap that bounds this entity.
|
||||||
|
func (cu *CellUnion) CapBound() Cap {
|
||||||
|
if len(*cu) == 0 {
|
||||||
|
return EmptyCap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the approximate centroid of the region. This won't produce the
|
||||||
|
// bounding cap of minimal area, but it should be close enough.
|
||||||
|
var centroid Point
|
||||||
|
|
||||||
|
for _, ci := range *cu {
|
||||||
|
area := AvgAreaMetric.Value(ci.Level())
|
||||||
|
centroid = Point{centroid.Add(ci.Point().Mul(area))}
|
||||||
|
}
|
||||||
|
|
||||||
|
if zero := (Point{}); centroid == zero {
|
||||||
|
centroid = PointFromCoords(1, 0, 0)
|
||||||
|
} else {
|
||||||
|
centroid = Point{centroid.Normalize()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the centroid as the cap axis, and expand the cap angle so that it
|
||||||
|
// contains the bounding caps of all the individual cells. Note that it is
|
||||||
|
// *not* sufficient to just bound all the cell vertices because the bounding
|
||||||
|
// cap may be concave (i.e. cover more than one hemisphere).
|
||||||
|
c := CapFromPoint(centroid)
|
||||||
|
for _, ci := range *cu {
|
||||||
|
c = c.AddCap(CellFromCellID(ci).CapBound())
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainsCell reports whether this cell union contains the given cell.
|
||||||
|
func (cu *CellUnion) ContainsCell(c Cell) bool {
|
||||||
|
return cu.ContainsCellID(c.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntersectsCell reports whether this cell union intersects the given cell.
|
||||||
|
func (cu *CellUnion) IntersectsCell(c Cell) bool {
|
||||||
|
return cu.IntersectsCellID(c.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainsPoint reports whether this cell union contains the given point.
|
||||||
|
func (cu *CellUnion) ContainsPoint(p Point) bool {
|
||||||
|
return cu.ContainsCell(CellFromPoint(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LeafCellsCovered reports the number of leaf cells covered by this cell union.
|
||||||
|
// This will be no more than 6*2^60 for the whole sphere.
|
||||||
|
func (cu *CellUnion) LeafCellsCovered() int64 {
|
||||||
|
var numLeaves int64
|
||||||
|
for _, c := range *cu {
|
||||||
|
numLeaves += 1 << uint64((maxLevel-int64(c.Level()))<<1)
|
||||||
|
}
|
||||||
|
return numLeaves
|
||||||
|
}
|
||||||
|
|
||||||
|
// BUG: Differences from C++:
|
||||||
|
// Contains(CellUnion)/Intersects(CellUnion)
|
||||||
|
// Union(CellUnion)/Intersection(CellUnion)/Difference(CellUnion)
|
||||||
|
// Expand
|
||||||
|
// ContainsPoint
|
||||||
|
// AverageArea/ApproxArea/ExactArea
|
31
vendor/github.com/golang/geo/s2/doc.go
generated
vendored
Normal file
31
vendor/github.com/golang/geo/s2/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package s2 implements types and functions for working with geometry in S² (spherical geometry).
|
||||||
|
|
||||||
|
Its related packages, parallel to this one, are s1 (operates on S¹), r1 (operates on ℝ¹)
|
||||||
|
and r3 (operates on ℝ³).
|
||||||
|
|
||||||
|
This package provides types and functions for the S2 cell hierarchy and coordinate systems.
|
||||||
|
The S2 cell hierarchy is a hierarchical decomposition of the surface of a unit sphere (S²)
|
||||||
|
into ``cells''; it is highly efficient, scales from continental size to under 1 cm²
|
||||||
|
and preserves spatial locality (nearby cells have close IDs).
|
||||||
|
|
||||||
|
A presentation that gives an overview of S2 is
|
||||||
|
https://docs.google.com/presentation/d/1Hl4KapfAENAOf4gv-pSngKwvS_jwNVHRPZTTDzXXn6Q/view.
|
||||||
|
*/
|
||||||
|
package s2
|
1405
vendor/github.com/golang/geo/s2/edgeutil.go
generated
vendored
Normal file
1405
vendor/github.com/golang/geo/s2/edgeutil.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
97
vendor/github.com/golang/geo/s2/latlng.go
generated
vendored
Normal file
97
vendor/github.com/golang/geo/s2/latlng.go
generated
vendored
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package s2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/golang/geo/r3"
|
||||||
|
"github.com/golang/geo/s1"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
northPoleLat = s1.Angle(math.Pi/2) * s1.Radian
|
||||||
|
southPoleLat = -northPoleLat
|
||||||
|
)
|
||||||
|
|
||||||
|
// LatLng represents a point on the unit sphere as a pair of angles.
|
||||||
|
type LatLng struct {
|
||||||
|
Lat, Lng s1.Angle
|
||||||
|
}
|
||||||
|
|
||||||
|
// LatLngFromDegrees returns a LatLng for the coordinates given in degrees.
|
||||||
|
func LatLngFromDegrees(lat, lng float64) LatLng {
|
||||||
|
return LatLng{s1.Angle(lat) * s1.Degree, s1.Angle(lng) * s1.Degree}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid returns true iff the LatLng is normalized, with Lat ∈ [-π/2,π/2] and Lng ∈ [-π,π].
|
||||||
|
func (ll LatLng) IsValid() bool {
|
||||||
|
return math.Abs(ll.Lat.Radians()) <= math.Pi/2 && math.Abs(ll.Lng.Radians()) <= math.Pi
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalized returns the normalized version of the LatLng,
|
||||||
|
// with Lat clamped to [-π/2,π/2] and Lng wrapped in [-π,π].
|
||||||
|
func (ll LatLng) Normalized() LatLng {
|
||||||
|
lat := ll.Lat
|
||||||
|
if lat > northPoleLat {
|
||||||
|
lat = northPoleLat
|
||||||
|
} else if lat < southPoleLat {
|
||||||
|
lat = southPoleLat
|
||||||
|
}
|
||||||
|
lng := s1.Angle(math.Remainder(ll.Lng.Radians(), 2*math.Pi)) * s1.Radian
|
||||||
|
return LatLng{lat, lng}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ll LatLng) String() string { return fmt.Sprintf("[%v, %v]", ll.Lat, ll.Lng) }
|
||||||
|
|
||||||
|
// Distance returns the angle between two LatLngs.
|
||||||
|
func (ll LatLng) Distance(ll2 LatLng) s1.Angle {
|
||||||
|
// Haversine formula, as used in C++ S2LatLng::GetDistance.
|
||||||
|
lat1, lat2 := ll.Lat.Radians(), ll2.Lat.Radians()
|
||||||
|
lng1, lng2 := ll.Lng.Radians(), ll2.Lng.Radians()
|
||||||
|
dlat := math.Sin(0.5 * (lat2 - lat1))
|
||||||
|
dlng := math.Sin(0.5 * (lng2 - lng1))
|
||||||
|
x := dlat*dlat + dlng*dlng*math.Cos(lat1)*math.Cos(lat2)
|
||||||
|
return s1.Angle(2*math.Atan2(math.Sqrt(x), math.Sqrt(math.Max(0, 1-x)))) * s1.Radian
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(mikeperrow): The C++ implementation publicly exposes latitude/longitude
|
||||||
|
// functions. Let's see if that's really necessary before exposing the same functionality.
|
||||||
|
|
||||||
|
func latitude(p Point) s1.Angle {
|
||||||
|
return s1.Angle(math.Atan2(p.Z, math.Sqrt(p.X*p.X+p.Y*p.Y))) * s1.Radian
|
||||||
|
}
|
||||||
|
|
||||||
|
func longitude(p Point) s1.Angle {
|
||||||
|
return s1.Angle(math.Atan2(p.Y, p.X)) * s1.Radian
|
||||||
|
}
|
||||||
|
|
||||||
|
// PointFromLatLng returns an Point for the given LatLng.
|
||||||
|
// The maximum error in the result is 1.5 * dblEpsilon. (This does not
|
||||||
|
// include the error of converting degrees, E5, E6, or E7 into radians.)
|
||||||
|
func PointFromLatLng(ll LatLng) Point {
|
||||||
|
phi := ll.Lat.Radians()
|
||||||
|
theta := ll.Lng.Radians()
|
||||||
|
cosphi := math.Cos(phi)
|
||||||
|
return Point{r3.Vector{math.Cos(theta) * cosphi, math.Sin(theta) * cosphi, math.Sin(phi)}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LatLngFromPoint returns an LatLng for a given Point.
|
||||||
|
func LatLngFromPoint(p Point) LatLng {
|
||||||
|
return LatLng{latitude(p), longitude(p)}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue