diff --git a/Makefile b/Makefile index 29897649d2..3bb82e44be 100644 --- a/Makefile +++ b/Makefile @@ -224,8 +224,7 @@ docker-mobile: DOCKER_CONTEXT=.$(DOCKER_MOBILE_IMAGE_TAG)-context && \ mkdir -p $$DOCKER_CONTEXT && \ cp $(LANTERN_MOBILE_DIR)/Dockerfile $$DOCKER_CONTEXT && \ - docker build -t $(DOCKER_MOBILE_IMAGE_TAG) $$DOCKER_CONTEXT && \ - $(GO) get -d golang.org/x/mobile/example/... + docker build -t $(DOCKER_MOBILE_IMAGE_TAG) $$DOCKER_CONTEXT linux: genassets linux-386 linux-amd64 linux-arm diff --git a/README.md b/README.md index c92e521a45..6c647c5b9c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # lantern [![Travis CI Status](https://travis-ci.org/getlantern/lantern.svg?branch=valencia)](https://travis-ci.org/getlantern/lantern) [![Coverage Status](https://coveralls.io/repos/getlantern/lantern/badge.png?branch=valencia)](https://coveralls.io/r/getlantern/lantern) lantern is a [gost](https://github.com/getlantern/gost) project that -provides repeatable builds and consolidated pull requests for lantern. **It's very important to read the gost documentation thoroughly in -order to build this project.** +provides repeatable builds and consolidated pull requests for lantern. ## Building Lantern @@ -18,6 +17,32 @@ for Windows and Linux, in order to compile Lantern for OSX you'll need an OSX host, this is a limitation caused by Lantern depending on C code and OSX build tools for certain features. + +### Contributing changes + +Go code in Lantern must pass several tests: + +* [errcheck](https://github.com/kisielk/errcheck) +* [golint](https://github.com/golang/lint) +* Go vet +* Go test -race + +You can find a generic [git-hook](https://github.com/getlantern/lantern/blob/valencia/git-hook) +file, which can be used as a pre-push (or pre-commit) hook to automatically +ensure these tests are passed before committing any code. Only Go packages in +`src/github.com/getlantern` will be tested, and only those that have changes in +them. + +Install by copying it into the local `.git/hooks/` directory, with the `pre-push` +file name if you want to run it before pushing. Alternatively, you can name it +`pre-commit` to run it before each commit.. + +**Important notice** + +If you *must* commit without running the hooks, you can run git with the +`--no-verify` flag. + + ### Building the docker image In order to build the docker image open a terminal, `cd` into the diff --git a/git-hook b/git-hook new file mode 100644 index 0000000000..ad718b9275 --- /dev/null +++ b/git-hook @@ -0,0 +1,63 @@ +#!/bin/sh + +# Git pre-push hook for the Lantern project +# Maintainer: Ulysses Aalto +# +# Installation: Copy into .git/hooks/pre-push + + +# Exit immediately if a command exits with a non-zero status. +set -e + +# Find only modified files/directories +MODIFIED_DIRS=$(git status --porcelain | \ + awk 'match($1, "M") && match($2, "src/github.com/getlantern/*"){print $2}' | \ + sed 's+src/github.com/getlantern/++g' | \ + sed 's+/.*++' | \ + uniq) +echo "Running hook -- Analyzing modified packages..." +for i in $MODIFIED_DIRS; do + echo " * $i"; +done +echo + +cd src/github.com/getlantern + +which errcheck >/dev/null || (echo "Unable to find errcheck, please install it: \`go get github.com/kisielk/errcheck\`" && exit 1) +which golint >/dev/null || (echo "Unable to find golint, please install it: \`go get -u github.com/golang/lint/golint\`" && exit 1) + +echo "*** Running \033[0;34mErrcheck\033[0m ***" && \ +for dir in $MODIFIED_DIRS; do + errcheck github.com/getlantern/$dir || (\ + echo "\033[0;31merrcheck returned error analyzing the package '$dir'\033[0m" && \ + echo "Please, fix and run again\n" && \ + exit 1) +done +echo "\033[1;34mErrcheck\033[0m ran successfully\n" + +echo "*** Running \033[0;34mGo vet\033[0m ***" && \ +for dir in $MODIFIED_DIRS; do + go vet github.com/getlantern/$dir || (\ + echo "\033[0;31mgo vet returned error analyzing the package '$dir'\033[0m" + echo "Please, fix and run again\n" && \ + exit 1) +done +echo "\033[1;34mGo vet\033[0m ran successfully\n" + +echo "*** Running \033[0;34mGolint\033[0m ***" && \ +for dir in $MODIFIED_DIRS; do + golint github.com/getlantern/$dir || (\ + echo "\033[0;31mgo vet returned error analyzing the package '$dir'\033[0m" + echo "Please, fix and run again\n" && \ + exit 1) +done +echo "\033[1;34mGolint\033[0m ran successfully\n" + +echo "*** Running \033[0;34mGo test -race\033[0m ***" && \ +for dir in $MODIFIED_DIRS; do + go test -race github.com/getlantern/$dir || (\ + echo "\033[0;31mgo vet returned error analyzing the package '$dir'\033[0m" + echo "Please, fix and run again\n" && \ + exit 1) +done +echo "\033[1;34mGo test -race\033[0m ran successfully\n" diff --git a/installer-resources/windows/lantern.nsi b/installer-resources/windows/lantern.nsi index db90828b8c..593d00cb98 100755 --- a/installer-resources/windows/lantern.nsi +++ b/installer-resources/windows/lantern.nsi @@ -82,7 +82,7 @@ Section CreateShortCut "$DESKTOP\Lantern.lnk" "$INSTDIR\lantern.exe" "" "$INSTDIR\lantern.ico" 0 # This is a bad registry entry created by old Lantern versions. - DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Run\value" + DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "value" # Add a registry key to set -clear-proxy-settings. See https://github.com/getlantern/lantern/issues/2776 WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Run" \ @@ -135,7 +135,7 @@ Section "uninstall" DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Lantern" # Don't run Lantern on startup. - DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Run\Lantern" + DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "Lantern" ${nsProcess::Unload} SectionEnd diff --git a/src/github.com/getlantern/analytics/analytics.go b/src/github.com/getlantern/analytics/analytics.go index 8d3da9eae8..f0df38d9ad 100644 --- a/src/github.com/getlantern/analytics/analytics.go +++ b/src/github.com/getlantern/analytics/analytics.go @@ -3,6 +3,7 @@ package analytics import ( "bytes" "net/http" + "net/http/httputil" "net/url" "runtime" "strconv" @@ -77,7 +78,11 @@ func Configure(trackingId string, version string, proxyAddr string) { return } // Store new session info whenever client proxy is ready - sessionEvent(trackingId, version) + if status, err := sessionEvent(trackingId, version); err != nil { + log.Errorf("Unable to store new session info: %v", err) + } else { + log.Tracef("Storing new session info: %v", status) + } }() } @@ -147,14 +152,23 @@ func SendRequest(payload *Payload) (status bool, err error) { r.Header.Add("Content-Type", "application/x-www-form-urlencoded") r.Header.Add("Content-Length", strconv.Itoa(len(args))) + if req, err := httputil.DumpRequestOut(r, true); err != nil { + log.Debugf("Could not dump request: %v", err) + } else { + log.Debugf("Full analytics request: %v", string(req)) + } + resp, err := httpClient.Do(r) if err != nil { log.Errorf("Could not send HTTP request to GA: %s", err) return false, err } log.Debugf("Successfully sent request to GA: %s", resp.Status) - defer resp.Body.Close() - + defer func() { + if err := resp.Body.Close(); err != nil { + log.Debugf("Unable to close response body: %v", err) + } + }() return true, nil } diff --git a/src/github.com/getlantern/appdir/appdir_linux.go b/src/github.com/getlantern/appdir/appdir_linux.go index 7430d29d87..d39c8a97fd 100644 --- a/src/github.com/getlantern/appdir/appdir_linux.go +++ b/src/github.com/getlantern/appdir/appdir_linux.go @@ -4,14 +4,21 @@ package appdir import ( "fmt" "path/filepath" + "runtime" "strings" ) func general(app string) string { - // It is more common on Linux to expect application related directories - // in all lowercase. The lantern wrapper also expects a lowercased - // directory. - return InHomeDir(fmt.Sprintf(".%s", strings.ToLower(app))) + if runtime.GOOS == "android" { + // TODO: Go for Android currently doesn't support Home Directory. + // Remove as soon as this is available in the future + return fmt.Sprintf(".%s", strings.ToLower(app)) + } else { + // It is more common on Linux to expect application related directories + // in all lowercase. The lantern wrapper also expects a lowercased + // directory. + return InHomeDir(fmt.Sprintf(".%s", strings.ToLower(app))) + } } func logs(app string) string { diff --git a/src/github.com/getlantern/autoupdate-server/main.go b/src/github.com/getlantern/autoupdate-server/main.go index cdd2488912..3f79c68539 100644 --- a/src/github.com/getlantern/autoupdate-server/main.go +++ b/src/github.com/getlantern/autoupdate-server/main.go @@ -50,7 +50,9 @@ func backgroundUpdate() { func (u *updateHandler) closeWithStatus(w http.ResponseWriter, status int) { w.WriteHeader(status) - w.Write([]byte(http.StatusText(status))) + if _, err := w.Write([]byte(http.StatusText(status))); err != nil { + log.Debugf("Unable to write status: %v", err) + } } func (u *updateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -58,7 +60,11 @@ func (u *updateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { var res *server.Result if r.Method == "POST" { - defer r.Body.Close() + defer func() { + if err := r.Body.Close(); err != nil { + log.Debugf("Unable to close request body: %v", err) + } + }() var params server.Params decoder := json.NewDecoder(r.Body) @@ -93,7 +99,9 @@ func (u *updateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json") - w.Write(content) + if _, err := w.Write(content); err != nil { + log.Debugf("Unable to write response: %v", err) + } return } u.closeWithStatus(w, http.StatusNotFound) diff --git a/src/github.com/getlantern/balancer/balancer_test.go b/src/github.com/getlantern/balancer/balancer_test.go index d9c2a55d95..d928191290 100644 --- a/src/github.com/getlantern/balancer/balancer_test.go +++ b/src/github.com/getlantern/balancer/balancer_test.go @@ -66,7 +66,11 @@ func TestAll(t *testing.T) { if err != nil { log.Fatalf("Unable to listen: %s", err) } - defer l.Close() + defer func() { + if err := l.Close(); err != nil { + log.Fatalf("Unable to close listener: %v", err) + } + }() go func() { for { c, err := l.Accept() @@ -264,7 +268,11 @@ func newFailingDialer(num int32, dialedBy *int32, attempts *int32) *Dialer { } func doTestConn(t *testing.T, conn net.Conn) { - defer conn.Close() + defer func() { + if err := conn.Close(); err != nil { + log.Debugf("Unable to close connection: %v", err) + } + }() var wg sync.WaitGroup wg.Add(2) diff --git a/src/github.com/getlantern/balancer/dialer.go b/src/github.com/getlantern/balancer/dialer.go index da6d6a4e2c..35d1416fe5 100644 --- a/src/github.com/getlantern/balancer/dialer.go +++ b/src/github.com/getlantern/balancer/dialer.go @@ -137,7 +137,9 @@ func (d *dialer) defaultCheck() bool { log.Debugf("Error testing dialer %s to humans.txt: %s", d.Label, err) return false, nil } - resp.Body.Close() + if err := resp.Body.Close(); err != nil { + log.Debugf("Unable to close response body: %v", err) + } log.Tracef("Tested dialer %s to humans.txt, status code %d", d.Label, resp.StatusCode) return resp.StatusCode == 200, nil }) diff --git a/src/github.com/getlantern/buuid/buuid.go b/src/github.com/getlantern/buuid/buuid.go index 1ae5a44750..2c09e9c8cd 100644 --- a/src/github.com/getlantern/buuid/buuid.go +++ b/src/github.com/getlantern/buuid/buuid.go @@ -7,6 +7,8 @@ import ( "fmt" "code.google.com/p/go-uuid/uuid" + + "github.com/getlantern/golog" ) const ( @@ -16,6 +18,8 @@ const ( var ( endianness = binary.LittleEndian zero = ID{} + + log = golog.LoggerFor("buuid") ) // ID is a type 4 UUID. @@ -58,7 +62,9 @@ func (id ID) Write(b []byte) error { // ToBytes returns a 16-byte representation of this ID func (id ID) ToBytes() []byte { b := make([]byte, EncodedLength) - id.Write(b) + if err := id.Write(b); err != nil { + log.Errorf("Unable to write as 16-bytes representation: %v", err) + } return b } @@ -70,6 +76,8 @@ func FromString(s string) (ID, error) { // String() returns the string-encoded version like in uuid.UUID. func (id ID) String() string { b := uuid.UUID(make([]byte, EncodedLength)) - id.Write([]byte(b)) + if err := id.Write([]byte(b)); err != nil { + log.Errorf("Unable to write as string: %v", err) + } return b.String() } diff --git a/src/github.com/getlantern/bytecounting/bytecounting_test.go b/src/github.com/getlantern/bytecounting/bytecounting_test.go index aad942657a..02d66d68cd 100644 --- a/src/github.com/getlantern/bytecounting/bytecounting_test.go +++ b/src/github.com/getlantern/bytecounting/bytecounting_test.go @@ -42,7 +42,11 @@ func TestCounting(t *testing.T) { atomic.AddInt64(&lw, bytes) }, } - defer il.Close() + defer func() { + if err := il.Close(); err != nil { + t.Fatalf("Unable to close listener: %v", err) + } + }() go func() { conn, err := il.Accept() @@ -50,9 +54,15 @@ func TestCounting(t *testing.T) { t.Fatalf("Unable to accept: %s", err) } b := make([]byte, len(req)) - conn.Read(b) - conn.Write(resp) - conn.Close() + if _, err := conn.Read(b); err != nil { + t.Fatalf("Unable to read from connection: %v", err) + } + if _, err := conn.Write(resp); err != nil { + t.Fatalf("Unable to write to connection: %v", err) + } + if err := conn.Close(); err != nil { + t.Fatalf("Unable to close connection: %v", err) + } }() addr := il.Addr().String() @@ -76,31 +86,44 @@ func TestCounting(t *testing.T) { assert.Equal(t, c.RemoteAddr(), conn.RemoteAddr(), "RemoteAddr should be same as on underlying") // Test short ReadDeadline - conn.SetReadDeadline(time.Now().Add(-1 * time.Second)) + if err := conn.SetReadDeadline(time.Now().Add(-1 * time.Second)); err != nil { + t.Fatalf("Unable to set read deadline: %v", err) + } b := make([]byte, len(resp)) _, err = conn.Read(b) assertTimeoutError(t, err) - conn.SetReadDeadline(time.Now().Add(1 * time.Hour)) + if err := conn.SetReadDeadline(time.Now().Add(1 * time.Hour)); err != nil { + t.Fatalf("Unable to set read deadline: %v", err) + } // Test short WriteDeadline - conn.SetWriteDeadline(time.Now().Add(-1 * time.Second)) + if err := conn.SetWriteDeadline(time.Now().Add(-1 * time.Second)); err != nil { + t.Fatalf("Unable to set read deadline: %v", err) + } _, err = conn.Write([]byte{}) assertTimeoutError(t, err) - conn.SetWriteDeadline(time.Now().Add(1 * time.Hour)) + if err := conn.SetWriteDeadline(time.Now().Add(1 * time.Hour)); err != nil { + t.Fatalf("Unable to set read deadline: %v", err) + } // Test short Deadline - conn.SetDeadline(time.Now().Add(-1 * time.Second)) + if err := conn.SetDeadline(time.Now().Add(-1 * time.Second)); err != nil { + t.Fatalf("Unable to set read deadline: %v", err) + } _, err = conn.Read(b) assertTimeoutError(t, err) _, err = conn.Write([]byte{}) assertTimeoutError(t, err) - conn.SetDeadline(time.Now().Add(1 * time.Hour)) + if err := conn.SetDeadline(time.Now().Add(1 * time.Hour)); err != nil { + t.Fatalf("Unable to set read deadline: %v", err) + } - _, err = conn.Write(req) - if err != nil { + if _, err = conn.Write(req); err != nil { t.Fatalf("Unable to write: %v", err) } - ioutil.ReadAll(conn) + if _, err := ioutil.ReadAll(conn); err != nil { + t.Fatalf("Unable to read: %v", err) + } assert.Equal(t, int64(len(resp)), cr, "Wrong number of bytes read by conn") assert.Equal(t, int64(len(req)), cw, "Wrong number of bytes written by conn") diff --git a/src/github.com/getlantern/byteexec/byteexec_test.go b/src/github.com/getlantern/byteexec/byteexec_test.go index cc5963df06..4ff3aad16e 100644 --- a/src/github.com/getlantern/byteexec/byteexec_test.go +++ b/src/github.com/getlantern/byteexec/byteexec_test.go @@ -52,7 +52,9 @@ func TestExec(t *testing.T) { // Now mess with the file contents and make sure it gets overwritten on next // ByteExec - ioutil.WriteFile(be.Filename, []byte("Junk"), 0755) + if err := ioutil.WriteFile(be.Filename, []byte("Junk"), 0755); err != nil { + t.Fatalf("Unable to write file: %v", err) + } be = createByteExec(t, data) updatedInfo = testByteExec(t, be) assert.NotEqual(t, originalInfo.ModTime(), updatedInfo.ModTime(), "File modification time should be changed after creating new ByteExec on bad data") diff --git a/src/github.com/getlantern/chained/chained_test.go b/src/github.com/getlantern/chained/chained_test.go index fad5d3ffc8..2410d08708 100644 --- a/src/github.com/getlantern/chained/chained_test.go +++ b/src/github.com/getlantern/chained/chained_test.go @@ -40,7 +40,9 @@ func TestBadServer(t *testing.T) { go func() { conn, err := l.Accept() if err == nil { - conn.Close() + if err := conn.Close(); err != nil { + t.Fatalf("Unable to close connection: %v", err) + } } }() @@ -64,7 +66,11 @@ func TestBadConnectStatus(t *testing.T) { resp.WriteHeader(403) // forbidden }), } - go hs.Serve(l) + go func() { + if err := hs.Serve(l); err != nil { + t.Fatalf("Unable to serve: %v", err) + } + }() dialer := NewDialer(Config{ DialServer: func() (net.Conn, error) { diff --git a/src/github.com/getlantern/chained/dialer.go b/src/github.com/getlantern/chained/dialer.go index 850fa065e3..c73d22afcc 100644 --- a/src/github.com/getlantern/chained/dialer.go +++ b/src/github.com/getlantern/chained/dialer.go @@ -40,9 +40,9 @@ func (d *dialer) Dial(network, addr string) (net.Conn, error) { if err != nil { return nil, fmt.Errorf("Unable to dial server: %s", err) } - err = d.sendCONNECT(network, addr, conn) - if err != nil { - conn.Close() + if err := d.sendCONNECT(network, addr, conn); err != nil { + // We discard this error, since we are only interested in sendCONNECT + _ = conn.Close() return nil, err } return conn, nil diff --git a/src/github.com/getlantern/chained/server.go b/src/github.com/getlantern/chained/server.go index 0c8ba68efb..cd5df57d26 100644 --- a/src/github.com/getlantern/chained/server.go +++ b/src/github.com/getlantern/chained/server.go @@ -50,7 +50,14 @@ func (s *Server) ServeHTTP(resp http.ResponseWriter, req *http.Request) { fmt.Fprintf(resp, "Unable to dial %s : %s", address, err) return } - defer connOut.Close() + + closeConnection := func(conn net.Conn) { + if err := conn.Close(); err != nil { + log.Errorf("Unable to close connection: %v", err) + } + } + + defer closeConnection(connOut) resp.WriteHeader(http.StatusOK) fmt.Fprint(resp, "CONNECT OK") fl.Flush() @@ -60,16 +67,20 @@ func (s *Server) ServeHTTP(resp http.ResponseWriter, req *http.Request) { log.Errorf("Unable to hijack connection: %s", err) return } - defer connIn.Close() + defer closeConnection(connIn) var wg sync.WaitGroup wg.Add(2) go func() { - io.Copy(connOut, connIn) + if _, err := io.Copy(connOut, connIn); err != nil { + log.Errorf("Unable to pipe in->out: %v", err) + } wg.Done() }() go func() { - io.Copy(connIn, connOut) + if _, err := io.Copy(connIn, connOut); err != nil { + log.Errorf("Unable to pipe out->in: %v", err) + } wg.Done() }() wg.Wait() diff --git a/src/github.com/getlantern/checkfallbacks/checkfallbacks.go b/src/github.com/getlantern/checkfallbacks/checkfallbacks.go index 9a0317b232..84bc4ffc37 100644 --- a/src/github.com/getlantern/checkfallbacks/checkfallbacks.go +++ b/src/github.com/getlantern/checkfallbacks/checkfallbacks.go @@ -134,7 +134,11 @@ func testFallbackServer(fb *client.ChainedServerInfo, workerId int) (err error) if err != nil { return fmt.Errorf("%v: requesting humans.txt failed: %v", fb.Addr, err) } - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + log.Debugf("Unable to close response body: %v", err) + } + }() if resp.StatusCode != 200 { return fmt.Errorf("%v: bad status code: %v", fb.Addr, resp.StatusCode) } diff --git a/src/github.com/getlantern/cloudflare/api.go b/src/github.com/getlantern/cloudflare/api.go index f37ce3b4ab..cd05c6f0b0 100644 --- a/src/github.com/getlantern/cloudflare/api.go +++ b/src/github.com/getlantern/cloudflare/api.go @@ -7,6 +7,12 @@ import ( "net/http" "net/url" "os" + + "github.com/getlantern/golog" +) + +var ( + log = golog.LoggerFor("cloudflare") ) // Client provides a client to the CloudflAre API @@ -85,7 +91,9 @@ func (c *Client) NewRequest(params map[string]string, method string, action stri // decodeBody is used to JSON decode a body func decodeBody(resp *http.Response, out interface{}) error { body, err := ioutil.ReadAll(resp.Body) - resp.Body.Close() + if err := resp.Body.Close(); err != nil { + log.Debugf("Unable to close response body: %v", err) + } if err != nil { return err diff --git a/src/github.com/getlantern/cloudflare/record.go b/src/github.com/getlantern/cloudflare/record.go index 6c643cd51a..97533d1c75 100644 --- a/src/github.com/getlantern/cloudflare/record.go +++ b/src/github.com/getlantern/cloudflare/record.go @@ -140,7 +140,11 @@ func (c *Client) CreateRecord(domain string, opts *CreateRecord) (*Record, error resp, err := checkResp(c.Http.Do(req)) if resp != nil && resp.Body != nil { - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + log.Debugf("Unable to close body of response: %v", err) + } + }() } if err != nil { return nil, fmt.Errorf("Error creating record: %s", err) @@ -178,7 +182,11 @@ func (c *Client) DestroyRecord(domain string, id string) error { resp, err := checkResp(c.Http.Do(req)) if resp != nil && resp.Body != nil { - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + log.Debugf("Unable to close response body: %v", err) + } + }() } if err != nil { return fmt.Errorf("Error deleting record: %s", err) @@ -248,7 +256,11 @@ func (c *Client) UpdateRecord(domain string, id string, opts *UpdateRecord) erro resp, err := checkResp(c.Http.Do(req)) if resp != nil && resp.Body != nil { - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + log.Debugf("Unable to close response body: %v", err) + } + }() } if err != nil { return fmt.Errorf("Error updating record: %s", err) @@ -331,7 +343,9 @@ func (c *Client) loadAll(params *map[string]string) (*RecordsResponse, error) { resp, err := checkResp(c.Http.Do(req)) if resp != nil && resp.Body != nil { defer func() { - resp.Body.Close() + if err := resp.Body.Close(); err != nil { + log.Debugf("Unable to close response body: %v", err) + } }() } if err != nil { diff --git a/src/github.com/getlantern/connpool/connpool_test.go b/src/github.com/getlantern/connpool/connpool_test.go index dcb33d89e9..cd8cba3286 100644 --- a/src/github.com/getlantern/connpool/connpool_test.go +++ b/src/github.com/getlantern/connpool/connpool_test.go @@ -45,7 +45,9 @@ func TestIt(t *testing.T) { // Close about half of the connections immediately to test /// closed checking if err == nil && rand.Float32() > 0.5 { - conn.Close() + if err := conn.Close(); err != nil { + t.Fatalf("Unable to close connection: %v", err) + } } } return conn, err @@ -118,7 +120,9 @@ func TestDialFailure(t *testing.T) { // Try to get connection, make sure it fails conn, err := p.Get() if !assert.Error(t, err, "Dialing should have failed") { - conn.Close() + if err := conn.Close(); err != nil { + t.Fatalf("Unable to close connection: %v", err) + } } // Wait for fill to run for a while with a failing connection @@ -174,7 +178,9 @@ func connectAndRead(t *testing.T, p Pool, loops int) { t.Fatalf("Error reading from connection: %s", err) } assert.Equal(t, msg, read, "Should have received %s from server", string(msg)) - c.Close() + if err := c.Close(); err != nil { + t.Fatalf("Unable to close connection: %v", err) + } wg.Done() }(&wg) @@ -198,11 +204,12 @@ func startTestServer() (string, error) { if err != nil { log.Fatalf("Error listening: %s", err) } - _, err = c.Write(msg) - if err != nil { + if _, err = c.Write(msg); err != nil { log.Fatalf("Unable to write message: %s", err) } - c.Close() + if err := c.Close(); err != nil { + log.Fatalf("Unable to close connection: %v", err) + } } }() return l.Addr().String(), nil diff --git a/src/github.com/getlantern/detour/detour.go b/src/github.com/getlantern/detour/detour.go index c7b2f381be..a99da45a65 100644 --- a/src/github.com/getlantern/detour/detour.go +++ b/src/github.com/getlantern/detour/detour.go @@ -51,7 +51,7 @@ var TimeoutToDetour = 3 * time.Second // if DirectAddrCh is set, when a direct connection is closed without any error, // the connection's remote address (in host:port format) will be send to it -var DirectAddrCh chan string +var DirectAddrCh chan string = make(chan string) var ( log = golog.LoggerFor("detour") @@ -124,7 +124,9 @@ func Dialer(d dialFunc) dialFunc { return dc, nil } log.Debugf("Dial %s to %s, dns hijacked, try detour", dc.stateDesc(), addr) - dc.conn.Close() + if err := dc.conn.Close(); err != nil { + log.Debugf("Unable to close connection: %v", err) + } } else if detector.TamperingSuspected(err) { log.Debugf("Dial %s to %s failed, try detour: %s", dc.stateDesc(), addr, err) } else { @@ -162,9 +164,13 @@ func (dc *Conn) Read(b []byte) (n int, err error) { return dc.countedRead(b) } // wait for at most TimeoutToDetour to read - dc.getConn().SetReadDeadline(start.Add(TimeoutToDetour)) + if err := dc.getConn().SetReadDeadline(start.Add(TimeoutToDetour)); err != nil { + log.Debugf("Unable to set read deadline: %v", err) + } n, err = dc.countedRead(b) - dc.getConn().SetReadDeadline(dc.readDeadline) + if err := dc.getConn().SetReadDeadline(dc.readDeadline); err != nil { + log.Debugf("Unable to set read deadline: %v", err) + } detector := blockDetector.Load().(*Detector) if err != nil { @@ -318,22 +324,30 @@ func (dc *Conn) RemoteAddr() net.Addr { // SetDeadline() implements the function from net.Conn func (dc *Conn) SetDeadline(t time.Time) error { - dc.SetReadDeadline(t) - dc.SetWriteDeadline(t) + if err := dc.SetReadDeadline(t); err != nil { + log.Debugf("Unable to set read deadline: %v", err) + } + if err := dc.SetWriteDeadline(t); err != nil { + log.Debugf("Unable to set write deadline: %v", err) + } return nil } // SetReadDeadline() implements the function from net.Conn func (dc *Conn) SetReadDeadline(t time.Time) error { dc.readDeadline = t - dc.getConn().SetReadDeadline(t) + if err := dc.getConn().SetReadDeadline(t); err != nil { + log.Debugf("Unable to set read deadline: %v", err) + } return nil } // SetWriteDeadline() implements the function from net.Conn func (dc *Conn) SetWriteDeadline(t time.Time) error { dc.writeDeadline = t - dc.getConn().SetWriteDeadline(t) + if err := dc.getConn().SetWriteDeadline(t); err != nil { + log.Debugf("Unable to set write deadline", err) + } return nil } @@ -389,10 +403,16 @@ func (dc *Conn) setConn(c net.Conn) { oldConn := dc.conn dc.conn = c dc.muConn.Unlock() - dc.conn.SetReadDeadline(dc.readDeadline) - dc.conn.SetWriteDeadline(dc.writeDeadline) + if err := dc.conn.SetReadDeadline(dc.readDeadline); err != nil { + log.Debugf("Unable to set read deadline: %v", err) + } + if err := dc.conn.SetWriteDeadline(dc.writeDeadline); err != nil { + log.Debugf("Unable to set write deadline: %v", err) + } log.Tracef("Replaced connection to %s from direct to detour and closing old one", dc.addr) - oldConn.Close() + if err := oldConn.Close(); err != nil { + log.Debugf("Unable to close old connection: %v", err) + } } func (dc *Conn) stateDesc() string { diff --git a/src/github.com/getlantern/detour/detour_test.go b/src/github.com/getlantern/detour/detour_test.go index f45b73a6db..ffc2ae4524 100644 --- a/src/github.com/getlantern/detour/detour_test.go +++ b/src/github.com/getlantern/detour/detour_test.go @@ -117,7 +117,9 @@ func TestClosing(t *testing.T) { mock.Msg(directMsg) DirectAddrCh = make(chan string) { - newClient(proxiedURL, 100*time.Millisecond).Get(mockURL) + if _, err := newClient(proxiedURL, 100*time.Millisecond).Get(mockURL); err != nil { + log.Debugf("Unable to send GET request to mock URL: %v", err) + } } u, _ := url.Parse(mockURL) addr := <-DirectAddrCh diff --git a/src/github.com/getlantern/detour/mock_test.go b/src/github.com/getlantern/detour/mock_test.go index e09425b5a0..5f2cc76e2a 100644 --- a/src/github.com/getlantern/detour/mock_test.go +++ b/src/github.com/getlantern/detour/mock_test.go @@ -19,14 +19,20 @@ func (m *mockHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (m *mockHandler) Raw(msg string) { m.writer = func(w http.ResponseWriter) { conn, _, _ := w.(http.Hijacker).Hijack() - conn.Write([]byte(msg)) - conn.Close() + if _, err := conn.Write([]byte(msg)); err != nil { + log.Debugf("Unable to write to connection: %v", err) + } + if err := conn.Close(); err != nil { + log.Debugf("Unable to close connection: %v", err) + } } } func (m *mockHandler) Msg(msg string) { m.writer = func(w http.ResponseWriter) { - w.Write([]byte(msg)) + if _, err := w.Write([]byte(msg)); err != nil { + log.Debugf("Unable to write to connection: %v", err) + } w.(http.Flusher).Flush() } } @@ -34,7 +40,9 @@ func (m *mockHandler) Msg(msg string) { func (m *mockHandler) Timeout(d time.Duration, msg string) { m.writer = func(w http.ResponseWriter) { time.Sleep(d) - w.Write([]byte(msg)) + if _, err := w.Write([]byte(msg)); err != nil { + log.Debugf("Unable to write to connection: %v", err) + } w.(http.Flusher).Flush() } } diff --git a/src/github.com/getlantern/enproxy/conn_impl.go b/src/github.com/getlantern/enproxy/conn_impl.go index 970e2db9cf..bead364c07 100644 --- a/src/github.com/getlantern/enproxy/conn_impl.go +++ b/src/github.com/getlantern/enproxy/conn_impl.go @@ -43,9 +43,13 @@ func Dial(addr string, config *Config) (net.Conn, error) { return idletiming.Conn(c, c.config.IdleTimeout, func() { log.Debugf("Proxy connection to %s via %s idle for %v, closing", addr, proxyConn.conn.RemoteAddr(), c.config.IdleTimeout) - c.Close() + if err := c.Close(); err != nil { + log.Debugf("Unable to close connection: %v", err) + } // Close the initial proxyConn just in case - proxyConn.conn.Close() + if err := proxyConn.conn.Close(); err != nil { + log.Debugf("Unable to close proxy connection: %v", err) + } }), nil } @@ -114,7 +118,9 @@ func (c *conn) redialProxyIfNecessary(proxyConn *connInfo) (*connInfo, error) { proxyConn.closedMutex.Lock() defer proxyConn.closedMutex.Unlock() if proxyConn.closed || proxyConn.conn.TimesOutIn() < oneSecond { - proxyConn.conn.Close() + if err := proxyConn.conn.Close(); err != nil { + log.Debugf("Unable to close proxy connection: %v", err) + } return c.dialProxy() } else { return proxyConn, nil @@ -170,7 +176,9 @@ func (c *conn) doRequest(proxyConn *connInfo, host string, op string, request *r log.Errorf("Could not dump response: %v", er) err = fmt.Errorf("Bad response status for read from fronting provider: %s", resp.Status) } - resp.Body.Close() + if err := resp.Body.Close(); err != nil { + log.Debugf("Unable to close response body: %v", err) + } resp = nil } else { log.Debugf("Got OK from fronting provider") diff --git a/src/github.com/getlantern/enproxy/conn_impl_reads.go b/src/github.com/getlantern/enproxy/conn_impl_reads.go index 287b75fe60..9bdcca4bc7 100644 --- a/src/github.com/getlantern/enproxy/conn_impl_reads.go +++ b/src/github.com/getlantern/enproxy/conn_impl_reads.go @@ -21,10 +21,14 @@ func (c *conn) processReads() { // or it will continuously receives data until hit EOF, // which is a waste of bandwidth. if proxyConn != nil { - proxyConn.conn.Close() + if err := proxyConn.conn.Close(); err != nil { + log.Debugf("Unable to close proxy connection: %v", err) + } } if resp != nil { - resp.Body.Close() + if err := resp.Body.Close(); err != nil { + log.Debugf("Unable to close response body: %v", err) + } } c.doneReadingCh <- true decrement(&readingFinishing) @@ -78,7 +82,9 @@ func (c *conn) processReads() { if err != nil { if err == io.EOF { // Current response is done - resp.Body.Close() + if err := resp.Body.Close(); err != nil { + log.Debugf("Unable to close response body: %v", err) + } resp = nil if hitEOFUpstream { // True EOF, stop reading diff --git a/src/github.com/getlantern/enproxy/conn_impl_requests.go b/src/github.com/getlantern/enproxy/conn_impl_requests.go index d302c6e718..cb803888a1 100644 --- a/src/github.com/getlantern/enproxy/conn_impl_requests.go +++ b/src/github.com/getlantern/enproxy/conn_impl_requests.go @@ -22,7 +22,9 @@ func (c *conn) processRequests(proxyConn *connInfo) { // If there's a proxyConn at the time that processRequests() exits, // close it. if !first && proxyConn != nil { - proxyConn.conn.Close() + if err := proxyConn.conn.Close(); err != nil { + log.Debugf("Unable to close proxy connection: %v", err) + } } }() @@ -57,7 +59,9 @@ func (c *conn) processRequests(proxyConn *connInfo) { } if !first { - resp.Body.Close() + if err := resp.Body.Close(); err != nil { + log.Debugf("Unable to close response body: %v", err) + } } else { // On our first request, find out what host we're actually // talking to and remember that for future requests. @@ -111,12 +115,16 @@ func (c *conn) finishRequesting(resp *http.Response, first bool) { increment(&requestingFinishing) close(c.initialResponseCh) if !first && resp != nil { - resp.Body.Close() + if err := resp.Body.Close(); err != nil { + log.Debugf("Unable to close response body: %v", err) + } } // Drain requestsOutCh for req := range c.requestOutCh { decrement(&writingRequestPending) - req.body.Close() + if err := req.body.Close(); err != nil { + log.Debugf("Unable to close request body: %v", err) + } } c.doneRequestingCh <- true decrement(&requestingFinishing) diff --git a/src/github.com/getlantern/enproxy/conn_impl_writes.go b/src/github.com/getlantern/enproxy/conn_impl_writes.go index 2b4bd8b4f8..0436cddcae 100644 --- a/src/github.com/getlantern/enproxy/conn_impl_writes.go +++ b/src/github.com/getlantern/enproxy/conn_impl_writes.go @@ -49,12 +49,16 @@ func (c *conn) processWrites() { // but that's a fairly big structural change on client and // server. increment(&writingWritingEmpty) - c.rs.write(emptyBytes) + if _, err := c.rs.write(emptyBytes); err != nil { + log.Debugf("Unable to write to connection: %v", err) + } decrement(&writingWritingEmpty) } increment(&writingFinishingBody) - c.rs.finishBody() + if err := c.rs.finishBody(); err != nil { + log.Debugf("Unable to write connection finishing body: %v", err) + } decrement(&writingFinishingBody) firstRequest = false @@ -94,7 +98,9 @@ func (c *conn) submitWrite(b []byte) bool { func (c *conn) finishWriting() { increment(&writingFinishing) if c.rs != nil { - c.rs.finishBody() + if err := c.rs.finishBody(); err != nil { + log.Debugf("Unable to write connection finishing body: %v", err) + } } close(c.requestOutCh) c.doneWritingCh <- true diff --git a/src/github.com/getlantern/enproxy/conn_intf.go b/src/github.com/getlantern/enproxy/conn_intf.go index a65426e1d0..5fe6206a11 100644 --- a/src/github.com/getlantern/enproxy/conn_intf.go +++ b/src/github.com/getlantern/enproxy/conn_intf.go @@ -252,7 +252,11 @@ func (c *conn) fail(err error) { } } - go c.Close() + go func() { + if err := c.Close(); err != nil { + log.Debugf("Unable to close connection: %v", err) + } + }() } func (c *conn) getAsyncErr() error { diff --git a/src/github.com/getlantern/enproxy/conn_test.go b/src/github.com/getlantern/enproxy/conn_test.go index 1f261fe612..f4f96f06b8 100644 --- a/src/github.com/getlantern/enproxy/conn_test.go +++ b/src/github.com/getlantern/enproxy/conn_test.go @@ -115,7 +115,9 @@ func TestHTTPRedirect(t *testing.T) { resp, err := client.Head("http://www.facebook.com") if assert.NoError(t, err, "Head request to facebook should have succeeded") { - resp.Body.Close() + if err := resp.Body.Close(); err != nil { + log.Debugf("Unable to close response body: %v", err) + } } assert.NoError(t, counter.AssertDelta(2), "All file descriptors except the connection from proxy to destination site should have been closed") @@ -211,7 +213,11 @@ func doTestBad(buffered bool, t *testing.T) { conn, err := prepareConn(httpAddr, buffered, true, t, nil) if err == nil { - defer conn.Close() + defer func() { + if err := conn.Close(); err != nil { + log.Debugf("Unable to close connection: %v", err) + } + }() t.Error("Bad conn should have returned error on Connect()") } } @@ -396,7 +402,9 @@ func doStartServer(t *testing.T, l net.Listener) { go func() { httpServer := &http.Server{ Handler: http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { - resp.Write([]byte(TEXT)) + if _, err := resp.Write([]byte(TEXT)); err != nil { + log.Debugf("Unable to write response: %v", err) + } }), } err := httpServer.Serve(l) diff --git a/src/github.com/getlantern/enproxy/lazyconn.go b/src/github.com/getlantern/enproxy/lazyconn.go index 07e407dcac..d630165b64 100644 --- a/src/github.com/getlantern/enproxy/lazyconn.go +++ b/src/github.com/getlantern/enproxy/lazyconn.go @@ -49,7 +49,9 @@ func (l *lazyConn) get() (conn net.Conn, err error) { l.p.connMapMutex.Lock() defer l.p.connMapMutex.Unlock() delete(l.p.connMap, l.id) - conn.Close() + if err := conn.Close(); err != nil { + log.Debugf("Unable to close connection: %v", err) + } }) } diff --git a/src/github.com/getlantern/enproxy/proxy.go b/src/github.com/getlantern/enproxy/proxy.go index 0b32956cb5..9cf7b6c14b 100644 --- a/src/github.com/getlantern/enproxy/proxy.go +++ b/src/github.com/getlantern/enproxy/proxy.go @@ -227,7 +227,9 @@ func (p *Proxy) handleRead(resp http.ResponseWriter, req *http.Request, lc *lazy lastReadTime := time.Now() for { readDeadline := time.Now().Add(p.FlushTimeout) - connOut.SetReadDeadline(readDeadline) + if err := connOut.SetReadDeadline(readDeadline); err != nil { + log.Debugf("Unable to set read deadline: %v", err) + } // Read n, readErr := connOut.Read(b) @@ -255,7 +257,9 @@ func (p *Proxy) handleRead(resp http.ResponseWriter, req *http.Request, lc *lazy _, writeErr := resp.Write(b[:n]) if writeErr != nil { log.Errorf("Error writing to response: %s", writeErr) - connOut.Close() + if err := connOut.Close(); err != nil { + log.Debugf("Unable to close out connection: %v", err) + } return } } @@ -356,5 +360,7 @@ func clientIpFor(req *http.Request) string { func respond(status int, resp http.ResponseWriter, msg string) { log.Errorf(msg) resp.WriteHeader(status) - resp.Write([]byte(msg)) + if _, err := resp.Write([]byte(msg)); err != nil { + log.Debugf("Unable to write response: %v", err) + } } diff --git a/src/github.com/getlantern/enproxy/request_strategy.go b/src/github.com/getlantern/enproxy/request_strategy.go index cd3b4abcd6..82580584f3 100644 --- a/src/github.com/getlantern/enproxy/request_strategy.go +++ b/src/github.com/getlantern/enproxy/request_strategy.go @@ -107,7 +107,9 @@ func (srs *streamingRequestStrategy) write(b []byte) (int, error) { go func() { // Drain the requestFinishedCh err := <-srs.c.requestFinishedCh - writer.Close() + if err := writer.Close(); err != nil { + log.Debugf("Unable to close writer: %v", err) + } if err != nil && err != io.EOF { srs.c.fail(err) } @@ -157,7 +159,9 @@ func (srs *streamingRequestStrategy) finishBody() error { return nil } - srs.writer.Close() + if err := srs.writer.Close(); err != nil { + log.Debugf("Unable to close writer: %v", err) + } srs.writer = nil decrement(&writePipeOpen) diff --git a/src/github.com/getlantern/fdcount/fdcount_test.go b/src/github.com/getlantern/fdcount/fdcount_test.go index 338cd5e1e2..9786c0db67 100644 --- a/src/github.com/getlantern/fdcount/fdcount_test.go +++ b/src/github.com/getlantern/fdcount/fdcount_test.go @@ -1,6 +1,7 @@ package fdcount import ( + "fmt" "net" "testing" "time" @@ -16,7 +17,11 @@ func TestTCP(t *testing.T) { if err != nil { t.Fatal(err) } - defer l0.Close() + defer func() { + if err := l0.Close(); err != nil { + t.Fatalf("Unable to close listener: %v", err) + } + }() start, fdc, err := Matching("TCP") if err != nil { @@ -34,7 +39,11 @@ func TestTCP(t *testing.T) { if err != nil { t.Fatal(err) } - defer l.Close() + defer func() { + if err := l.Close(); err != nil { + fmt.Println("Unable to close listener: %v", err) + } + }() _, middle, err := Matching("TCP") if err != nil { t.Fatal(err) @@ -55,7 +64,9 @@ func TestTCP(t *testing.T) { assert.True(t, len(err.Error()) > 100) } - l.Close() + if err := l.Close(); err != nil { + t.Fatalf("Unable to close listener", err) + } err = middle.AssertDelta(0) if assert.Error(t, err, "Asserting wrong count should fail") { assert.Contains(t, err.Error(), "Expected 0, have -1") @@ -69,13 +80,19 @@ func TestWaitUntilNoneMatchOK(t *testing.T) { if err != nil { t.Fatalf("Unable to dial google: %v", err) } - defer conn.Close() + defer func() { + if err := conn.Close(); err != nil { + fmt.Println("Unable to close connection: %v", err) + } + }() wait := 250 * time.Millisecond start := time.Now() go func() { time.Sleep(wait) - conn.Close() + if err := conn.Close(); err != nil { + t.Fatalf("Unable to close connection: %v", err) + } }() err = WaitUntilNoneMatch("TCP", wait*2) @@ -89,13 +106,19 @@ func TestWaitUntilNoneMatchTimeout(t *testing.T) { if err != nil { t.Fatalf("Unable to dial google: %v", err) } - defer conn.Close() + defer func() { + if err := conn.Close(); err != nil { + fmt.Println("Unable to close connection: %v", err) + } + }() wait := 500 * time.Millisecond start := time.Now() go func() { time.Sleep(wait) - conn.Close() + if err := conn.Close(); err != nil { + t.Fatalf("Unable to close connection: %v", err) + } }() err = WaitUntilNoneMatch("TCP", wait/2) diff --git a/src/github.com/getlantern/filepersist/filepersist.go b/src/github.com/getlantern/filepersist/filepersist.go index 716167dfb8..1f6aec4872 100644 --- a/src/github.com/getlantern/filepersist/filepersist.go +++ b/src/github.com/getlantern/filepersist/filepersist.go @@ -44,11 +44,17 @@ func Save(filename string, data []byte, fileMode os.FileMode) error { log.Tracef("Created new file at %s, writing data", filename) _, err = file.Write(data) if err != nil { - os.Remove(filename) + if err := os.Remove(filename); err != nil { + log.Debugf("Unable to remove file: %v", err) + } return fmt.Errorf("Unable to write to file at %s: %s", filename, err) } - file.Sync() - file.Close() + if err := file.Sync(); err != nil { + log.Debugf("Unable to sync file: %v", err) + } + if err := file.Close(); err != nil { + log.Debugf("Unable to close file: %v", err) + } log.Trace("File saved") return nil diff --git a/src/github.com/getlantern/flashlight/analytics/analytics.go b/src/github.com/getlantern/flashlight/analytics/analytics.go index 01183080aa..d6d2674818 100644 --- a/src/github.com/getlantern/flashlight/analytics/analytics.go +++ b/src/github.com/getlantern/flashlight/analytics/analytics.go @@ -13,7 +13,7 @@ import ( const ( messageType = `Analytics` - TrackingId = "UA-21815217-2" + TrackingId = "UA-21815217-12" ) var ( @@ -89,7 +89,11 @@ func read() { // for now, the only analytics messages we are // currently receiving from the UI are initial page // views which indicate new UI sessions - analytics.SendRequest(&payload) + if status, err := analytics.SendRequest(&payload); err != nil { + log.Debugf("Error sending analytics request: %v", err) + } else { + log.Tracef("Analytics request status: %v", status) + } } } } diff --git a/src/github.com/getlantern/flashlight/config/config.go b/src/github.com/getlantern/flashlight/config/config.go index affd786b0f..1f3d2b1b5b 100644 --- a/src/github.com/getlantern/flashlight/config/config.go +++ b/src/github.com/getlantern/flashlight/config/config.go @@ -18,6 +18,7 @@ import ( "github.com/getlantern/appdir" "github.com/getlantern/fronted" "github.com/getlantern/golog" + "github.com/getlantern/launcher" "github.com/getlantern/proxiedsites" "github.com/getlantern/yaml" "github.com/getlantern/yamlconf" @@ -298,7 +299,8 @@ func (cfg *Config) applyClientDefaults() { if cfg.AutoLaunch == nil { cfg.AutoLaunch = new(bool) - *cfg.AutoLaunch = false + *cfg.AutoLaunch = true + launcher.CreateLaunchFile(*cfg.AutoLaunch) } // Make sure all servers have a QOS and Weight configured @@ -356,7 +358,11 @@ func (cfg Config) fetchCloudConfig() ([]byte, error) { if err != nil { return nil, fmt.Errorf("Unable to fetch cloud config at %s: %s", url, err) } - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + log.Debugf("Error closing response body: %v", err) + } + }() if resp.StatusCode == 304 { log.Debugf("Config unchanged in cloud") diff --git a/src/github.com/getlantern/flashlight/flashlight.go b/src/github.com/getlantern/flashlight/flashlight.go index 46f9745fd7..ccd5ce99ec 100644 --- a/src/github.com/getlantern/flashlight/flashlight.go +++ b/src/github.com/getlantern/flashlight/flashlight.go @@ -97,30 +97,34 @@ func main() { } func _main() { - err := doMain() - if err != nil { + if err := doMain(); err != nil { log.Error(err) } log.Debug("Lantern stopped") - logging.Close() + + if err := logging.Close(); err != nil { + log.Debugf("Error closing log: %v", err) + } os.Exit(0) } func doMain() error { - err := logging.Init() - if err != nil { + if err := logging.Init(); err != nil { return err } // Schedule cleanup actions handleSignals() - addExitFunc(func() { logging.Close() }) + addExitFunc(func() { + if err := logging.Close(); err != nil { + log.Debugf("Error closing log: %v", err) + } + }) addExitFunc(quitSystray) i18nInit() if showui { - err = configureSystemTray() - if err != nil { + if err := configureSystemTray(); err != nil { return err } } @@ -149,8 +153,7 @@ func doMain() error { defer finishProfiling() // Configure stats initially - err = statreporter.Configure(cfg.Stats) - if err != nil { + if err := statreporter.Configure(cfg.Stats); err != nil { return err } @@ -169,8 +172,7 @@ func i18nInit() { i18n.SetMessagesFunc(func(filename string) ([]byte, error) { return ui.Translations.Get(filename) }) - err := i18n.UseOSLocale() - if err != nil { + if err := i18n.UseOSLocale(); err != nil { log.Debugf("i18n.UseOSLocale: %q", err) } } @@ -192,17 +194,15 @@ func parseFlags() { } // Note - we can ignore the returned error because CommandLine.Parse() will // exit if it fails. - flag.CommandLine.Parse(args) + _ = flag.CommandLine.Parse(args) } // runClientProxy runs the client-side (get mode) proxy. func runClientProxy(cfg *config.Config) { - var err error - // Set Lantern as system proxy by creating and using a PAC file. setProxyAddr(cfg.Addr) - if err = setUpPacTool(); err != nil { + if err := setUpPacTool(); err != nil { exit(err) } @@ -313,7 +313,7 @@ func applyClientConfig(client *client.Client, cfg *config.Config) { log.Debugf("Proxy all traffic or not: %v", cfg.Client.ProxyAll) ServeProxyAllPacFile(cfg.Client.ProxyAll) // Note - we deliberately ignore the error from statreporter.Configure here - statreporter.Configure(cfg.Stats) + _ = statreporter.Configure(cfg.Stats) // Update client configuration and get the highest QOS dialer available. hqfd := client.Configure(cfg.Client) @@ -367,7 +367,10 @@ func runServerProxy(cfg *config.Config) { for { cfg := <-configUpdates updateServerSideConfigClient(cfg) - statreporter.Configure(cfg.Stats) + if err := statreporter.Configure(cfg.Stats); err != nil { + log.Debugf("Error configuring statreporter: %v", err) + } + srv.Configure(cfg.Server) } }() diff --git a/src/github.com/getlantern/flashlight/flashlight_test.go b/src/github.com/getlantern/flashlight/flashlight_test.go index b3c971e8b3..02cc9fb902 100644 --- a/src/github.com/getlantern/flashlight/flashlight_test.go +++ b/src/github.com/getlantern/flashlight/flashlight_test.go @@ -48,7 +48,11 @@ func TestCloudFlare(t *testing.T) { if err != nil { t.Fatalf("Unable to init mock HTTP(S) server: %s", err) } - defer mockServer.deleteCerts() + defer func() { + if err := mockServer.deleteCerts(); err != nil { + t.Fatalf("Error deleting certificates: %v", err) + } + }() mockServer.run(t) waitForServer(HTTP_ADDR, 5*time.Second, t) @@ -60,7 +64,11 @@ func TestCloudFlare(t *testing.T) { if err != nil { t.Fatalf("Unable to init mock CloudFlare: %s", err) } - defer cf.deleteCerts() + defer func() { + if err := cf.deleteCerts(); err != nil { + t.Fatalf("Error deleting certificates: %v", err) + } + }() go func() { err := cf.run(t) @@ -75,8 +83,16 @@ func TestCloudFlare(t *testing.T) { PKFile: randomTempPath(), ServerCertFile: randomTempPath(), } - defer os.Remove(certContext.PKFile) - defer os.Remove(certContext.ServerCertFile) + defer func() { + if err := os.Remove(certContext.PKFile); err != nil { + t.Fatalf("Error removing PKFile: %v", err) + } + }() + defer func() { + if err := os.Remove(certContext.ServerCertFile); err != nil { + t.Fatalf("Error removing Server Certificate: %v", err) + } + }() // Run server proxy srv := &server.Server{ @@ -111,10 +127,13 @@ func TestCloudFlare(t *testing.T) { WriteTimeout: 0, } - globals.SetTrustedCAs([]string{ + err = globals.SetTrustedCAs([]string{ "-----BEGIN CERTIFICATE-----\nMIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG\nA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv\nb3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw\nMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i\nYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT\naWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ\njc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp\nxy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp\n1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG\nsnUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ\nU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8\n9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E\nBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B\nAQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz\nyj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE\n38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP\nAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad\nDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME\nHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==\n-----END CERTIFICATE-----\n", string(cf.certContext.ServerCert.PEMEncoded()), }) + if err != nil { + log.Fatalf("Error setting trusted CAs: %v", err) + } clt.Configure(&client.ClientConfig{ MasqueradeSets: map[string][]*fronted.Masquerade{ "cloudflare": []*fronted.Masquerade{ @@ -175,7 +194,11 @@ func testRequest(testCase string, t *testing.T, requests chan *http.Request, htt if !gotCorrectError { t.Errorf("%s: Wrong error.\nExpected: %s\nGot : %s", testCase, expectedErr, err) } else if requestSuccessful { - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + t.Fatalf("Error closing response body", err) + } + }() if resp.StatusCode != expectedStatus { t.Errorf("%s: Wrong response status. Expected %d, got %d", testCase, expectedStatus, resp.StatusCode) } else { @@ -211,20 +234,23 @@ func (srv *MockServer) init() error { return nil } -func (server *MockServer) deleteCerts() { - os.Remove(server.certContext.PKFile) - os.Remove(server.certContext.ServerCertFile) +func (server *MockServer) deleteCerts() (err error) { + if err = os.Remove(server.certContext.PKFile); err != nil { + return err + } + err = os.Remove(server.certContext.ServerCertFile) + return } func (server *MockServer) run(t *testing.T) { httpServer := &http.Server{ Addr: HTTP_ADDR, - Handler: http.HandlerFunc(server.handle), + Handler: http.HandlerFunc(server.handle(t)), } httpsServer := &http.Server{ Addr: HTTPS_ADDR, - Handler: http.HandlerFunc(server.handle), + Handler: http.HandlerFunc(server.handle(t)), } go func() { @@ -244,9 +270,13 @@ func (server *MockServer) run(t *testing.T) { }() } -func (server *MockServer) handle(resp http.ResponseWriter, req *http.Request) { - resp.Write([]byte(EXPECTED_BODY)) - server.requests <- req +func (server *MockServer) handle(t *testing.T) func(http.ResponseWriter, *http.Request) { + return func(resp http.ResponseWriter, req *http.Request) { + if _, err := resp.Write([]byte(EXPECTED_BODY)); err != nil { + t.Errorf("Unable to write response body: %v", err) + } + server.requests <- req + } } // MockCloudFlare is a ReverseProxy that pretends to be CloudFlare @@ -267,9 +297,12 @@ func (cf *MockCloudFlare) init() error { return nil } -func (cf *MockCloudFlare) deleteCerts() { - os.Remove(cf.certContext.PKFile) - os.Remove(cf.certContext.ServerCertFile) +func (cf *MockCloudFlare) deleteCerts() (err error) { + if err = os.Remove(cf.certContext.PKFile); err != nil { + return err + } + err = os.Remove(cf.certContext.ServerCertFile) + return } func (cf *MockCloudFlare) run(t *testing.T) error { @@ -313,7 +346,9 @@ func waitForServer(addr string, limit time.Duration, t *testing.T) { } c, err := net.DialTimeout("tcp", addr, limit) if err == nil { - c.Close() + if err := c.Close(); err != nil { + t.Errorf("Error closing connection: %v", err) + } return } time.Sleep(10 * time.Millisecond) diff --git a/src/github.com/getlantern/flashlight/genconfig/genconfig.go b/src/github.com/getlantern/flashlight/genconfig/genconfig.go index 00ab697d01..2b1928dcd8 100644 --- a/src/github.com/getlantern/flashlight/genconfig/genconfig.go +++ b/src/github.com/getlantern/flashlight/genconfig/genconfig.go @@ -170,7 +170,11 @@ func loadFtVersion() { log.Fatalf("Error fetching FireTweet version file: %s", err) } - defer res.Body.Close() + defer func() { + if err := res.Body.Close(); err != nil { + log.Debugf("Error closing response body: %v", err) + } + }() body, err := ioutil.ReadAll(res.Body) if err != nil { log.Fatalf("Could not read FT version file: %s", err) @@ -250,7 +254,9 @@ func grabCerts() { log.Errorf("Unable to dial domain %s: %s", domain, err) continue } - cwt.Conn.Close() + if err := cwt.Conn.Close(); err != nil { + log.Debugf("Error closing connection: %v", err) + } chain := cwt.VerifiedChains[0] rootCA := chain[len(chain)-1] rootCert, err := keyman.LoadCertificateFromX509(rootCA) @@ -343,7 +349,9 @@ func buildModel(cas map[string]*castat, masquerades []*masquerade) map[string]in log.Debugf("Skipping fallback %v because dialing Google failed: %v", addr, err) continue } - conn.Close() + if err := conn.Close(); err != nil { + log.Debugf("Error closing connection: %v", err) + } // Use this fallback fbs = append(fbs, fb) @@ -368,7 +376,11 @@ func generateTemplate(model map[string]interface{}, tmplString string, filename log.Errorf("Unable to create %s: %s", filename, err) return } - defer out.Close() + defer func() { + if err := out.Close(); err != nil { + log.Debugf("Error closing file: %v", err) + } + }() err = tmpl.Execute(out, model) if err != nil { log.Errorf("Unable to generate %s: %s", filename, err) diff --git a/src/github.com/getlantern/flashlight/logging/logging.go b/src/github.com/getlantern/flashlight/logging/logging.go index af5492ce2e..e447c4dd5c 100644 --- a/src/github.com/getlantern/flashlight/logging/logging.go +++ b/src/github.com/getlantern/flashlight/logging/logging.go @@ -8,6 +8,7 @@ import ( "path/filepath" "runtime" "strings" + "sync" "time" "github.com/getlantern/appdir" @@ -37,7 +38,9 @@ var ( errorOut io.Writer debugOut io.Writer - lastAddr string + lastAddr string + duplicates = make(map[string]bool) + dupLock sync.Mutex ) func Init() error { @@ -59,6 +62,18 @@ func Init() error { // Loggly has its own timestamp so don't bother adding it in message, // moreover, golog always write each line in whole, so we need not to care about line breaks. + + // timestamped adds a timestamp to the beginning of log lines + timestamped := func(orig io.Writer) io.Writer { + return wfilter.SimplePrepender(orig, func(w io.Writer) (int, error) { + ts := time.Now() + runningSecs := ts.Sub(processStart).Seconds() + secs := int(math.Mod(runningSecs, 60)) + mins := int(runningSecs / 60) + return fmt.Fprintf(w, "%s - %dm%ds ", ts.In(time.UTC).Format(logTimestampFormat), mins, secs) + }) + } + errorOut = timestamped(NonStopWriter(os.Stderr, logFile)) debugOut = timestamped(NonStopWriter(os.Stdout, logFile)) golog.SetOutputs(errorOut, debugOut) @@ -101,17 +116,6 @@ func Close() error { return logFile.Close() } -// timestamped adds a timestamp to the beginning of log lines -func timestamped(orig io.Writer) io.Writer { - return wfilter.LinePrepender(orig, func(w io.Writer) (int, error) { - ts := time.Now() - runningSecs := ts.Sub(processStart).Seconds() - secs := int(math.Mod(runningSecs, 60)) - mins := int(runningSecs / 60) - return fmt.Fprintf(w, "%s - %dm%ds ", ts.In(time.UTC).Format(logTimestampFormat), mins, secs) - }) -} - func enableLoggly(addr string, cloudConfigCA string, instanceId string, version string, revisionDate string) { if addr == "" { @@ -161,7 +165,29 @@ type logglyErrorWriter struct { client *loggly.Client } +func isDuplicate(msg string) bool { + dupLock.Lock() + defer dupLock.Unlock() + + if duplicates[msg] { + return true + } + + // Implement a crude cap on the size of the map + if len(duplicates) < 1000 { + duplicates[msg] = true + } + + return false +} + func (w logglyErrorWriter) Write(b []byte) (int, error) { + fullMessage := string(b) + if isDuplicate(fullMessage) { + log.Debugf("Not logging duplicate: %v", fullMessage) + return 0, nil + } + extra := map[string]string{ "logLevel": "ERROR", "osName": runtime.GOOS, @@ -172,7 +198,6 @@ func (w logglyErrorWriter) Write(b []byte) (int, error) { "timeZone": w.tz, "version": w.versionToLoggly, } - fullMessage := string(b) // extract last 2 (at most) chunks of fullMessage to message, without prefix, // so we can group logs with same reason in Loggly @@ -234,7 +259,9 @@ func NonStopWriter(writers ...io.Writer) io.Writer { // It never fails and always return the length of bytes passed in func (t *nonStopWriter) Write(p []byte) (int, error) { for _, w := range t.writers { - w.Write(p) + if n, err := w.Write(p); err != nil { + return n, err + } } return len(p), nil } diff --git a/src/github.com/getlantern/flashlight/pac.go b/src/github.com/getlantern/flashlight/pac.go index a1ede5f79a..4b95651e6b 100644 --- a/src/github.com/getlantern/flashlight/pac.go +++ b/src/github.com/getlantern/flashlight/pac.go @@ -107,7 +107,6 @@ func genPACFile() { // watchDirectAddrs adds any site that has accessed directly without error to PAC file func watchDirectAddrs() { - detour.DirectAddrCh = make(chan string) go func() { for { addr := <-detour.DirectAddrCh @@ -136,7 +135,9 @@ func pacOn() { resp.Header().Set("Content-Type", "application/x-ns-proxy-autoconfig") resp.WriteHeader(http.StatusOK) muPACFile.RLock() - resp.Write(pacFile) + if _, err := resp.Write(pacFile); err != nil { + log.Debugf("Error writing response: %v", err) + } muPACFile.RUnlock() } genPACFile() diff --git a/src/github.com/getlantern/flashlight/packaged/packaged.go b/src/github.com/getlantern/flashlight/packaged/packaged.go index 775e177f00..075f4c7fde 100644 --- a/src/github.com/getlantern/flashlight/packaged/packaged.go +++ b/src/github.com/getlantern/flashlight/packaged/packaged.go @@ -78,7 +78,8 @@ func readSettingsFromFile(yamlPath string) (string, *PackagedSettings, error) { log.Debugf("Opening file at: %v", yamlPath) data, err := ioutil.ReadFile(yamlPath) if err != nil { - log.Errorf("Error reading file %v", err) + // This will happen whenever there's no packaged settings, which is often + log.Debugf("Error reading file %v", err) return "", &PackagedSettings{}, err } diff --git a/src/github.com/getlantern/flashlight/proxiedsites/proxiedsites.go b/src/github.com/getlantern/flashlight/proxiedsites/proxiedsites.go index 84a07b1aa9..e4c2d1a031 100644 --- a/src/github.com/getlantern/flashlight/proxiedsites/proxiedsites.go +++ b/src/github.com/getlantern/flashlight/proxiedsites/proxiedsites.go @@ -94,10 +94,13 @@ func start() (err error) { func read() { for msg := range service.In { - config.Update(func(updated *config.Config) error { + err := config.Update(func(updated *config.Config) error { log.Debugf("Applying update from UI") updated.ProxiedSites.Delta.Merge(msg.(*proxiedsites.Delta)) return nil }) + if err != nil { + log.Debugf("Error applying update from UI: %v", err) + } } } diff --git a/src/github.com/getlantern/flashlight/pubsub/pubsub_test.go b/src/github.com/getlantern/flashlight/pubsub/pubsub_test.go index 3496c9ae5b..4d66dae5be 100644 --- a/src/github.com/getlantern/flashlight/pubsub/pubsub_test.go +++ b/src/github.com/getlantern/flashlight/pubsub/pubsub_test.go @@ -19,9 +19,13 @@ func TestSub(t *testing.T) { func TestPublish(t *testing.T) { msgs := make(chan string) - Sub(Location, func(s string) { + err := Sub(Location, func(s string) { msgs <- s }) + if err != nil { + t.Fatalf("Unable to subscribe: %v", err) + } + Pub(Location, "test") msg := <-msgs diff --git a/src/github.com/getlantern/flashlight/server/server.go b/src/github.com/getlantern/flashlight/server/server.go index 3f31c032e4..8e483bbe8a 100644 --- a/src/github.com/getlantern/flashlight/server/server.go +++ b/src/github.com/getlantern/flashlight/server/server.go @@ -234,7 +234,9 @@ func (server *Server) register(updateConfig func(func(*ServerConfig) error)) { } } if err == nil { - resp.Body.Close() + if err := resp.Body.Close(); err != nil { + log.Debugf("Error closing response body: %v", err) + } } } } @@ -327,10 +329,10 @@ func mapPort(addr string, port int) error { if err != nil { return fmt.Errorf("Unable to get IGD: %s", err) } - - igd.RemovePortMapping(igdman.TCP, port) - err = igd.AddPortMapping(igdman.TCP, internalIP, internalPort, port, 0) - if err != nil { + if err := igd.RemovePortMapping(igdman.TCP, port); err != nil { + log.Debugf("Unable to remove port mapping: %v", err) + } + if err = igd.AddPortMapping(igdman.TCP, internalIP, internalPort, port, 0); err != nil { return fmt.Errorf("Unable to map port with igdman %d: %s", port, err) } @@ -343,8 +345,7 @@ func unmapPort(port int) error { return fmt.Errorf("Unable to get IGD: %s", err) } - igd.RemovePortMapping(igdman.TCP, port) - if err != nil { + if err := igd.RemovePortMapping(igdman.TCP, port); err != nil { return fmt.Errorf("Unable to unmap port with igdman %d: %s", port, err) } @@ -360,7 +361,11 @@ func determineInternalIP() (string, error) { if err != nil { return "", fmt.Errorf("Unable to determine local IP: %s", err) } - defer conn.Close() + defer func() { + if err := conn.Close(); err != nil { + log.Debugf("Error closing connection: %v", err) + } + }() host, _, err := net.SplitHostPort(conn.LocalAddr().String()) return host, err } diff --git a/src/github.com/getlantern/flashlight/settings/settings.go b/src/github.com/getlantern/flashlight/settings/settings.go index e80f1d44b0..247193bcc0 100644 --- a/src/github.com/getlantern/flashlight/settings/settings.go +++ b/src/github.com/getlantern/flashlight/settings/settings.go @@ -89,12 +89,14 @@ func read() { for msg := range service.In { log.Tracef("Read settings message!! %q", msg) settings := (msg).(map[string]interface{}) - config.Update(func(updated *config.Config) error { + err := config.Update(func(updated *config.Config) error { if autoReport, ok := settings["autoReport"].(bool); ok { // turn on/off analaytics reporting if autoReport { - analytics.StartService() + if err := analytics.StartService(); err != nil { + log.Debugf("Unable to start analytics service: %v", err) + } } else { analytics.StopService() } @@ -110,5 +112,8 @@ func read() { } return nil }) + if err != nil { + log.Errorf("Unable to update settings: %v", err) + } } } diff --git a/src/github.com/getlantern/flashlight/statreporter/statreporter.go b/src/github.com/getlantern/flashlight/statreporter/statreporter.go index df5ae4e8e7..428a39bd53 100644 --- a/src/github.com/getlantern/flashlight/statreporter/statreporter.go +++ b/src/github.com/getlantern/flashlight/statreporter/statreporter.go @@ -241,7 +241,12 @@ func posterForDimGroupStats(cfg *Config) reportPoster { if err != nil { return fmt.Errorf("Unable to post stats to statshub: %s", err) } - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + // Log instead of returning, so we can keep the other, more probable, errors + log.Debugf("Unable to close response body: %s", err) + } + }() jsonString := string(jsonBytes) if resp.StatusCode != 200 { diff --git a/src/github.com/getlantern/flashlight/statreporter/statreporter_test.go b/src/github.com/getlantern/flashlight/statreporter/statreporter_test.go index 920909e88a..8453548465 100644 --- a/src/github.com/getlantern/flashlight/statreporter/statreporter_test.go +++ b/src/github.com/getlantern/flashlight/statreporter/statreporter_test.go @@ -47,7 +47,7 @@ func TestAll(t *testing.T) { originalReporter := currentReporter // Reconfigure reporting - doConfigure(&Config{ + err = doConfigure(&Config{ ReportingPeriod: 200 * time.Millisecond, }, func(r report) error { go func() { @@ -55,6 +55,9 @@ func TestAll(t *testing.T) { }() return nil }) + if err != nil { + t.Fatalf("Unable to reconfigure reporting: %v", err) + } // Get the first report report1 := <-reportCh diff --git a/src/github.com/getlantern/flashlight/ui/ui.go b/src/github.com/getlantern/flashlight/ui/ui.go index e97e3c232c..384019107e 100644 --- a/src/github.com/getlantern/flashlight/ui/ui.go +++ b/src/github.com/getlantern/flashlight/ui/ui.go @@ -130,7 +130,8 @@ func openExternalUrl() { path, s, err := packaged.ReadSettings() if err != nil { - log.Errorf("Could not read yaml from %v: %v", path, err) + // Let packaged itself log errors as necessary. + log.Debugf("Could not read yaml from %v: %v", path, err) return } diff --git a/src/github.com/getlantern/fronted/fronted_test.go b/src/github.com/getlantern/fronted/fronted_test.go index 00fe2a0db9..affdcd8c04 100644 --- a/src/github.com/getlantern/fronted/fronted_test.go +++ b/src/github.com/getlantern/fronted/fronted_test.go @@ -95,7 +95,11 @@ func TestNonGlobalAddressNoHost(t *testing.T) { func doTestNonGlobalAddress(t *testing.T, overrideAddr string) { l := startServer(t, false, nil) d := dialerFor(t, l, 0) - defer d.Close() + defer func() { + if err := d.Close(); err != nil { + t.Fatalf("Unable to close dialer: %v", err) + } + }() gotConn := false var gotConnMutex sync.Mutex @@ -104,7 +108,9 @@ func doTestNonGlobalAddress(t *testing.T, overrideAddr string) { t.Fatalf("Unable to listen: %s", err) } go func() { - tl.Accept() + if _, err := tl.Accept(); err != nil { + t.Fatalf("Unable to accept connections: %v", err) + } gotConnMutex.Lock() gotConn = true gotConnMutex.Unlock() @@ -120,7 +126,9 @@ func doTestNonGlobalAddress(t *testing.T, overrideAddr string) { } data := []byte("Some Meaningless Data") - conn.Write(data) + if _, err := conn.Write(data); err != nil { + t.Fatalf("Unable to write to connection: %v", err) + } // Give enproxy time to flush time.Sleep(500 * time.Millisecond) _, err = conn.Write(data) @@ -136,7 +144,9 @@ func TestAllowed(t *testing.T) { t.Fatalf("Unable to listen: %s", err) } go func() { - tl.Accept() + if _, err := tl.Accept(); err != nil { + t.Fatalf("Unable to accept connections: %v", err) + } gotConnMutex.Lock() gotConn = true gotConnMutex.Unlock() @@ -154,14 +164,24 @@ func TestAllowed(t *testing.T) { // Only allow some port other than the actual port l := startServer(t, true, []int{port + 1}) d := dialerFor(t, l, 0) - defer d.Close() + defer func() { + if err := d.Close(); err != nil { + t.Fatalf("Unable to close dialer: %v", err) + } + }() addr := tl.Addr().String() conn, err := d.Dial("tcp", addr) - defer conn.Close() + defer func() { + if err := conn.Close(); err != nil { + t.Fatalf("Unable to close connection: %v", err) + } + }() data := []byte("Some Meaningless Data") - conn.Write(data) + if _, err := conn.Write(data); err != nil { + t.Fatalf("Unable to write connection: %v", err) + } // Give enproxy time to flush time.Sleep(500 * time.Millisecond) _, err = conn.Write(data) @@ -172,7 +192,11 @@ func TestAllowed(t *testing.T) { func TestRoundTripPooled(t *testing.T) { l := startServer(t, true, nil) d := dialerFor(t, l, 20) - defer d.Close() + defer func() { + if err := d.Close(); err != nil { + t.Fatalf("Unable to close dialer: %v", err) + } + }() proxy.Test(t, d) } @@ -180,7 +204,11 @@ func TestRoundTripPooled(t *testing.T) { func TestRoundTripUnpooled(t *testing.T) { l := startServer(t, true, nil) d := dialerFor(t, l, 0) - defer d.Close() + defer func() { + if err := d.Close(); err != nil { + t.Fatalf("Unable to close dialer: %v", err) + } + }() proxy.Test(t, d) } @@ -208,7 +236,11 @@ func TestIntegration(t *testing.T) { } d := integrationDialer(t, statsFunc) - defer d.Close() + defer func() { + if err := d.Close(); err != nil { + t.Fatalf("Unable to close dialer: %v", err) + } + }() hc := &http.Client{ Transport: &http.Transport{ @@ -220,7 +252,11 @@ func TestIntegration(t *testing.T) { if err != nil { t.Fatalf("Unable to fetch from Google: %s", err) } - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + t.Fatalf("Unable to close response body: %v", err) + } + }() b, err := ioutil.ReadAll(resp.Body) if err != nil { t.Fatalf("Unable to read response from Google: %s", err) @@ -238,12 +274,20 @@ func TestIntegration(t *testing.T) { func TestIntegrationDirect(t *testing.T) { d := integrationDialer(t, nil) - defer d.Close() + defer func() { + if err := d.Close(); err != nil { + t.Fatalf("Unable to close dialer: %v", err) + } + }() client := d.NewDirectDomainFronter() resp, err := client.Get("http://geo.getiantem.org/lookup") if assert.NoError(t, err, "Should be able to call geo.getiantem.org") { - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + t.Fatalf("Unable to close response body: %v", err) + } + }() if assert.Equal(t, 200, resp.StatusCode, "Response should be successful") { reflectedIp := resp.Header.Get("X-Reflected-Ip") assert.NotEmpty(t, reflectedIp, "Response from geo.getiantem.org should contains a reflected ip") diff --git a/src/github.com/getlantern/fronted/masquerade.go b/src/github.com/getlantern/fronted/masquerade.go index 24c471d99b..a53682fe87 100644 --- a/src/github.com/getlantern/fronted/masquerade.go +++ b/src/github.com/getlantern/fronted/masquerade.go @@ -149,7 +149,11 @@ func (vms *verifiedMasqueradeSet) doVerify(masquerade *Masquerade) bool { return } else { body, err := ioutil.ReadAll(resp.Body) - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + log.Debugf("Unable to close response body: %v", err) + } + }() if err != nil { errCh <- fmt.Errorf("HTTP Body Error: %s", body) } else { diff --git a/src/github.com/getlantern/geolookup/geolookup.go b/src/github.com/getlantern/geolookup/geolookup.go index f43903080a..c8fc427e4e 100644 --- a/src/github.com/getlantern/geolookup/geolookup.go +++ b/src/github.com/getlantern/geolookup/geolookup.go @@ -122,7 +122,11 @@ func LookupIPWithClient(ipAddr string, httpClient *http.Client) (*City, error) { if resp, err = httpClient.Do(req); err != nil { return nil, fmt.Errorf("Could not get response from server: %q", err) } - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + log.Debugf("Unable to close reponse body: %v", err) + } + }() if resp.StatusCode != http.StatusOK { body := "body unreadable" diff --git a/src/github.com/getlantern/go-loggly/loggly.go b/src/github.com/getlantern/go-loggly/loggly.go index 24367332e5..ffe87037da 100644 --- a/src/github.com/getlantern/go-loggly/loggly.go +++ b/src/github.com/getlantern/go-loggly/loggly.go @@ -118,7 +118,11 @@ func (c *Client) Send(msg Message) error { debug("buffer (%d/%d) %v", len(c.buffer), c.BufferSize, msg) if len(c.buffer) >= c.BufferSize { - go c.Flush() + go func() { + if err := c.Flush(); err != nil { + debug("Unable to flush: %v", err) + } + }() } return nil @@ -138,7 +142,11 @@ func (c *Client) Write(b []byte) (int, error) { debug("buffer (%d/%d) %q", len(c.buffer), c.BufferSize, b) if len(c.buffer) >= c.BufferSize { - go c.Flush() + go func() { + if err := c.Flush(); err != nil { + debug("Unable to flush: %v", err) + } + }() } return len(b), nil @@ -281,7 +289,11 @@ func (c *Client) Flush() error { return err } - defer res.Body.Close() + defer func() { + if err := res.Body.Close(); err != nil { + debug("Unable to close response body: %v", err) + } + }() debug("%d response", res.StatusCode) if res.StatusCode >= 400 { @@ -315,7 +327,9 @@ func (c *Client) start() { for { time.Sleep(c.FlushInterval) debug("interval %v reached", c.FlushInterval) - c.Flush() + if err := c.Flush(); err != nil { + debug("Unable to flush: %v", err) + } } } diff --git a/src/github.com/getlantern/go-update/update.go b/src/github.com/getlantern/go-update/update.go index 20c57bb4c6..93e620cd7b 100644 --- a/src/github.com/getlantern/go-update/update.go +++ b/src/github.com/getlantern/go-update/update.go @@ -281,7 +281,11 @@ func (u *Update) FromFile(path string) (err error, errRecover error) { if err != nil { return } - defer fp.Close() + defer func() { + if err := fp.Close(); err != nil { + fmt.Errorf("Unable to close file: %v\n", err) + } + }() // do the update return u.FromStream(fp) @@ -370,12 +374,18 @@ func (u *Update) FromStream(updateWith io.Reader) (err error, errRecover error) if err != nil { return } - defer fp.Close() - _, err = io.Copy(fp, bytes.NewReader(newBytes)) + // We won't log this error, because it's always going to happen. + // TODO: Document why are we doing this second fp.Close() at all? + defer func() { _ = fp.Close() }() + if _, err = io.Copy(fp, bytes.NewReader(newBytes)); err != nil { + fmt.Errorf("Unable to copy data: %v\n", err) + } // if we don't call fp.Close(), windows won't let us move the new executable // because the file will still be "in use" - fp.Close() + if err := fp.Close(); err != nil { + fmt.Errorf("Unable to close file: %v\n", err) + } // this is where we'll move the executable to so that we can swap in the updated replacement oldPath := filepath.Join(updateDir, fmt.Sprintf(".%s.old", filename)) @@ -429,7 +439,9 @@ func (u *Update) CanUpdate() (err error) { if err != nil { return } - fp.Close() + if err := fp.Close(); err != nil { + fmt.Errorf("Unable to close file: %v\n", err) + } _ = os.Remove(newPath) return @@ -441,7 +453,11 @@ func applyPatch(patch io.Reader, updatePath string) ([]byte, error) { if err != nil { return nil, err } - defer old.Close() + defer func() { + if err := old.Close(); err != nil { + fmt.Errorf("Unable to close file: %v\n", err) + } + }() // apply the patch applied := new(bytes.Buffer) @@ -471,7 +487,11 @@ func ChecksumForFile(path string) ([]byte, error) { if err != nil { return nil, err } - defer f.Close() + defer func() { + if err := f.Close(); err != nil { + fmt.Errorf("Unable to close file: %v\n", err) + } + }() return ChecksumForReader(f) } diff --git a/src/github.com/getlantern/go-update/update_test.go b/src/github.com/getlantern/go-update/update_test.go index b40b398a64..e8a33946d9 100644 --- a/src/github.com/getlantern/go-update/update_test.go +++ b/src/github.com/getlantern/go-update/update_test.go @@ -21,8 +21,10 @@ var ( newFile = []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06} ) -func cleanup(path string) { - os.Remove(path) +func cleanup(path string, t *testing.T) { + if err := os.Remove(path); err != nil { + t.Fatalf("Unable to remove file: %v", err) + } } // we write with a separate name for each test so that we can run them in parallel @@ -51,7 +53,7 @@ func TestFromStream(t *testing.T) { t.Parallel() fName := "TestFromStream" - defer cleanup(fName) + defer cleanup(fName, t) writeOldFile(fName, t) err, _ := New().Target(fName).FromStream(bytes.NewReader(newFile)) @@ -63,8 +65,8 @@ func TestFromFile(t *testing.T) { fName := "TestFromFile" newFName := "NewTestFromFile" - defer cleanup(fName) - defer cleanup(newFName) + defer cleanup(fName, t) + defer cleanup(newFName, t) writeOldFile(fName, t) if err := ioutil.WriteFile(newFName, newFile, 0777); err != nil { @@ -79,7 +81,7 @@ func TestFromUrl(t *testing.T) { t.Parallel() fName := "TestFromUrl" - defer cleanup(fName) + defer cleanup(fName, t) writeOldFile(fName, t) l, err := net.Listen("tcp", ":0") @@ -88,9 +90,18 @@ func TestFromUrl(t *testing.T) { } addr := l.Addr().String() - go http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(newFile) - })) + go func() { + err := http.Serve(l, http.HandlerFunc( + func(w http.ResponseWriter, + r *http.Request) { + if _, err := w.Write(newFile); err != nil { + t.Fatalf("Unable to write to file: %v", err) + } + })) + if err != nil { + t.Fatalf("Error setting up HTTP server: %v", err) + } + }() err, _ = New().Target(fName).FromUrl("http://" + addr) validateUpdate(fName, err, t) @@ -100,7 +111,7 @@ func TestVerifyChecksum(t *testing.T) { t.Parallel() fName := "TestVerifyChecksum" - defer cleanup(fName) + defer cleanup(fName, t) writeOldFile(fName, t) checksum, err := ChecksumForBytes(newFile) @@ -116,7 +127,7 @@ func TestVerifyChecksumNegative(t *testing.T) { t.Parallel() fName := "TestVerifyChecksumNegative" - defer cleanup(fName) + defer cleanup(fName, t) writeOldFile(fName, t) badChecksum := []byte{0x0A, 0x0B, 0x0C, 0xFF} @@ -130,7 +141,7 @@ func TestApplyPatch(t *testing.T) { t.Parallel() fName := "TestApplyPatch" - defer cleanup(fName) + defer cleanup(fName, t) writeOldFile(fName, t) patch := new(bytes.Buffer) @@ -148,7 +159,7 @@ func TestCorruptPatch(t *testing.T) { t.Parallel() fName := "TestCorruptPatch" - defer cleanup(fName) + defer cleanup(fName, t) writeOldFile(fName, t) badPatch := []byte{0x44, 0x38, 0x86, 0x3c, 0x4f, 0x8d, 0x26, 0x54, 0xb, 0x11, 0xce, 0xfe, 0xc1, 0xc0, 0xf8, 0x31, 0x38, 0xa0, 0x12, 0x1a, 0xa2, 0x57, 0x2a, 0xe1, 0x3a, 0x48, 0x62, 0x40, 0x2b, 0x81, 0x12, 0xb1, 0x21, 0xa5, 0x16, 0xed, 0x73, 0xd6, 0x54, 0x84, 0x29, 0xa6, 0xd6, 0xb2, 0x1b, 0xfb, 0xe6, 0xbe, 0x7b, 0x70} @@ -163,7 +174,7 @@ func TestVerifyChecksumPatchNegative(t *testing.T) { t.Parallel() fName := "TestVerifyChecksumPatchNegative" - defer cleanup(fName) + defer cleanup(fName, t) writeOldFile(fName, t) checksum, err := ChecksumForBytes(newFile) @@ -251,7 +262,7 @@ func TestVerifySignature(t *testing.T) { t.Parallel() fName := "TestVerifySignature" - defer cleanup(fName) + defer cleanup(fName, t) writeOldFile(fName, t) up, err := New().Target(fName).VerifySignatureWithPEM([]byte(publicKey)) @@ -268,7 +279,7 @@ func TestVerifyFailBadSignature(t *testing.T) { t.Parallel() fName := "TestVerifyFailBadSignature" - defer cleanup(fName) + defer cleanup(fName, t) writeOldFile(fName, t) up, err := New().Target(fName).VerifySignatureWithPEM([]byte(publicKey)) @@ -287,7 +298,7 @@ func TestVerifyFailNoSignature(t *testing.T) { t.Parallel() fName := "TestVerifySignatureWithPEM" - defer cleanup(fName) + defer cleanup(fName, t) writeOldFile(fName, t) up, err := New().Target(fName).VerifySignatureWithPEM([]byte(publicKey)) @@ -333,7 +344,7 @@ func TestVerifyFailWrongSignature(t *testing.T) { t.Parallel() fName := "TestVerifyFailWrongSignature" - defer cleanup(fName) + defer cleanup(fName, t) writeOldFile(fName, t) up, err := New().Target(fName).VerifySignatureWithPEM([]byte(publicKey)) @@ -352,7 +363,7 @@ func TestSignatureButNoPublicKey(t *testing.T) { t.Parallel() fName := "TestSignatureButNoPublicKey" - defer cleanup(fName) + defer cleanup(fName, t) writeOldFile(fName, t) sig := sign(privateKey, newFile, t) @@ -366,7 +377,7 @@ func TestPublicKeyButNoSignature(t *testing.T) { t.Parallel() fName := "TestPublicKeyButNoSignature" - defer cleanup(fName) + defer cleanup(fName, t) writeOldFile(fName, t) up, err := New().Target(fName).VerifySignatureWithPEM([]byte(publicKey)) diff --git a/src/github.com/getlantern/golog/golog.go b/src/github.com/getlantern/golog/golog.go index 0564d57b61..78e50b6e1a 100644 --- a/src/github.com/getlantern/golog/golog.go +++ b/src/github.com/getlantern/golog/golog.go @@ -207,8 +207,16 @@ func (l *logger) newTraceWriter() io.Writer { return pw } go func() { - defer pr.Close() - defer pw.Close() + defer func() { + if err := pr.Close(); err != nil { + errorOnLogging(err) + } + }() + defer func() { + if err := pw.Close(); err != nil { + errorOnLogging(err) + } + }() for { line, err := br.ReadString('\n') @@ -259,7 +267,9 @@ func (l *logger) doPrintStack() { file, line := funcForPc.FileLine(pc) fmt.Fprintf(buf, "\t%s\t%s: %d\n", name, file, line) } - buf.WriteTo(os.Stderr) + if _, err := buf.WriteTo(os.Stderr); err != nil { + errorOnLogging(err) + } } func errorOnLogging(err error) { diff --git a/src/github.com/getlantern/golog/golog_test.go b/src/github.com/getlantern/golog/golog_test.go index fa711851dc..a23018e4d5 100644 --- a/src/github.com/getlantern/golog/golog_test.go +++ b/src/github.com/getlantern/golog/golog_test.go @@ -52,7 +52,11 @@ func TestTraceEnabled(t *testing.T) { if err != nil { t.Fatalf("Unable to set trace to true") } - defer os.Setenv("TRACE", originalTrace) + defer func() { + if err := os.Setenv("TRACE", originalTrace); err != nil { + t.Fatalf("Unable to set TRACE environment variable: %v", err) + } + }() out := bytes.NewBuffer(nil) SetOutputs(ioutil.Discard, out) @@ -60,8 +64,12 @@ func TestTraceEnabled(t *testing.T) { l.Trace("Hello world") l.Tracef("Hello %d", 5) tw := l.TraceOut() - tw.Write([]byte("Gravy\n")) - tw.(io.Closer).Close() + if _, err := tw.Write([]byte("Gravy\n")); err != nil { + t.Fatalf("Unable to write: %v", err) + } + if err := tw.(io.Closer).Close(); err != nil { + t.Fatalf("Unable to close: %v", err) + } // Give trace writer a moment to catch up time.Sleep(50 * time.Millisecond) @@ -74,14 +82,20 @@ func TestTraceDisabled(t *testing.T) { if err != nil { t.Fatalf("Unable to set trace to false") } - defer os.Setenv("TRACE", originalTrace) + defer func() { + if err := os.Setenv("TRACE", originalTrace); err != nil { + t.Fatalf("Unable to set TRACE environment variable: %v", err) + } + }() out := bytes.NewBuffer(nil) SetOutputs(ioutil.Discard, out) l := LoggerFor("myprefix") l.Trace("Hello world") l.Tracef("Hello %d", 5) - l.TraceOut().Write([]byte("Gravy\n")) + if _, err := l.TraceOut().Write([]byte("Gravy\n")); err != nil { + t.Fatalf("Unable to write: %v", err) + } // Give trace writer a moment to catch up time.Sleep(50 * time.Millisecond) diff --git a/src/github.com/getlantern/i18n/translate.go b/src/github.com/getlantern/i18n/translate.go index afe4173f07..5fd10f7951 100644 --- a/src/github.com/getlantern/i18n/translate.go +++ b/src/github.com/getlantern/i18n/translate.go @@ -65,7 +65,11 @@ func makeReadFunc(d string) ReadFunc { err = fmt.Errorf("Error open file %s: %s", fileName, err) return } - defer f.Close() + defer func() { + if err := f.Close(); err != nil { + log.Debugf("Unable to close file: %v", err) + } + }() if buf, err = ioutil.ReadAll(f); err != nil { err = fmt.Errorf("Error read file %s: %s", fileName, err) } diff --git a/src/github.com/getlantern/i18n/translate_test.go b/src/github.com/getlantern/i18n/translate_test.go index febda50564..91b2296b70 100644 --- a/src/github.com/getlantern/i18n/translate_test.go +++ b/src/github.com/getlantern/i18n/translate_test.go @@ -9,7 +9,9 @@ import ( func TestTranslate(t *testing.T) { assertTranslation(t, "", "HELLO") - UseOSLocale() + if err := UseOSLocale(); err != nil { + log.Debugf("Unable to detect and use OS locale: %v", err) + } assertTranslation(t, "I speak America English!", "ONLY_IN_EN_US") assertTranslation(t, "I speak Generic English!", "ONLY_IN_EN") assertTranslation(t, "", "NOT_EXISTED") @@ -59,7 +61,9 @@ func TestReadFromMemory(t *testing.T) { func TestGoroutine(t *testing.T) { SetMessagesDir("locale") - SetLocale("en_US") + if err := SetLocale("en_US"); err != nil { + log.Debugf("Unable to set en_US locale: %v", err) + } var wg sync.WaitGroup wg.Add(2) go func() { diff --git a/src/github.com/getlantern/idletiming/idletiming_conn.go b/src/github.com/getlantern/idletiming/idletiming_conn.go index 12c7427839..1c11b9e66e 100644 --- a/src/github.com/getlantern/idletiming/idletiming_conn.go +++ b/src/github.com/getlantern/idletiming/idletiming_conn.go @@ -6,10 +6,13 @@ import ( "net" "sync/atomic" "time" + + "github.com/getlantern/golog" ) var ( epoch = time.Unix(0, 0) + log = golog.LoggerFor("idletiming") ) // Conn creates a new net.Conn wrapping the given net.Conn that times out after @@ -95,14 +98,18 @@ func (c *IdleTimingConn) Read(b []byte) (int, error) { maxDeadline := time.Now().Add(c.halfIdleTimeout) if readDeadline != epoch && !maxDeadline.Before(readDeadline) { // Caller's deadline is before ours, use it - c.conn.SetReadDeadline(readDeadline) + if err := c.conn.SetReadDeadline(readDeadline); err != nil { + log.Errorf("Unable to set read deadline: %v", err) + } n, err := c.conn.Read(b) c.markActive(n) totalN = totalN + n return totalN, err } else { // Use our own deadline - c.conn.SetReadDeadline(maxDeadline) + if err := c.conn.SetReadDeadline(maxDeadline); err != nil { + log.Errorf("Unable to set read deadline: %v", err) + } n, err := c.conn.Read(b) c.markActive(n) totalN = totalN + n @@ -132,14 +139,18 @@ func (c *IdleTimingConn) Write(b []byte) (int, error) { maxDeadline := time.Now().Add(c.halfIdleTimeout) if writeDeadline != epoch && !maxDeadline.Before(writeDeadline) { // Caller's deadline is before ours, use it - c.conn.SetWriteDeadline(writeDeadline) + if err := c.conn.SetWriteDeadline(writeDeadline); err != nil { + log.Errorf("Unable to set write deadline: %v", err) + } n, err := c.conn.Write(b) c.markActive(n) totalN = totalN + n return totalN, err } else { // Use our own deadline - c.conn.SetWriteDeadline(maxDeadline) + if err := c.conn.SetWriteDeadline(maxDeadline); err != nil { + log.Errorf("Unable to set write deadline: %v", err) + } n, err := c.conn.Write(b) c.markActive(n) totalN = totalN + n @@ -178,8 +189,12 @@ func (c *IdleTimingConn) RemoteAddr() net.Addr { } func (c *IdleTimingConn) SetDeadline(t time.Time) error { - c.SetReadDeadline(t) - c.SetWriteDeadline(t) + if err := c.SetReadDeadline(t); err != nil { + log.Errorf("Unable to set read deadline: %v", err) + } + if err := c.SetWriteDeadline(t); err != nil { + log.Errorf("Unable to set write deadline: %v", err) + } return nil } diff --git a/src/github.com/getlantern/idletiming/idletiming_example_test.go b/src/github.com/getlantern/idletiming/idletiming_example_test.go index 13126df99c..4fbc0d3c0c 100644 --- a/src/github.com/getlantern/idletiming/idletiming_example_test.go +++ b/src/github.com/getlantern/idletiming/idletiming_example_test.go @@ -1,7 +1,6 @@ package idletiming import ( - "log" "net" "time" ) @@ -13,10 +12,12 @@ func ExampleConn() { } ic := Conn(c, 5*time.Second, func() { - log.Printf("Connection was idled") + log.Debugf("Connection was idled") }) - ic.Write([]byte("My data")) + if _, err := ic.Write([]byte("My data")); err != nil { + log.Fatalf("Unable to write to connection: %v", err) + } } func ExampleListener() { @@ -26,8 +27,10 @@ func ExampleListener() { } il := Listener(l, 5*time.Second, func(conn net.Conn) { - log.Printf("Connection was idled") + log.Debugf("Connection was idled") }) - il.Accept() + if _, err := il.Accept(); err != nil { + log.Fatalf("Unable to accept connections: %v", err) + } } diff --git a/src/github.com/getlantern/idletiming/idletiming_test.go b/src/github.com/getlantern/idletiming/idletiming_test.go index d00007af8c..71d213dbf8 100644 --- a/src/github.com/getlantern/idletiming/idletiming_test.go +++ b/src/github.com/getlantern/idletiming/idletiming_test.go @@ -39,10 +39,14 @@ func TestWrite(t *testing.T) { addr := l.Addr().String() il := Listener(l, serverTimeout, func(conn net.Conn) { atomic.StoreInt32(&listenerIdled, 1) - conn.Close() + if err := conn.Close(); err != nil { + t.Errorf("Unable to close connection: %v", err) + } }) defer func() { - il.Close() + if err := il.Close(); err != nil { + t.Errorf("Unable to close listener: %v", err) + } time.Sleep(1 * time.Second) err = fdc.AssertDelta(0) if err != nil { @@ -57,7 +61,9 @@ func TestWrite(t *testing.T) { } go func() { // Discard data - io.Copy(ioutil.Discard, conn) + if _, err := io.Copy(ioutil.Discard, conn); err != nil { + t.Fatalf("Unable to discard data: %v", err) + } }() }() @@ -70,7 +76,9 @@ func TestWrite(t *testing.T) { c := Conn(conn, clientTimeout, func() { atomic.StoreInt32(&connIdled, 1) - conn.Close() + if err := conn.Close(); err != nil { + t.Fatalf("Unable to close connection: %v", err) + } }) // Write messages @@ -82,7 +90,9 @@ func TestWrite(t *testing.T) { } // Now write msg with a really short deadline - c.SetWriteDeadline(time.Now().Add(1 * time.Nanosecond)) + if err := c.SetWriteDeadline(time.Now().Add(1 * time.Nanosecond)); err != nil { + t.Fatalf("Unable to set write deadline: %v", err) + } _, err = c.Write(msg) if netErr, ok := err.(net.Error); ok { if !netErr.Timeout() { @@ -124,10 +134,14 @@ func TestRead(t *testing.T) { il := Listener(l, serverTimeout, func(conn net.Conn) { atomic.StoreInt32(&listenerIdled, 1) - conn.Close() + if err := conn.Close(); err != nil { + t.Fatalf("Unable to close connection: %v", err) + } }) defer func() { - il.Close() + if err := il.Close(); err != nil { + t.Fatalf("Unable to close listener: %v", err) + } time.Sleep(1 * time.Second) err = fdc.AssertDelta(0) if err != nil { @@ -162,7 +176,9 @@ func TestRead(t *testing.T) { c := Conn(conn, clientTimeout, func() { atomic.StoreInt32(&connIdled, 1) - conn.Close() + if err := conn.Close(); err != nil { + t.Fatalf("Unable to close connection: %v", err) + } }) // Read messages (we use a buffer matching the message size to make sure @@ -182,7 +198,9 @@ func TestRead(t *testing.T) { } // Now read with a really short deadline - c.SetReadDeadline(time.Now().Add(1 * time.Nanosecond)) + if err := c.SetReadDeadline(time.Now().Add(1 * time.Nanosecond)); err != nil { + t.Fatalf("Unable to set read deadline: %v", err) + } _, err = c.Read(msg) if netErr, ok := err.(net.Error); ok { if !netErr.Timeout() { @@ -214,7 +232,9 @@ func TestClose(t *testing.T) { t.Fatalf("Unable to listen: %s", err) } defer func() { - l.Close() + if err := l.Close(); err != nil { + t.Fatalf("Unable to close listener: %v", err) + } time.Sleep(1 * time.Second) err = fdc.AssertDelta(0) if err != nil { @@ -230,7 +250,7 @@ func TestClose(t *testing.T) { c := Conn(conn, clientTimeout, func() {}) for i := 0; i < 100; i++ { - c.Close() + _ = c.Close() } } diff --git a/src/github.com/getlantern/jibber_jabber/jibber_jabber_unix_test.go b/src/github.com/getlantern/jibber_jabber/jibber_jabber_unix_test.go index a32e538c7a..2e3563d77b 100644 --- a/src/github.com/getlantern/jibber_jabber/jibber_jabber_unix_test.go +++ b/src/github.com/getlantern/jibber_jabber/jibber_jabber_unix_test.go @@ -3,6 +3,7 @@ package jibber_jabber_test import ( + "fmt" . "github.com/pivotal-cf-experimental/jibber_jabber" "os" @@ -12,27 +13,37 @@ import ( var _ = Describe("Unix", func() { AfterEach(func() { - os.Setenv("LC_ALL", "") - os.Setenv("LANG", "en_US.UTF-8") + if err := os.Setenv("LC_ALL", ""); err != nil { + fmt.Errorf("Unable to set environment variable: %v", err) + } + if err := os.Setenv("LANG", "en_US.UTF-8"); err != nil { + fmt.Errorf("Unable to set environment variable: %v", err) + } }) Describe("#DetectIETF", func() { Context("Returns IETF encoded locale", func() { It("should return the locale set to LC_ALL", func() { - os.Setenv("LC_ALL", "fr_FR.UTF-8") + if err := os.Setenv("LC_ALL", "fr_FR.UTF-8"); err != nil { + fmt.Errorf("Unable to set environment variable: %v", err) + } result, _ := DetectIETF() Ω(result).Should(Equal("fr-FR")) }) It("should return the locale set to LANG if LC_ALL isn't set", func() { - os.Setenv("LANG", "fr_FR.UTF-8") + if err := os.Setenv("LANG", "fr_FR.UTF-8"); err != nil { + fmt.Errorf("Unable to set environment variable: %v", err) + } result, _ := DetectIETF() Ω(result).Should(Equal("fr-FR")) }) It("should return an error if it cannot detect a locale", func() { - os.Setenv("LANG", "") + if err := os.Setenv("LANG", ""); err != nil { + fmt.Errorf("Unable to set environment variable: %v", err) + } _, err := DetectIETF() Ω(err.Error()).Should(Equal(COULD_NOT_DETECT_PACKAGE_ERROR_MESSAGE)) @@ -41,7 +52,9 @@ var _ = Describe("Unix", func() { Context("when the locale is simply 'fr'", func() { BeforeEach(func() { - os.Setenv("LANG", "fr") + if err := os.Setenv("LANG", "fr"); err != nil { + fmt.Errorf("Unable to set environment variable: %v", err) + } }) It("should return the locale without a territory", func() { @@ -55,20 +68,26 @@ var _ = Describe("Unix", func() { Describe("#DetectLanguage", func() { Context("Returns encoded language", func() { It("should return the language set to LC_ALL", func() { - os.Setenv("LC_ALL", "fr_FR.UTF-8") + if err := os.Setenv("LC_ALL", "fr_FR.UTF-8"); err != nil { + fmt.Errorf("Unable to set environment variable: %v", err) + } result, _ := DetectLanguage() Ω(result).Should(Equal("fr")) }) It("should return the language set to LANG if LC_ALL isn't set", func() { - os.Setenv("LANG", "fr_FR.UTF-8") + if err := os.Setenv("LANG", "fr_FR.UTF-8"); err != nil { + fmt.Errorf("Unable to set environment variable: %v", err) + } result, _ := DetectLanguage() Ω(result).Should(Equal("fr")) }) It("should return an error if it cannot detect a language", func() { - os.Setenv("LANG", "") + if err := os.Setenv("LANG", ""); err != nil { + fmt.Errorf("Unable to set environment variable: %v", err) + } _, err := DetectLanguage() Ω(err.Error()).Should(Equal(COULD_NOT_DETECT_PACKAGE_ERROR_MESSAGE)) @@ -79,20 +98,26 @@ var _ = Describe("Unix", func() { Describe("#DetectTerritory", func() { Context("Returns encoded territory", func() { It("should return the territory set to LC_ALL", func() { - os.Setenv("LC_ALL", "fr_FR.UTF-8") + if err := os.Setenv("LC_ALL", "fr_FR.UTF-8"); err != nil { + fmt.Errorf("Unable to set environment variable: %v", err) + } result, _ := DetectTerritory() Ω(result).Should(Equal("FR")) }) It("should return the territory set to LANG if LC_ALL isn't set", func() { - os.Setenv("LANG", "fr_FR.UTF-8") + if err := os.Setenv("LANG", "fr_FR.UTF-8"); err != nil { + fmt.Errorf("Unable to set environment variable: %v", err) + } result, _ := DetectTerritory() Ω(result).Should(Equal("FR")) }) It("should return an error if it cannot detect a territory", func() { - os.Setenv("LANG", "") + if err := os.Setenv("LANG", ""); err != nil { + fmt.Errorf("Unable to set environment variable: %v", err) + } _, err := DetectTerritory() Ω(err.Error()).Should(Equal(COULD_NOT_DETECT_PACKAGE_ERROR_MESSAGE)) diff --git a/src/github.com/getlantern/keyman/keyman.go b/src/github.com/getlantern/keyman/keyman.go index b690e63426..87c07975ef 100644 --- a/src/github.com/getlantern/keyman/keyman.go +++ b/src/github.com/getlantern/keyman/keyman.go @@ -86,7 +86,9 @@ func (key *PrivateKey) WriteToFile(filename string) (err error) { if err := pem.Encode(keyOut, key.pemBlock()); err != nil { return fmt.Errorf("Unable to PEM encode private key: %s", err) } - keyOut.Close() + if err := keyOut.Close(); err != nil { + log.Debugf("Unable to close file: %v", err) + } return } @@ -234,7 +236,11 @@ func (cert *Certificate) WriteToFile(filename string) (err error) { if err != nil { return fmt.Errorf("Failed to open %s for writing: %s", filename, err) } - defer certOut.Close() + defer func() { + if err := certOut.Close(); err != nil { + log.Debugf("Unable to close file: %v", err) + } + }() return pem.Encode(certOut, cert.pemBlock()) } @@ -258,7 +264,11 @@ func (cert *Certificate) WriteToDERFile(filename string) (err error) { if err != nil { return fmt.Errorf("Failed to open %s for writing: %s", filename, err) } - defer certOut.Close() + defer func() { + if err := certOut.Close(); err != nil { + log.Debugf("Unable to close file: %v", err) + } + }() _, err = certOut.Write(cert.derBytes) return err } diff --git a/src/github.com/getlantern/keyman/keyman_test.go b/src/github.com/getlantern/keyman/keyman_test.go index 16e1d9c313..7e8743f091 100644 --- a/src/github.com/getlantern/keyman/keyman_test.go +++ b/src/github.com/getlantern/keyman/keyman_test.go @@ -18,8 +18,16 @@ const ( ) func TestRoundTrip(t *testing.T) { - defer os.Remove(PK_FILE) - defer os.Remove(CERT_FILE) + defer func() { + if err := os.Remove(PK_FILE); err != nil { + log.Debugf("Unable to remove file: %v", err) + } + }() + defer func() { + if err := os.Remove(CERT_FILE); err != nil { + log.Debugf("Unable to remove file: %v", err) + } + }() pk, err := GeneratePK(1024) assert.NoError(t, err, "Unable to generate PK") diff --git a/src/github.com/getlantern/keyman/keyman_trust_darwin.go b/src/github.com/getlantern/keyman/keyman_trust_darwin.go index a8e385160c..80fea050cf 100644 --- a/src/github.com/getlantern/keyman/keyman_trust_darwin.go +++ b/src/github.com/getlantern/keyman/keyman_trust_darwin.go @@ -15,7 +15,11 @@ const ( // root CA. func (cert *Certificate) AddAsTrustedRoot() error { tempFileName, err := cert.WriteToTempFile() - defer os.Remove(tempFileName) + defer func() { + if err := os.Remove(tempFileName); err != nil { + log.Debugf("Unable to remove file: %v", err) + } + }() if err != nil { return fmt.Errorf("Unable to create temp file: %s", err) } diff --git a/src/github.com/getlantern/lantern-mobile/Dockerfile b/src/github.com/getlantern/lantern-mobile/Dockerfile index 449f20b46b..e0026a25b3 100644 --- a/src/github.com/getlantern/lantern-mobile/Dockerfile +++ b/src/github.com/getlantern/lantern-mobile/Dockerfile @@ -33,8 +33,8 @@ RUN (curl -sSL https://golang.org/dl/go1.4.linux-amd64.tar.gz | tar -vxz -C /tmp # GOVERSION string is the output of 'git log -n 1 --format="format: devel +%h %cd" HEAD' # like in go tool dist. # Revision picked on Jul 13, 2015. -ENV GO_REV d23973d23ca19f5c2992467c01e02df1780b576c -ENV GO_VERSION devel +ebb6783 Fri Jul 17 17:58:16 2015 +0000 +ENV GO_REV go1.5rc1 +ENV GO_VERSION devel +0d20a61 Thu Aug 6 04:06:12 2015 +0000 ENV GOROOT /go ENV GOPATH / diff --git a/src/github.com/getlantern/lantern-mobile/client.go b/src/github.com/getlantern/lantern-mobile/client.go index 267b35361c..9057dd3472 100644 --- a/src/github.com/getlantern/lantern-mobile/client.go +++ b/src/github.com/getlantern/lantern-mobile/client.go @@ -68,7 +68,9 @@ func newClient(addr, appName string) *mobileClient { fronter: hqfd.NewDirectDomainFronter(), appName: appName, } - mClient.updateConfig() + if err := mClient.updateConfig(); err != nil { + log.Errorf("Unable to update config: %v", err) + } return mClient } @@ -137,7 +139,9 @@ func (client *mobileClient) pollConfiguration() { return case <-pollTimer.C: // Attempt to update configuration. - client.updateConfig() + if err := client.updateConfig(); err != nil { + log.Errorf("Unable to update config: %v", err) + } // Sleeping 'till next pull. // update timer to poll every 60 seconds diff --git a/src/github.com/getlantern/lantern-mobile/config.go b/src/github.com/getlantern/lantern-mobile/config.go index 1565d80d23..d1f74c1ca9 100644 --- a/src/github.com/getlantern/lantern-mobile/config.go +++ b/src/github.com/getlantern/lantern-mobile/config.go @@ -91,7 +91,11 @@ func pullConfigFile(cli *http.Client) ([]byte, error) { if body, err = gzip.NewReader(res.Body); err != nil { return nil, err } - defer body.Close() + defer func() { + if err := body.Close(); err != nil { + log.Debugf("Unable to close body: %v", err) + } + }() // Uncompressing bytes. return ioutil.ReadAll(body) diff --git a/src/github.com/getlantern/lantern-mobile/interface.go b/src/github.com/getlantern/lantern-mobile/interface.go index d93eeb047b..d9d9822842 100644 --- a/src/github.com/getlantern/lantern-mobile/interface.go +++ b/src/github.com/getlantern/lantern-mobile/interface.go @@ -1,19 +1,27 @@ package client -// StopClientProxy stops the proxy. -func StopClientProxy() error { - defaultClient.stop() - return nil -} - // Getfiretweetversion returns the current build version string func GetFireTweetVersion() string { return defaultClient.getFireTweetVersion() } +// GoCallback is the supertype of callbacks passed to Go +type GoCallback interface { + Do() +} + // RunClientProxy creates a new client at the given address. -func RunClientProxy(listenAddr, appName string) error { - defaultClient = newClient(listenAddr, appName) - defaultClient.serveHTTP() +func RunClientProxy(listenAddr, appName string, ready GoCallback) error { + go func() { + defaultClient = newClient(listenAddr, appName) + defaultClient.serveHTTP() + ready.Do() + }() + return nil +} + +// StopClientProxy stops the proxy. +func StopClientProxy() error { + defaultClient.stop() return nil } diff --git a/src/github.com/getlantern/lantern-mobile/interface_test.go b/src/github.com/getlantern/lantern-mobile/interface_test.go index fc9f8660ff..2db31b4e77 100644 --- a/src/github.com/getlantern/lantern-mobile/interface_test.go +++ b/src/github.com/getlantern/lantern-mobile/interface_test.go @@ -17,6 +17,10 @@ const ( const expectedBody = "Google is built by a large team of engineers, designers, researchers, robots, and others in many different sites across the globe. It is updated continuously, and built with more tools and technologies than we can shake a stick at. If you'd like to help us out, see google.com/careers.\n" +type testCb struct{} + +func (cb *testCb) Do() {} + func testReverseProxy() error { var req *http.Request @@ -70,8 +74,7 @@ func TestStartClientAndTestReverseProxy(t *testing.T) { // Let's run a proxy instance. go func() { - var err error - if err = RunClientProxy(listenProxyAddr, "TestApp"); err != nil { + if RunClientProxy(listenProxyAddr, "TestApp", new(testCb)); err != nil { t.Fatalf("RunClientProxy: %q", err) } }() @@ -91,7 +94,7 @@ func TestStartClientAndTestReverseProxy(t *testing.T) { // Attempt to run again on the same port should not fail since we stopped the // server. - if err = RunClientProxy(listenProxyAddr, "TestApp"); err != nil { + if err = RunClientProxy(listenProxyAddr, "TestApp", new(testCb)); err != nil { t.Fatalf("RunClientProxy: %q", err) } diff --git a/src/github.com/getlantern/lantern-ui/app/js/constants.js b/src/github.com/getlantern/lantern-ui/app/js/constants.js index c9643a5c30..f5d2d76aac 100644 --- a/src/github.com/getlantern/lantern-ui/app/js/constants.js +++ b/src/github.com/getlantern/lantern-ui/app/js/constants.js @@ -35,7 +35,7 @@ var DEFAULT_LANG = 'en_US', es: {dir: 'ltr', name: 'español'}, ar: {dir: 'rtl', name: 'العربية'} }, - GOOGLE_ANALYTICS_WEBPROP_ID = 'UA-21815217-2', + GOOGLE_ANALYTICS_WEBPROP_ID = 'UA-21815217-12', GOOGLE_ANALYTICS_DISABLE_KEY = 'ga-disable-'+GOOGLE_ANALYTICS_WEBPROP_ID, loc = typeof location == 'object' ? location : undefined, // this allows the real backend to mount the entire app under a random path diff --git a/src/github.com/getlantern/peerscanner/host.go b/src/github.com/getlantern/peerscanner/host.go index 2110161405..015401d66d 100644 --- a/src/github.com/getlantern/peerscanner/host.go +++ b/src/github.com/getlantern/peerscanner/host.go @@ -230,7 +230,9 @@ func (h *host) initCloudfront() { func (h *host) doInitCfrDist() { if h.cfrDist != nil && h.cfrDist.Status == "InProgress" { - cfr.RefreshStatus(cfrutil, h.cfrDist) + if err := cfr.RefreshStatus(cfrutil, h.cfrDist); err != nil { + log.Debugf("Unable to refresh status: %v", err) + } } if h.cfrDist == nil { dist, err := cfr.CreateDistribution( @@ -553,7 +555,9 @@ func (h *host) reallyDoIsAbleToProxy(port string) (bool, bool, error) { err2 := fmt.Errorf("Unable to connect to %v: %v", addr, err) return false, strings.Contains(err.Error(), "connection refused"), err2 } - conn.Close() + if err := conn.Close(); err != nil { + log.Debugf("Unable to close connection: %v", err) + } // Now actually try to proxy an http request site := testSites[rand.Intn(len(testSites))] @@ -561,7 +565,11 @@ func (h *host) reallyDoIsAbleToProxy(port string) (bool, bool, error) { if err != nil { return false, false, fmt.Errorf("Unable to make proxied HEAD request to %v: %v", site, err) } - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + log.Debugf("Unable to close response body: %v", err) + } + }() if resp.StatusCode != 200 && resp.StatusCode != 301 { err2 := fmt.Errorf("Proxying to %v via %v returned unexpected status %d,", site, h.ip, resp.StatusCode) diff --git a/src/github.com/getlantern/profiling/profiling.go b/src/github.com/getlantern/profiling/profiling.go index 625ba44f86..dafcd627fc 100644 --- a/src/github.com/getlantern/profiling/profiling.go +++ b/src/github.com/getlantern/profiling/profiling.go @@ -55,7 +55,9 @@ func startCPUProfiling(filename string) { if err != nil { log.Fatal(err) } - pprof.StartCPUProfile(f) + if err := pprof.StartCPUProfile(f); err != nil { + log.Fatalf("Unable to start profiling: %v", err) + } log.Debugf("Process will save cpu profile to %s after terminating", filename) } @@ -72,8 +74,12 @@ func saveMemProfile(filename string) { return } log.Debugf("Saving heap profile to: %s", filename) - pprof.WriteHeapProfile(f) - f.Close() + if err := pprof.WriteHeapProfile(f); err != nil { + log.Debugf("Unable to write heap profile: %v", err) + } + if err := f.Close(); err != nil { + log.Debugf("Unable to close file: %v", err) + } } func saveProfilingOnSigINT(cpu string, mem string) { diff --git a/src/github.com/getlantern/profiling/profiling_example_test.go b/src/github.com/getlantern/profiling/profiling_example_test.go index 74beb7e2c9..7b5964694d 100644 --- a/src/github.com/getlantern/profiling/profiling_example_test.go +++ b/src/github.com/getlantern/profiling/profiling_example_test.go @@ -1,6 +1,6 @@ package profiling -import profiling "." +import "github.com/getlantern/profiling" func ExampleStart() { finishProfiling := profiling.Start("cpu.prof", "mem.prof") diff --git a/src/github.com/getlantern/proxy/proxy.go b/src/github.com/getlantern/proxy/proxy.go index c351751841..ddf9983c44 100644 --- a/src/github.com/getlantern/proxy/proxy.go +++ b/src/github.com/getlantern/proxy/proxy.go @@ -38,7 +38,11 @@ func Test(t *testing.T, dialer Dialer) { t.Fatalf("Unable to accept connection: %s", err) return } - defer conn.Close() + defer func() { + if err := conn.Close(); err != nil { + t.Logf("Unable to close connection: %v", err) + } + }() b := make([]byte, 4) _, err = io.ReadFull(conn, b) if err != nil { @@ -55,7 +59,11 @@ func Test(t *testing.T, dialer Dialer) { if err != nil { t.Fatalf("Unable to dial via proxy: %s", err) } - defer conn.Close() + defer func() { + if err := conn.Close(); err != nil { + t.Logf("Unable to close connection: %v", err) + } + }() _, err = conn.Write(ping) if err != nil { diff --git a/src/github.com/getlantern/rotator/daily_rotator.go b/src/github.com/getlantern/rotator/daily_rotator.go index 1cd5ceffd4..4f8e3da2a4 100644 --- a/src/github.com/getlantern/rotator/daily_rotator.go +++ b/src/github.com/getlantern/rotator/daily_rotator.go @@ -44,7 +44,9 @@ func (r *DailyRotator) Write(bytes []byte) (n int, err error) { // If file exists and modificated last date, just rotate it modDate := stat.ModTime().Format(dateFormat) if modDate != nextDate { - os.Rename(r.path, r.path+"."+modDate) + if err := os.Rename(r.path, r.path+"."+modDate); err != nil { + log.Debugf("Unable to rename file: %v", err) + } } } @@ -78,7 +80,9 @@ func (r *DailyRotator) Write(bytes []byte) (n int, err error) { } } // Rename current log file to be archived - os.Rename(r.path, renamedName) + if err := os.Rename(r.path, renamedName); err != nil { + log.Debugf("Unable to rename file: %v", err) + } file, err := os.OpenFile(r.path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) if err != nil { diff --git a/src/github.com/getlantern/rotator/daily_rotator_test.go b/src/github.com/getlantern/rotator/daily_rotator_test.go index 3dc5a838e6..6205333cb7 100644 --- a/src/github.com/getlantern/rotator/daily_rotator_test.go +++ b/src/github.com/getlantern/rotator/daily_rotator_test.go @@ -13,29 +13,49 @@ func TestRotationNormalOutput(t *testing.T) { stat, _ := os.Lstat(path) if stat != nil { - os.Remove(path) + if err := os.Remove(path); err != nil { + t.Fatalf("Unable to remove file: %v", err) + } } rotator := NewDailyRotator(path) - defer rotator.Close() - - rotator.WriteString("SAMPLE LOG") + defer func() { + if err := rotator.Close(); err != nil { + t.Fatalf("Unable to close rotator: %v", err) + } + }() + + if _, err := rotator.WriteString("SAMPLE LOG"); err != nil { + t.Fatalf("Unable to write string: %v", err) + } file, err := os.OpenFile(path, os.O_RDONLY, 0644) if err != nil { panic(err) } - defer file.Close() + defer func() { + if err := file.Close(); err != nil { + t.Fatalf("Unable to close file: %v", err) + } + }() b := make([]byte, 10) - file.Read(b) + if _, err := file.Read(b); err != nil { + t.Fatalf("Unable to read file: %v", err) + } assert.Equal(t, "SAMPLE LOG", string(b)) - rotator.WriteString("\nNEXT LOG") - rotator.WriteString("\nLAST LOG") + if _, err := rotator.WriteString("\nNEXT LOG"); err != nil { + t.Fatalf("Unable to write string: %v", err) + } + if _, err := rotator.WriteString("\nLAST LOG"); err != nil { + t.Fatalf("Unable to write string: %v", err) + } b = make([]byte, 28) - file.ReadAt(b, 0) + if _, err := file.ReadAt(b, 0); err != nil { + t.Fatalf("Unable to read file: %v", err) + } assert.Equal(t, "SAMPLE LOG\nNEXT LOG\nLAST LOG", string(b)) @@ -47,27 +67,41 @@ func TestDailyRotationOnce(t *testing.T) { stat, _ := os.Lstat(path) if stat != nil { - os.Remove(path) + if err := os.Remove(path); err != nil { + t.Fatalf("Unable to remove: %v", err) + } } now := time.Now() rotator := NewDailyRotator(path) - defer rotator.Close() + defer func() { + if err := rotator.Close(); err != nil { + t.Fatalf("Unable to close rotator: %v", err) + } + }() rotator.Now = now - rotator.WriteString("SAMPLE LOG") + if _, err := rotator.WriteString("SAMPLE LOG"); err != nil { + t.Fatalf("Unable to write string: %v", err) + } // simulate next day rotator.Now = time.Unix(now.Unix()+86400, 0) - rotator.WriteString("NEXT LOG") + if _, err := rotator.WriteString("NEXT LOG"); err != nil { + t.Fatalf("Unable to write string: %v", err) + } stat, _ = os.Lstat(path + "." + now.Format(dateFormat)) assert.NotNil(t, stat) - os.Remove(path) - os.Remove(path + "." + now.Format(dateFormat)) + if err := os.Remove(path); err != nil { + t.Fatalf("Unable to remove file: %v", err) + } + if err := os.Remove(path + "." + now.Format(dateFormat)); err != nil { + t.Fatalf("Unable to remove file: %v", err) + } } func TestDailyRotationAtOpen(t *testing.T) { @@ -76,28 +110,44 @@ func TestDailyRotationAtOpen(t *testing.T) { stat, _ := os.Lstat(path) if stat != nil { - os.Remove(path) + if err := os.Remove(path); err != nil { + t.Fatalf("Unable to remove file: %v", err) + } } rotator := NewDailyRotator(path) - rotator.WriteString("FIRST LOG") - rotator.Close() + if _, err := rotator.WriteString("FIRST LOG"); err != nil { + t.Fatalf("Unable to write string: %v", err) + } + if err := rotator.Close(); err != nil { + t.Fatalf("Unable to close rotator: %v", err) + } now := time.Now() // simulate next day rotator = NewDailyRotator(path) - defer rotator.Close() + defer func() { + if err := rotator.Close(); err != nil { + t.Fatalf("Unable to close rotator: %v", err) + } + }() rotator.Now = time.Unix(now.Unix()+86400, 0) - rotator.WriteString("NEXT LOG") + if _, err := rotator.WriteString("NEXT LOG"); err != nil { + t.Fatalf("Unable to write string: %v", err) + } stat, _ = os.Lstat(path + "." + now.Format(dateFormat)) assert.NotNil(t, stat) - os.Remove(path) - os.Remove(path + "." + now.Format(dateFormat)) + if err := os.Remove(path); err != nil { + t.Fatalf("Unable to remove file: %v", err) + } + if err := os.Remove(path + "." + now.Format(dateFormat)); err != nil { + t.Fatalf("Unable to remove file: %v", err) + } } func TestDailyRotationError(t *testing.T) { @@ -106,28 +156,45 @@ func TestDailyRotationError(t *testing.T) { stat, _ := os.Lstat(path) if stat != nil { - os.Remove(path) + if err := os.Remove(path); err != nil { + t.Fatalf("Unable to remove file: %v", err) + } } now := time.Now() rotator := NewDailyRotator(path) - rotator.WriteString("FIRST LOG") + if _, err := rotator.WriteString("FIRST LOG"); err != nil { + t.Fatalf("Unable to write string: %v", err) + } // Simulate rotation rotator.Now = time.Unix(now.Unix()+86400, 0) - rotator.WriteString("SECOND LOG") - rotator.Close() + if _, err := rotator.WriteString("SECOND LOG"); err != nil { + t.Fatalf("Unable to write string: %v", err) + } + if err := rotator.Close(); err != nil { + t.Fatalf("Unable to close rotator: %v", err) + } rotator = NewDailyRotator(path) - defer rotator.Close() - rotator.WriteString("FIRST LOG") + defer func() { + if err := rotator.Close(); err != nil { + t.Fatalf("Unable to close rotator: %v", err) + } + }() + if _, err := rotator.WriteString("FIRST LOG"); err != nil { + t.Fatalf("Unable to write string: %v", err) + } // Simulate rotation twice rotator.Now = time.Unix(now.Unix()+86400, 0) _, err := rotator.WriteString("SECOND LOG") assert.Nil(t, err) - os.Remove(path) - os.Remove(path + "." + now.Format(dateFormat)) - + if err := os.Remove(path); err != nil { + t.Fatalf("Unable to remove file: %v", err) + } + if err := os.Remove(path + "." + now.Format(dateFormat)); err != nil { + t.Fatalf("Unable to remove file: %v", err) + } } diff --git a/src/github.com/getlantern/rotator/rotator.go b/src/github.com/getlantern/rotator/rotator.go index 6068a6e838..394f8a2eea 100644 --- a/src/github.com/getlantern/rotator/rotator.go +++ b/src/github.com/getlantern/rotator/rotator.go @@ -1,6 +1,14 @@ package rotator -import "io" +import ( + "io" + + "github.com/getlantern/golog" +) + +var ( + log = golog.LoggerFor("sizerotator") +) // Rotator interface type Rotator interface { diff --git a/src/github.com/getlantern/rotator/rotator_test.go b/src/github.com/getlantern/rotator/rotator_test.go index ce695319ef..b76ca645fc 100644 --- a/src/github.com/getlantern/rotator/rotator_test.go +++ b/src/github.com/getlantern/rotator/rotator_test.go @@ -14,7 +14,9 @@ func TestRotatorInterfaceByDailyRotator(t *testing.T) { stat, _ := os.Lstat(path) if stat != nil { - os.Remove(path) + if err := os.Remove(path); err != nil { + t.Fatalf("Unable to remove path: %v", err) + } } var r Rotator @@ -23,27 +25,45 @@ func TestRotatorInterfaceByDailyRotator(t *testing.T) { r = NewDailyRotator(path) // 1. Close method - defer r.Close() + defer func() { + if err := r.Close(); err != nil { + t.Fatalf("Unable to close rotator: %v", err) + } + }() // 2. Write method - r.Write(bytes.NewBufferString("SAMPLE LOG").Bytes()) + if _, err := r.Write(bytes.NewBufferString("SAMPLE LOG").Bytes()); err != nil { + t.Fatalf("Unable to write: %v", err) + } file, err := os.OpenFile(path, os.O_RDONLY, 0644) if err != nil { panic(err) } - defer file.Close() + defer func() { + if err := file.Close(); err != nil { + t.Fatalf("Unable to close file: %v", err) + } + }() b := make([]byte, 10) - file.Read(b) + if _, err := file.Read(b); err != nil { + t.Fatalf("Unable to read file: %v", err) + } assert.Equal(t, "SAMPLE LOG", string(b)) // 3. WriteString method - r.WriteString("\nNEXT LOG") - r.WriteString("\nLAST LOG") + if _, err := r.WriteString("\nNEXT LOG"); err != nil { + t.Fatalf("Unable to write string: %v", err) + } + if _, err := r.WriteString("\nLAST LOG"); err != nil { + t.Fatalf("Unable to write string: %v", err) + } b = make([]byte, 28) - file.ReadAt(b, 0) + if _, err := file.ReadAt(b, 0); err != nil { + t.Fatalf("Unable to read: %v", err) + } assert.Equal(t, "SAMPLE LOG\nNEXT LOG\nLAST LOG", string(b)) @@ -55,7 +75,9 @@ func TestRotatorInterfaceBySizeRotator(t *testing.T) { stat, _ := os.Lstat(path) if stat != nil { - os.Remove(path) + if err := os.Remove(path); err != nil { + t.Fatalf("Unable to remove file: %v", err) + } } var r Rotator @@ -64,27 +86,45 @@ func TestRotatorInterfaceBySizeRotator(t *testing.T) { r = NewSizeRotator(path) // 1. Close method - defer r.Close() + defer func() { + if err := r.Close(); err != nil { + t.Fatalf("Unable to close rotator: %v", err) + } + }() // 2. Write method - r.Write(bytes.NewBufferString("SAMPLE LOG").Bytes()) + if _, err := r.Write(bytes.NewBufferString("SAMPLE LOG").Bytes()); err != nil { + t.Fatalf("Unable to write: %v", err) + } file, err := os.OpenFile(path, os.O_RDONLY, 0644) if err != nil { panic(err) } - defer file.Close() + defer func() { + if err := file.Close(); err != nil { + t.Fatalf("Unable to close file: %v", err) + } + }() b := make([]byte, 10) - file.Read(b) + if _, err := file.Read(b); err != nil { + t.Fatalf("Unable to read file: %v", err) + } assert.Equal(t, "SAMPLE LOG", string(b)) // 3. WriteString method - r.WriteString("|NEXT LOG") - r.WriteString("|LAST LOG") + if _, err := r.WriteString("|NEXT LOG"); err != nil { + t.Fatalf("Unable to write: %v", err) + } + if _, err := r.WriteString("|LAST LOG"); err != nil { + t.Fatalf("Unable to write: %v", err) + } b = make([]byte, 28) - file.ReadAt(b, 0) + if _, err := file.ReadAt(b, 0); err != nil { + t.Fatalf("Unable to read: %v", err) + } assert.Equal(t, "SAMPLE LOG|NEXT LOG|LAST LOG", string(b)) } diff --git a/src/github.com/getlantern/rotator/size_rotator.go b/src/github.com/getlantern/rotator/size_rotator.go index 09389df6e6..e442ea80a2 100644 --- a/src/github.com/getlantern/rotator/size_rotator.go +++ b/src/github.com/getlantern/rotator/size_rotator.go @@ -41,7 +41,9 @@ func (r *SizeRotator) Write(bytes []byte) (n int, err error) { // Do rotate when size exceeded if r.totalSize+int64(len(bytes)) > r.RotationSize { if r.file != nil { - r.file.Close() + if err := r.file.Close(); err != nil { + return 0, fmt.Errorf("Unable to close file: %v", err) + } r.file = nil } // Remove oldest file (in case it exists) diff --git a/src/github.com/getlantern/rotator/size_rotator_test.go b/src/github.com/getlantern/rotator/size_rotator_test.go index d425dcf049..0a06739d28 100644 --- a/src/github.com/getlantern/rotator/size_rotator_test.go +++ b/src/github.com/getlantern/rotator/size_rotator_test.go @@ -10,38 +10,64 @@ const ( path = "test_size.log" ) -func cleanup() { - os.Remove(path) - os.Remove(path + ".1") - os.Remove(path + ".2") - os.Remove(path + ".3") +func cleanup(t *testing.T) { + if err := os.Remove(path); err != nil { + t.Logf("Unable to remove file: %v", err) + } + if err := os.Remove(path + ".1"); err != nil { + t.Logf("Unable to remove file: %v", err) + } + if err := os.Remove(path + ".2"); err != nil { + t.Logf("Unable to remove file: %v", err) + } + if err := os.Remove(path + ".3"); err != nil { + t.Logf("Unable to remove file: %v", err) + } } func TestSizeNormalOutput(t *testing.T) { - cleanup() - defer cleanup() + cleanup(t) + defer cleanup(t) rotator := NewSizeRotator(path) - defer rotator.Close() - - rotator.WriteString("SAMPLE LOG") + defer func() { + if err := rotator.Close(); err != nil { + t.Fatalf("Unable to close rotator: %v", err) + } + }() + + if _, err := rotator.WriteString("SAMPLE LOG"); err != nil { + t.Fatalf("Unable to write string: %v", err) + } file, err := os.OpenFile(path, os.O_RDONLY, 0644) if err != nil { panic(err) } - defer file.Close() + defer func() { + if err := file.Close(); err != nil { + t.Fatalf("Unable to close file: %v", err) + } + }() b := make([]byte, 10) - file.Read(b) + if _, err := file.Read(b); err != nil { + t.Fatalf("Unable to read: %v", err) + } assert.Equal(t, "SAMPLE LOG", string(b)) - rotator.WriteString("|NEXT LOG") - rotator.WriteString("|LAST LOG") + if _, err := rotator.WriteString("|NEXT LOG"); err != nil { + t.Fatalf("Unable to write string: %v", err) + } + if _, err := rotator.WriteString("|LAST LOG"); err != nil { + t.Fatalf("Unable to write string: %v", err) + } b = make([]byte, 28) - file.ReadAt(b, 0) + if _, err := file.ReadAt(b, 0); err != nil { + t.Fatalf("Unable to read file: %v", err) + } assert.Equal(t, "SAMPLE LOG|NEXT LOG|LAST LOG", string(b)) @@ -49,20 +75,28 @@ func TestSizeNormalOutput(t *testing.T) { func TestSizeRotation(t *testing.T) { - cleanup() - defer cleanup() + cleanup(t) + defer cleanup(t) rotator := NewSizeRotator(path) rotator.RotationSize = 10 - defer rotator.Close() - - rotator.WriteString("0123456789") + defer func() { + if err := rotator.Close(); err != nil { + t.Fatalf("Unable to close rotator: %v", err) + } + }() + + if _, err := rotator.WriteString("0123456789"); err != nil { + t.Fatalf("Unable to write string: %v", err) + } // it should not be rotated stat, _ := os.Lstat(path + ".1") assert.Nil(t, stat) // it should be rotated - rotator.WriteString("0123456789") + if _, err := rotator.WriteString("0123456789"); err != nil { + t.Fatalf("Unable to close rotator: %v", err) + } stat, _ = os.Lstat(path) assert.NotNil(t, stat) assert.EqualValues(t, stat.Size(), 10) @@ -75,12 +109,17 @@ func TestSizeRotation(t *testing.T) { func TestSizeAppendExist(t *testing.T) { - cleanup() - defer cleanup() + cleanup(t) + defer cleanup(t) file, _ := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0644) - file.WriteString("01234") // size should be 5 - file.Close() + // size should be 5; + if _, err := file.WriteString("01234"); err != nil { + t.Fatalf("Unable to write string: %v", err) + } + if err := file.Close(); err != nil { + t.Fatalf("Unable to close file: %v", err) + } rotator := NewSizeRotator(path) rotator.RotationSize = 10 @@ -99,21 +138,33 @@ func TestSizeAppendExist(t *testing.T) { func TestSizeMaxRotation(t *testing.T) { - cleanup() - defer cleanup() + cleanup(t) + defer cleanup(t) rotator := NewSizeRotator(path) rotator.RotationSize = 10 rotator.MaxRotation = 3 - defer rotator.Close() - - rotator.WriteString("0123456789") + defer func() { + if err := rotator.Close(); err != nil { + t.Fatalf("Unable to close rotator: %v", err) + } + }() + + if _, err := rotator.WriteString("0123456789"); err != nil { + t.Fatalf("Unable to write string: %v", err) + } stat, _ := os.Lstat(path + ".1") assert.Nil(t, stat) - rotator.WriteString("0123456789") - rotator.WriteString("0123456789") - rotator.WriteString("0123456789") + if _, err := rotator.WriteString("0123456789"); err != nil { + t.Fatalf("Unable to close rotator: %v", err) + } + if _, err := rotator.WriteString("0123456789"); err != nil { + t.Fatalf("Unable to close rotator: %v", err) + } + if _, err := rotator.WriteString("0123456789"); err != nil { + t.Fatalf("Unable to close rotator: %v", err) + } stat, _ = os.Lstat(path + ".1") assert.NotNil(t, stat) diff --git a/src/github.com/getlantern/tarfs/embed.go b/src/github.com/getlantern/tarfs/embed.go index f7eeff53fe..1fc15424e6 100644 --- a/src/github.com/getlantern/tarfs/embed.go +++ b/src/github.com/getlantern/tarfs/embed.go @@ -14,15 +14,21 @@ import ( // the given Writer in the form of an unquoted UTF-8 encoded string that // contains a tar archive of the directory, for example // \x69\x6e\x64\x65\x78\x2e\x68\x74 ... -func EncodeToTarString(dir string, w io.Writer) error { +func EncodeToTarString(dir string, w io.Writer) (err error) { bw := bufio.NewWriter(w) tw := tar.NewWriter(&stringencodingwriter{bw}) - defer tw.Close() + defer func() { + errClose := tw.Close() + // Overwrite return error only if it was nil + if err == nil && errClose != nil { + err = errClose + } + }() dirPrefix := dir + "/" dirPrefixLen := len(dirPrefix) - err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if err != nil { return fmt.Errorf("Unable to walk to %v: %v", path, err) } @@ -45,7 +51,13 @@ func EncodeToTarString(dir string, w io.Writer) error { if err != nil { return fmt.Errorf("Unable to open file %v: %v", path, err) } - defer file.Close() + defer func() { + errClose := file.Close() + // Overwrite return error only if it was nil + if err == nil && errClose != nil { + err = errClose + } + }() _, err = io.Copy(tw, file) if err != nil { return fmt.Errorf("Unable to copy file %v to tar: %v", path, err) diff --git a/src/github.com/getlantern/tlsdialer/tlsdialer.go b/src/github.com/getlantern/tlsdialer/tlsdialer.go index ae7d3e5ee9..f9a9c58291 100644 --- a/src/github.com/getlantern/tlsdialer/tlsdialer.go +++ b/src/github.com/getlantern/tlsdialer/tlsdialer.go @@ -187,7 +187,9 @@ func DialForTimings(dialer *net.Dialer, network, addr string, sendServerName boo if err != nil { log.Trace("Handshake or verification error, closing underlying connection") - rawConn.Close() + if err := rawConn.Close(); err != nil { + log.Debugf("Unable to close connection: %v", err) + } return result, err } diff --git a/src/github.com/getlantern/tlsdialer/tlsdialer_test.go b/src/github.com/getlantern/tlsdialer/tlsdialer_test.go index 015a41f044..b725b6d9af 100644 --- a/src/github.com/getlantern/tlsdialer/tlsdialer_test.go +++ b/src/github.com/getlantern/tlsdialer/tlsdialer_test.go @@ -56,9 +56,12 @@ func init() { } go func() { tlsConn := conn.(*tls.Conn) - tlsConn.Handshake() + // Discard this error, since we will use it for testing + _ = tlsConn.Handshake() serverName := tlsConn.ConnectionState().ServerName - conn.Close() + if err := conn.Close(); err != nil { + log.Fatalf("Unable to close connection: %v", err) + } receivedServerNames <- serverName }() } @@ -202,6 +205,10 @@ func TestVariableTimeouts(t *testing.T) { } for i := 0; i < 500; i++ { + // The 5000 microseconds limit is arbitrary. In some systems this may be too high, + // leading to a successful connection and thus a failed test. On the other hand, + // we need to make this limit relatively high, to allow timeouts to happen at different + // places doTestTimeout(t, time.Duration(rand.Intn(5000)+1)*time.Microsecond) } @@ -213,7 +220,9 @@ func TestVariableTimeouts(t *testing.T) { func doTestTimeout(t *testing.T, timeout time.Duration) { _, err := DialWithDialer(&net.Dialer{ Timeout: timeout, - }, "tcp", ADDR, false, nil) + }, "tcp", ADDR, false, &tls.Config{ + RootCAs: cert.PoolContainingCert(), + }) assert.Error(t, err, "There should have been a problem dialing", timeout) @@ -252,7 +261,9 @@ func TestDeadlineBeforeTimeout(t *testing.T) { func closeAndCountFDs(t *testing.T, conn *tls.Conn, err error, fdc *fdcount.Counter) { if err == nil { - conn.Close() + if err := conn.Close(); err != nil { + t.Fatalf("Unable to close connection: %v", err) + } } assert.NoError(t, fdc.AssertDelta(0), "Number of open TCP files should be the same after test as before") } diff --git a/src/github.com/getlantern/waitforserver/waitforserver.go b/src/github.com/getlantern/waitforserver/waitforserver.go index d418eb6efd..9902bf32ef 100644 --- a/src/github.com/getlantern/waitforserver/waitforserver.go +++ b/src/github.com/getlantern/waitforserver/waitforserver.go @@ -35,8 +35,7 @@ func WaitForServer(protocol string, addr string, limit time.Duration) error { } c, err := net.DialTimeout(protocol, addr, limit) if err == nil { - c.Close() - return nil + return c.Close() } time.Sleep(50 * time.Millisecond) } diff --git a/src/github.com/getlantern/waitforserver/waitforserver_test.go b/src/github.com/getlantern/waitforserver/waitforserver_test.go index 086ac49b8c..7238f20e9c 100644 --- a/src/github.com/getlantern/waitforserver/waitforserver_test.go +++ b/src/github.com/getlantern/waitforserver/waitforserver_test.go @@ -13,7 +13,11 @@ func TestSuccess(t *testing.T) { if err != nil { t.Fatalf("Unable to listen: %s", err) } - defer l.Close() + defer func() { + if err := l.Close(); err != nil { + t.Fatalf("Unable to close listener: %v", err) + } + }() err = WaitForServer("tcp", l.Addr().String(), 100*time.Millisecond) assert.NoError(t, err, "Server should have been found") } diff --git a/src/github.com/getlantern/wfilter/wfilter.go b/src/github.com/getlantern/wfilter/wfilter.go index 17794d09bf..e953b5e68b 100644 --- a/src/github.com/getlantern/wfilter/wfilter.go +++ b/src/github.com/getlantern/wfilter/wfilter.go @@ -9,19 +9,19 @@ import ( type Prepend func(w io.Writer) (int, error) // LinePrepender creates an io.Writer that prepends to each line by calling the -// given prepend function. Perepend can write whatever it wants. It should +// given prepend function. Prepend can write whatever it wants. It should // return the bytes written and any error encountered. func LinePrepender(w io.Writer, prepend Prepend) io.Writer { - return &lp{w, prepend, true} + return &linePrepender{w, prepend, true} } -type lp struct { +type linePrepender struct { io.Writer prepend Prepend prependNeeded bool } -func (w *lp) Write(buf []byte) (int, error) { +func (w *linePrepender) Write(buf []byte) (int, error) { if w.prependNeeded { _, err := w.prepend(w.Writer) if err != nil { @@ -66,3 +66,29 @@ func (w *lp) Write(buf []byte) (int, error) { totalN += n return totalN, err } + +// SimplePrepender creates an io.Writer that prepends by calling the given +// prepend function. Prepend can write whatever it wants. It should return +// the bytes written and any error encountered. +func SimplePrepender(w io.Writer, prepend Prepend) io.Writer { + return &simplePrepender{w, prepend} +} + +type simplePrepender struct { + io.Writer + prepend Prepend +} + +func (w *simplePrepender) Write(buf []byte) (int, error) { + written := 0 + + n, err := w.prepend(w.Writer) + written = written + n + if err != nil { + return written, err + } + + n, err = w.Writer.Write(buf) + written = written + n + return written, err +} diff --git a/src/github.com/getlantern/wfilter/wfilter_test.go b/src/github.com/getlantern/wfilter/wfilter_test.go index 2085d8184b..c7360eddad 100644 --- a/src/github.com/getlantern/wfilter/wfilter_test.go +++ b/src/github.com/getlantern/wfilter/wfilter_test.go @@ -10,7 +10,13 @@ import ( "github.com/getlantern/testify/assert" ) -func TestLines(t *testing.T) { +var expected = `1 A +2 B +3 C +4 D +` + +func TestLinePrepender(t *testing.T) { buf := bytes.NewBuffer(nil) i := int32(0) w := LinePrepender(buf, func(w io.Writer) (int, error) { @@ -38,8 +44,62 @@ func TestLines(t *testing.T) { assert.Equal(t, expected, string(buf.Bytes())) } -var expected = `1 A -2 B -3 C -4 D -` +func TestSimplePrepender(t *testing.T) { + buf := bytes.NewBuffer(nil) + w := SimplePrepender(buf, func(w io.Writer) (int, error) { + return fmt.Fprintf(w, "++ ") + }) + + n, err := fmt.Fprint(w, "##") + if assert.NoError(t, err, "Error writing A") { + assert.Equal(t, 5, n, "Wrong bytes written for A") + } + + n, err = fmt.Fprint(w, "##\n\n") + if assert.NoError(t, err, "Error writing A") { + assert.Equal(t, 7, n, "Wrong bytes written for A") + } + + n, err = fmt.Fprint(w, "\n\n##\n\n") + if assert.NoError(t, err, "Error writing A") { + assert.Equal(t, 9, n, "Wrong bytes written for A") + } + + w = SimplePrepender(buf, func(w io.Writer) (int, error) { + return fmt.Fprintf(w, "") + }) + + n, err = fmt.Fprint(w, "##") + if assert.NoError(t, err, "Error writing A") { + assert.Equal(t, 2, n, "Wrong bytes written for A") + } + + n, err = fmt.Fprint(w, "##\n\n") + if assert.NoError(t, err, "Error writing A") { + assert.Equal(t, 4, n, "Wrong bytes written for A") + } + + n, err = fmt.Fprint(w, "\n\n##\n\n") + if assert.NoError(t, err, "Error writing A") { + assert.Equal(t, 6, n, "Wrong bytes written for A") + } + + w = SimplePrepender(buf, func(w io.Writer) (int, error) { + return fmt.Fprintf(w, "\n\n") + }) + + n, err = fmt.Fprint(w, "##") + if assert.NoError(t, err, "Error writing A") { + assert.Equal(t, 4, n, "Wrong bytes written for A") + } + + n, err = fmt.Fprint(w, "##\n\n") + if assert.NoError(t, err, "Error writing A") { + assert.Equal(t, 6, n, "Wrong bytes written for A") + } + + n, err = fmt.Fprint(w, "\n\n##\n\n") + if assert.NoError(t, err, "Error writing A") { + assert.Equal(t, 8, n, "Wrong bytes written for A") + } +} diff --git a/src/github.com/getlantern/yamlconf/yamlconf_disk.go b/src/github.com/getlantern/yamlconf/yamlconf_disk.go index 0ef71f9697..2383db30d1 100644 --- a/src/github.com/getlantern/yamlconf/yamlconf_disk.go +++ b/src/github.com/getlantern/yamlconf/yamlconf_disk.go @@ -36,7 +36,9 @@ func (m *Manager) reloadFromDisk() (bool, error) { if m.cfg != nil && m.cfg.GetVersion() != cfg.GetVersion() { log.Trace("Version mismatch on disk, overwriting what's on disk with current version") - m.writeToDisk(m.cfg) + if err := m.writeToDisk(m.cfg); err != nil { + log.Errorf("Unable to write to disk: %v", err) + } return false, fmt.Errorf("Version of config on disk did not match expected. Expected %d, found %d", m.cfg.GetVersion(), cfg.GetVersion()) } diff --git a/src/github.com/getlantern/yamlconf/yamlconf_http.go b/src/github.com/getlantern/yamlconf/yamlconf_http.go index 8c5979004d..1a4faf5b35 100644 --- a/src/github.com/getlantern/yamlconf/yamlconf_http.go +++ b/src/github.com/getlantern/yamlconf/yamlconf_http.go @@ -38,7 +38,11 @@ func (m *Manager) startConfigServer() error { } func (m *Manager) ServeHTTP(resp http.ResponseWriter, req *http.Request) { - defer req.Body.Close() + defer func() { + if err := req.Body.Close(); err != nil { + log.Debugf("Unable to close request body: %v", err) + } + }() if len(req.URL.Path) < 2 { fail(resp, "Invalid path") @@ -103,5 +107,7 @@ func (m *Manager) ServeHTTP(resp http.ResponseWriter, req *http.Request) { func fail(resp http.ResponseWriter, msg string) { resp.Header().Set("Content-Type", "text/plain") resp.WriteHeader(BadRequest) - resp.Write([]byte(msg)) + if _, err := resp.Write([]byte(msg)); err != nil { + log.Debugf("Unable to write response: %v", err) + } } diff --git a/src/github.com/getlantern/yamlconf/yamlconf_test.go b/src/github.com/getlantern/yamlconf/yamlconf_test.go index 90e6f8af65..63b387abcf 100644 --- a/src/github.com/getlantern/yamlconf/yamlconf_test.go +++ b/src/github.com/getlantern/yamlconf/yamlconf_test.go @@ -55,7 +55,11 @@ func TestFileAndUpdate(t *testing.T) { if err != nil { t.Fatalf("Unable to create temp file: %s", err) } - defer os.Remove(file.Name()) + defer func() { + if err := os.Remove(file.Name()); err != nil { + t.Fatalf("Unable to remove file: %v", err) + } + }() m := &Manager{ EmptyConfig: func() Config { @@ -155,7 +159,11 @@ func TestCustomPoll(t *testing.T) { if err != nil { t.Fatalf("Unable to create temp file: %s", err) } - defer os.Remove(file.Name()) + defer func() { + if err := os.Remove(file.Name()); err != nil { + t.Fatalf("Unable to remove file: %s", err) + } + }() poll := 0 m := &Manager{ @@ -211,7 +219,11 @@ func TestConfigServer(t *testing.T) { if err != nil { t.Fatalf("Unable to create temp file: %s", err) } - defer os.Remove(file.Name()) + defer func() { + if err := os.Remove(file.Name()); err != nil { + t.Fatalf("Unable to remove file: %s", err) + } + }() m := &Manager{ EmptyConfig: func() Config { diff --git a/src/golang.org/x/image/.gitattributes b/src/golang.org/x/image/.gitattributes new file mode 100644 index 0000000000..d2f212e5da --- /dev/null +++ b/src/golang.org/x/image/.gitattributes @@ -0,0 +1,10 @@ +# Treat all files in this repo as binary, with no git magic updating +# line endings. Windows users contributing to Go will need to use a +# modern version of git and editors capable of LF line endings. +# +# We'll prevent accidental CRLF line endings from entering the repo +# via the git-review gofmt checks. +# +# See golang.org/issue/9281 + +* -text diff --git a/src/golang.org/x/image/.gitignore b/src/golang.org/x/image/.gitignore new file mode 100644 index 0000000000..8339fd61d3 --- /dev/null +++ b/src/golang.org/x/image/.gitignore @@ -0,0 +1,2 @@ +# Add no patterns to .hgignore except for files generated by the build. +last-change diff --git a/src/golang.org/x/image/AUTHORS b/src/golang.org/x/image/AUTHORS new file mode 100644 index 0000000000..15167cd746 --- /dev/null +++ b/src/golang.org/x/image/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at http://tip.golang.org/AUTHORS. diff --git a/src/golang.org/x/image/CONTRIBUTING.md b/src/golang.org/x/image/CONTRIBUTING.md new file mode 100644 index 0000000000..88dff59bc7 --- /dev/null +++ b/src/golang.org/x/image/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# Contributing to Go + +Go is an open source project. + +It is the work of hundreds of contributors. We appreciate your help! + + +## Filing issues + +When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions: + +1. What version of Go are you using (`go version`)? +2. What operating system and processor architecture are you using? +3. What did you do? +4. What did you expect to see? +5. What did you see instead? + +General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker. +The gophers there will answer or ask you to file an issue if you've tripped over a bug. + +## Contributing code + +Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html) +before sending patches. + +**We do not accept GitHub pull requests** +(we use [Gerrit](https://code.google.com/p/gerrit/) instead for code review). + +Unless otherwise noted, the Go source files are distributed under +the BSD-style license found in the LICENSE file. + diff --git a/src/golang.org/x/image/CONTRIBUTORS b/src/golang.org/x/image/CONTRIBUTORS new file mode 100644 index 0000000000..1c4577e968 --- /dev/null +++ b/src/golang.org/x/image/CONTRIBUTORS @@ -0,0 +1,3 @@ +# This source code was written by the Go contributors. +# The master list of contributors is in the main Go distribution, +# visible at http://tip.golang.org/CONTRIBUTORS. diff --git a/src/golang.org/x/image/LICENSE b/src/golang.org/x/image/LICENSE new file mode 100644 index 0000000000..6a66aea5ea --- /dev/null +++ b/src/golang.org/x/image/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/golang.org/x/image/PATENTS b/src/golang.org/x/image/PATENTS new file mode 100644 index 0000000000..733099041f --- /dev/null +++ b/src/golang.org/x/image/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google 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, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/src/golang.org/x/image/README b/src/golang.org/x/image/README new file mode 100644 index 0000000000..4620380791 --- /dev/null +++ b/src/golang.org/x/image/README @@ -0,0 +1,3 @@ +This repository holds supplementary Go image libraries. + +To submit changes to this repository, see http://golang.org/doc/contribute.html. diff --git a/src/golang.org/x/image/bmp/reader.go b/src/golang.org/x/image/bmp/reader.go new file mode 100644 index 0000000000..a0f2715cd9 --- /dev/null +++ b/src/golang.org/x/image/bmp/reader.go @@ -0,0 +1,199 @@ +// 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. + +// Package bmp implements a BMP image decoder and encoder. +// +// The BMP specification is at http://www.digicamsoft.com/bmp/bmp.html. +package bmp // import "golang.org/x/image/bmp" + +import ( + "errors" + "image" + "image/color" + "io" +) + +// ErrUnsupported means that the input BMP image uses a valid but unsupported +// feature. +var ErrUnsupported = errors.New("bmp: unsupported BMP image") + +func readUint16(b []byte) uint16 { + return uint16(b[0]) | uint16(b[1])<<8 +} + +func readUint32(b []byte) uint32 { + return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 +} + +// decodePaletted reads an 8 bit-per-pixel BMP image from r. +// If topDown is false, the image rows will be read bottom-up. +func decodePaletted(r io.Reader, c image.Config, topDown bool) (image.Image, error) { + paletted := image.NewPaletted(image.Rect(0, 0, c.Width, c.Height), c.ColorModel.(color.Palette)) + if c.Width == 0 || c.Height == 0 { + return paletted, nil + } + var tmp [4]byte + y0, y1, yDelta := c.Height-1, -1, -1 + if topDown { + y0, y1, yDelta = 0, c.Height, +1 + } + for y := y0; y != y1; y += yDelta { + p := paletted.Pix[y*paletted.Stride : y*paletted.Stride+c.Width] + if _, err := io.ReadFull(r, p); err != nil { + return nil, err + } + // Each row is 4-byte aligned. + if c.Width%4 != 0 { + _, err := io.ReadFull(r, tmp[:4-c.Width%4]) + if err != nil { + return nil, err + } + } + } + return paletted, nil +} + +// decodeRGB reads a 24 bit-per-pixel BMP image from r. +// If topDown is false, the image rows will be read bottom-up. +func decodeRGB(r io.Reader, c image.Config, topDown bool) (image.Image, error) { + rgba := image.NewRGBA(image.Rect(0, 0, c.Width, c.Height)) + if c.Width == 0 || c.Height == 0 { + return rgba, nil + } + // There are 3 bytes per pixel, and each row is 4-byte aligned. + b := make([]byte, (3*c.Width+3)&^3) + y0, y1, yDelta := c.Height-1, -1, -1 + if topDown { + y0, y1, yDelta = 0, c.Height, +1 + } + for y := y0; y != y1; y += yDelta { + if _, err := io.ReadFull(r, b); err != nil { + return nil, err + } + p := rgba.Pix[y*rgba.Stride : y*rgba.Stride+c.Width*4] + for i, j := 0, 0; i < len(p); i, j = i+4, j+3 { + // BMP images are stored in BGR order rather than RGB order. + p[i+0] = b[j+2] + p[i+1] = b[j+1] + p[i+2] = b[j+0] + p[i+3] = 0xFF + } + } + return rgba, nil +} + +// decodeNRGBA reads a 32 bit-per-pixel BMP image from r. +// If topDown is false, the image rows will be read bottom-up. +func decodeNRGBA(r io.Reader, c image.Config, topDown bool) (image.Image, error) { + rgba := image.NewNRGBA(image.Rect(0, 0, c.Width, c.Height)) + if c.Width == 0 || c.Height == 0 { + return rgba, nil + } + y0, y1, yDelta := c.Height-1, -1, -1 + if topDown { + y0, y1, yDelta = 0, c.Height, +1 + } + for y := y0; y != y1; y += yDelta { + p := rgba.Pix[y*rgba.Stride : y*rgba.Stride+c.Width*4] + if _, err := io.ReadFull(r, p); err != nil { + return nil, err + } + for i := 0; i < len(p); i += 4 { + // BMP images are stored in BGRA order rather than RGBA order. + p[i+0], p[i+2] = p[i+2], p[i+0] + } + } + return rgba, nil +} + +// Decode reads a BMP image from r and returns it as an image.Image. +// Limitation: The file must be 8, 24 or 32 bits per pixel. +func Decode(r io.Reader) (image.Image, error) { + c, bpp, topDown, err := decodeConfig(r) + if err != nil { + return nil, err + } + switch bpp { + case 8: + return decodePaletted(r, c, topDown) + case 24: + return decodeRGB(r, c, topDown) + case 32: + return decodeNRGBA(r, c, topDown) + } + panic("unreachable") +} + +// DecodeConfig returns the color model and dimensions of a BMP image without +// decoding the entire image. +// Limitation: The file must be 8, 24 or 32 bits per pixel. +func DecodeConfig(r io.Reader) (image.Config, error) { + config, _, _, err := decodeConfig(r) + return config, err +} + +func decodeConfig(r io.Reader) (config image.Config, bitsPerPixel int, topDown bool, err error) { + // We only support those BMP images that are a BITMAPFILEHEADER + // immediately followed by a BITMAPINFOHEADER. + const ( + fileHeaderLen = 14 + infoHeaderLen = 40 + ) + var b [1024]byte + if _, err := io.ReadFull(r, b[:fileHeaderLen+infoHeaderLen]); err != nil { + return image.Config{}, 0, false, err + } + if string(b[:2]) != "BM" { + return image.Config{}, 0, false, errors.New("bmp: invalid format") + } + offset := readUint32(b[10:14]) + if readUint32(b[14:18]) != infoHeaderLen { + return image.Config{}, 0, false, ErrUnsupported + } + width := int(int32(readUint32(b[18:22]))) + height := int(int32(readUint32(b[22:26]))) + if height < 0 { + height, topDown = -height, true + } + if width < 0 || height < 0 { + return image.Config{}, 0, false, ErrUnsupported + } + // We only support 1 plane, 8 or 24 bits per pixel and no compression. + planes, bpp, compression := readUint16(b[26:28]), readUint16(b[28:30]), readUint32(b[30:34]) + if planes != 1 || compression != 0 { + return image.Config{}, 0, false, ErrUnsupported + } + switch bpp { + case 8: + if offset != fileHeaderLen+infoHeaderLen+256*4 { + return image.Config{}, 0, false, ErrUnsupported + } + _, err = io.ReadFull(r, b[:256*4]) + if err != nil { + return image.Config{}, 0, false, err + } + pcm := make(color.Palette, 256) + for i := range pcm { + // BMP images are stored in BGR order rather than RGB order. + // Every 4th byte is padding. + pcm[i] = color.RGBA{b[4*i+2], b[4*i+1], b[4*i+0], 0xFF} + } + return image.Config{ColorModel: pcm, Width: width, Height: height}, 8, topDown, nil + case 24: + if offset != fileHeaderLen+infoHeaderLen { + return image.Config{}, 0, false, ErrUnsupported + } + return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 24, topDown, nil + case 32: + if offset != fileHeaderLen+infoHeaderLen { + return image.Config{}, 0, false, ErrUnsupported + } + return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 32, topDown, nil + } + return image.Config{}, 0, false, ErrUnsupported +} + +func init() { + image.RegisterFormat("bmp", "BM????\x00\x00\x00\x00", Decode, DecodeConfig) +} diff --git a/src/golang.org/x/image/bmp/reader_test.go b/src/golang.org/x/image/bmp/reader_test.go new file mode 100644 index 0000000000..fd6ff64f6c --- /dev/null +++ b/src/golang.org/x/image/bmp/reader_test.go @@ -0,0 +1,75 @@ +// Copyright 2012 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. + +package bmp + +import ( + "fmt" + "image" + "os" + "testing" + + _ "image/png" +) + +const testdataDir = "../testdata/" + +func compare(t *testing.T, img0, img1 image.Image) error { + b := img1.Bounds() + if !b.Eq(img0.Bounds()) { + return fmt.Errorf("wrong image size: want %s, got %s", img0.Bounds(), b) + } + for y := b.Min.Y; y < b.Max.Y; y++ { + for x := b.Min.X; x < b.Max.X; x++ { + c0 := img0.At(x, y) + c1 := img1.At(x, y) + r0, g0, b0, a0 := c0.RGBA() + r1, g1, b1, a1 := c1.RGBA() + if r0 != r1 || g0 != g1 || b0 != b1 || a0 != a1 { + return fmt.Errorf("pixel at (%d, %d) has wrong color: want %v, got %v", x, y, c0, c1) + } + } + } + return nil +} + +// TestDecode tests that decoding a PNG image and a BMP image result in the +// same pixel data. +func TestDecode(t *testing.T) { + testCases := []string{ + "video-001", + "yellow_rose-small", + } + + for _, tc := range testCases { + f0, err := os.Open(testdataDir + tc + ".png") + if err != nil { + t.Errorf("%s: Open PNG: %v", tc, err) + continue + } + defer f0.Close() + img0, _, err := image.Decode(f0) + if err != nil { + t.Errorf("%s: Decode PNG: %v", tc, err) + continue + } + + f1, err := os.Open(testdataDir + tc + ".bmp") + if err != nil { + t.Errorf("%s: Open BMP: %v", tc, err) + continue + } + defer f1.Close() + img1, _, err := image.Decode(f1) + if err != nil { + t.Errorf("%s: Decode BMP: %v", tc, err) + continue + } + + if err := compare(t, img0, img1); err != nil { + t.Errorf("%s: %v", tc, err) + continue + } + } +} diff --git a/src/golang.org/x/image/bmp/writer.go b/src/golang.org/x/image/bmp/writer.go new file mode 100644 index 0000000000..6947968a40 --- /dev/null +++ b/src/golang.org/x/image/bmp/writer.go @@ -0,0 +1,166 @@ +// 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. + +package bmp + +import ( + "encoding/binary" + "errors" + "image" + "io" +) + +type header struct { + sigBM [2]byte + fileSize uint32 + resverved [2]uint16 + pixOffset uint32 + dibHeaderSize uint32 + width uint32 + height uint32 + colorPlane uint16 + bpp uint16 + compression uint32 + imageSize uint32 + xPixelsPerMeter uint32 + yPixelsPerMeter uint32 + colorUse uint32 + colorImportant uint32 +} + +func encodePaletted(w io.Writer, pix []uint8, dx, dy, stride, step int) error { + var padding []byte + if dx < step { + padding = make([]byte, step-dx) + } + for y := dy - 1; y >= 0; y-- { + min := y*stride + 0 + max := y*stride + dx + if _, err := w.Write(pix[min:max]); err != nil { + return err + } + if padding != nil { + if _, err := w.Write(padding); err != nil { + return err + } + } + } + return nil +} + +func encodeRGBA(w io.Writer, pix []uint8, dx, dy, stride, step int) error { + buf := make([]byte, step) + for y := dy - 1; y >= 0; y-- { + min := y*stride + 0 + max := y*stride + dx*4 + off := 0 + for i := min; i < max; i += 4 { + buf[off+2] = pix[i+0] + buf[off+1] = pix[i+1] + buf[off+0] = pix[i+2] + off += 3 + } + if _, err := w.Write(buf); err != nil { + return err + } + } + return nil +} + +func encode(w io.Writer, m image.Image, step int) error { + b := m.Bounds() + buf := make([]byte, step) + for y := b.Max.Y - 1; y >= b.Min.Y; y-- { + off := 0 + for x := b.Min.X; x < b.Max.X; x++ { + r, g, b, _ := m.At(x, y).RGBA() + buf[off+2] = byte(r >> 8) + buf[off+1] = byte(g >> 8) + buf[off+0] = byte(b >> 8) + off += 3 + } + if _, err := w.Write(buf); err != nil { + return err + } + } + return nil +} + +// Encode writes the image m to w in BMP format. +func Encode(w io.Writer, m image.Image) error { + d := m.Bounds().Size() + if d.X < 0 || d.Y < 0 { + return errors.New("bmp: negative bounds") + } + h := &header{ + sigBM: [2]byte{'B', 'M'}, + fileSize: 14 + 40, + pixOffset: 14 + 40, + dibHeaderSize: 40, + width: uint32(d.X), + height: uint32(d.Y), + colorPlane: 1, + } + + var step int + var palette []byte + switch m := m.(type) { + case *image.Gray: + step = (d.X + 3) &^ 3 + palette = make([]byte, 1024) + for i := 0; i < 256; i++ { + palette[i*4+0] = uint8(i) + palette[i*4+1] = uint8(i) + palette[i*4+2] = uint8(i) + palette[i*4+3] = 0xFF + } + h.imageSize = uint32(d.Y * step) + h.fileSize += uint32(len(palette)) + h.imageSize + h.pixOffset += uint32(len(palette)) + h.bpp = 8 + + case *image.Paletted: + step = (d.X + 3) &^ 3 + palette = make([]byte, 1024) + for i := 0; i < len(m.Palette) && i < 256; i++ { + r, g, b, _ := m.Palette[i].RGBA() + palette[i*4+0] = uint8(b >> 8) + palette[i*4+1] = uint8(g >> 8) + palette[i*4+2] = uint8(r >> 8) + palette[i*4+3] = 0xFF + } + h.imageSize = uint32(d.Y * step) + h.fileSize += uint32(len(palette)) + h.imageSize + h.pixOffset += uint32(len(palette)) + h.bpp = 8 + default: + step = (3*d.X + 3) &^ 3 + h.imageSize = uint32(d.Y * step) + h.fileSize += h.imageSize + h.bpp = 24 + } + + if err := binary.Write(w, binary.LittleEndian, h); err != nil { + return err + } + if palette != nil { + if err := binary.Write(w, binary.LittleEndian, palette); err != nil { + return err + } + } + + if d.X == 0 || d.Y == 0 { + return nil + } + + switch m := m.(type) { + case *image.Gray: + return encodePaletted(w, m.Pix, d.X, d.Y, m.Stride, step) + case *image.Paletted: + return encodePaletted(w, m.Pix, d.X, d.Y, m.Stride, step) + case *image.RGBA: + return encodeRGBA(w, m.Pix, d.X, d.Y, m.Stride, step) + } + return encode(w, m, step) +} diff --git a/src/golang.org/x/image/bmp/writer_test.go b/src/golang.org/x/image/bmp/writer_test.go new file mode 100644 index 0000000000..9e5a327416 --- /dev/null +++ b/src/golang.org/x/image/bmp/writer_test.go @@ -0,0 +1,91 @@ +// 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. + +package bmp + +import ( + "bytes" + "fmt" + "image" + "io/ioutil" + "os" + "testing" + "time" +) + +func openImage(filename string) (image.Image, error) { + f, err := os.Open(testdataDir + filename) + if err != nil { + return nil, err + } + defer f.Close() + return Decode(f) +} + +func TestEncode(t *testing.T) { + img0, err := openImage("video-001.bmp") + if err != nil { + t.Fatal(err) + } + + buf := new(bytes.Buffer) + err = Encode(buf, img0) + if err != nil { + t.Fatal(err) + } + + img1, err := Decode(buf) + if err != nil { + t.Fatal(err) + } + + compare(t, img0, img1) +} + +// TestZeroWidthVeryLargeHeight tests that encoding and decoding a degenerate +// image with zero width but over one billion pixels in height is faster than +// naively calling an io.Reader or io.Writer method once per row. +func TestZeroWidthVeryLargeHeight(t *testing.T) { + c := make(chan error, 1) + go func() { + b := image.Rect(0, 0, 0, 0x3fffffff) + var buf bytes.Buffer + if err := Encode(&buf, image.NewRGBA(b)); err != nil { + c <- err + return + } + m, err := Decode(&buf) + if err != nil { + c <- err + return + } + if got := m.Bounds(); got != b { + c <- fmt.Errorf("bounds: got %v, want %v", got, b) + return + } + c <- nil + }() + select { + case err := <-c: + if err != nil { + t.Fatal(err) + } + case <-time.After(3 * time.Second): + t.Fatalf("timed out") + } +} + +// BenchmarkEncode benchmarks the encoding of an image. +func BenchmarkEncode(b *testing.B) { + img, err := openImage("video-001.bmp") + if err != nil { + b.Fatal(err) + } + s := img.Bounds().Size() + b.SetBytes(int64(s.X * s.Y * 4)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Encode(ioutil.Discard, img) + } +} diff --git a/src/golang.org/x/image/cmd/webp-manual-test/main.go b/src/golang.org/x/image/cmd/webp-manual-test/main.go new file mode 100644 index 0000000000..c041e21350 --- /dev/null +++ b/src/golang.org/x/image/cmd/webp-manual-test/main.go @@ -0,0 +1,201 @@ +// Copyright 2014 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 ignore +// +// This build tag means that "go install golang.org/x/image/..." doesn't +// install this manual test. Use "go run main.go" to explicitly run it. + +// Program webp-manual-test checks that the Go WEBP library's decodings match +// the C WEBP library's. +package main // import "golang.org/x/image/cmd/webp-manual-test" + +import ( + "bytes" + "encoding/hex" + "flag" + "fmt" + "image" + "io" + "log" + "os" + "os/exec" + "path/filepath" + "sort" + "strings" + + "golang.org/x/image/webp" + "golang.org/x/image/webp/nycbcra" +) + +var ( + dwebp = flag.String("dwebp", "/usr/bin/dwebp", "path to the dwebp program "+ + "installed from https://developers.google.com/speed/webp/download") + testdata = flag.String("testdata", "", "path to the libwebp-test-data directory "+ + "checked out from https://chromium.googlesource.com/webm/libwebp-test-data") +) + +func main() { + flag.Parse() + if *dwebp == "" { + flag.Usage() + log.Fatal("dwebp flag was not specified") + } + if _, err := os.Stat(*dwebp); err != nil { + flag.Usage() + log.Fatalf("could not find dwebp program at %q", *dwebp) + } + if *testdata == "" { + flag.Usage() + log.Fatal("testdata flag was not specified") + } + + f, err := os.Open(*testdata) + if err != nil { + log.Fatalf("Open: %v", err) + } + defer f.Close() + names, err := f.Readdirnames(-1) + if err != nil { + log.Fatalf("Readdirnames: %v", err) + } + sort.Strings(names) + + nFail, nPass := 0, 0 + for _, name := range names { + if !strings.HasSuffix(name, "webp") { + continue + } + if err := test(name); err != nil { + fmt.Printf("FAIL\t%s\t%v\n", name, err) + nFail++ + } else { + fmt.Printf("PASS\t%s\n", name) + nPass++ + } + } + fmt.Printf("%d PASS, %d FAIL, %d TOTAL\n", nPass, nFail, nPass+nFail) + if nFail != 0 { + os.Exit(1) + } +} + +// test tests a single WEBP image. +func test(name string) error { + filename := filepath.Join(*testdata, name) + f, err := os.Open(filename) + if err != nil { + return fmt.Errorf("Open: %v", err) + } + defer f.Close() + + gotImage, err := webp.Decode(f) + if err != nil { + return fmt.Errorf("Decode: %v", err) + } + format, encode := "-pgm", encodePGM + if _, lossless := gotImage.(*image.NRGBA); lossless { + format, encode = "-pam", encodePAM + } + got, err := encode(gotImage) + if err != nil { + return fmt.Errorf("encode: %v", err) + } + + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + c := exec.Command(*dwebp, filename, format, "-o", "/dev/stdout") + c.Stdout = stdout + c.Stderr = stderr + if err := c.Run(); err != nil { + os.Stderr.Write(stderr.Bytes()) + return fmt.Errorf("executing dwebp: %v", err) + } + want := stdout.Bytes() + + if len(got) != len(want) { + return fmt.Errorf("encodings have different length: got %d, want %d", len(got), len(want)) + } + for i, g := range got { + if w := want[i]; g != w { + return fmt.Errorf("encodings differ at position 0x%x: got 0x%02x, want 0x%02x", i, g, w) + } + } + return nil +} + +// encodePAM encodes gotImage in the PAM format. +func encodePAM(gotImage image.Image) ([]byte, error) { + m, ok := gotImage.(*image.NRGBA) + if !ok { + return nil, fmt.Errorf("lossless image did not decode to an *image.NRGBA") + } + b := m.Bounds() + w, h := b.Dx(), b.Dy() + buf := new(bytes.Buffer) + fmt.Fprintf(buf, "P7\nWIDTH %d\nHEIGHT %d\nDEPTH 4\nMAXVAL 255\nTUPLTYPE RGB_ALPHA\nENDHDR\n", w, h) + for y := b.Min.Y; y < b.Max.Y; y++ { + o := m.PixOffset(b.Min.X, y) + buf.Write(m.Pix[o : o+4*w]) + } + return buf.Bytes(), nil +} + +// encodePGM encodes gotImage in the PGM format in the IMC4 layout. +func encodePGM(gotImage image.Image) ([]byte, error) { + var ( + m *image.YCbCr + ma *nycbcra.Image + ) + switch g := gotImage.(type) { + case *image.YCbCr: + m = g + case *nycbcra.Image: + m = &g.YCbCr + ma = g + default: + return nil, fmt.Errorf("lossy image did not decode to an *image.YCbCr") + } + if m.SubsampleRatio != image.YCbCrSubsampleRatio420 { + return nil, fmt.Errorf("lossy image did not decode to a 4:2:0 YCbCr") + } + b := m.Bounds() + w, h := b.Dx(), b.Dy() + w2, h2 := (w+1)/2, (h+1)/2 + outW, outH := 2*w2, h+h2 + if ma != nil { + outH += h + } + buf := new(bytes.Buffer) + fmt.Fprintf(buf, "P5\n%d %d\n255\n", outW, outH) + for y := b.Min.Y; y < b.Max.Y; y++ { + o := m.YOffset(b.Min.X, y) + buf.Write(m.Y[o : o+w]) + if w&1 != 0 { + buf.WriteByte(0x00) + } + } + for y := b.Min.Y; y < b.Max.Y; y += 2 { + o := m.COffset(b.Min.X, y) + buf.Write(m.Cb[o : o+w2]) + buf.Write(m.Cr[o : o+w2]) + } + if ma != nil { + for y := b.Min.Y; y < b.Max.Y; y++ { + o := ma.AOffset(b.Min.X, y) + buf.Write(ma.A[o : o+w]) + if w&1 != 0 { + buf.WriteByte(0x00) + } + } + } + return buf.Bytes(), nil +} + +// dump can be useful for debugging. +func dump(w io.Writer, b []byte) { + h := hex.Dumper(w) + h.Write(b) + h.Close() +} diff --git a/src/golang.org/x/image/codereview.cfg b/src/golang.org/x/image/codereview.cfg new file mode 100644 index 0000000000..3f8b14b64e --- /dev/null +++ b/src/golang.org/x/image/codereview.cfg @@ -0,0 +1 @@ +issuerepo: golang/go diff --git a/src/golang.org/x/image/draw/draw.go b/src/golang.org/x/image/draw/draw.go new file mode 100644 index 0000000000..b92e3c7f96 --- /dev/null +++ b/src/golang.org/x/image/draw/draw.go @@ -0,0 +1,79 @@ +// Copyright 2015 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. + +// Package draw provides image composition functions. +// +// See "The Go image/draw package" for an introduction to this package: +// http://golang.org/doc/articles/image_draw.html +// +// This package is a superset of and a drop-in replacement for the image/draw +// package in the standard library. +package draw + +// This file just contains the API exported by the image/draw package in the +// standard library. Other files in this package provide additional features. + +import ( + "image" + "image/color" + "image/draw" +) + +// Draw calls DrawMask with a nil mask. +func Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point, op Op) { + draw.Draw(dst, r, src, sp, draw.Op(op)) +} + +// DrawMask aligns r.Min in dst with sp in src and mp in mask and then +// replaces the rectangle r in dst with the result of a Porter-Duff +// composition. A nil mask is treated as opaque. +func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) { + draw.DrawMask(dst, r, src, sp, mask, mp, draw.Op(op)) +} + +// Drawer contains the Draw method. +type Drawer interface { + // Draw aligns r.Min in dst with sp in src and then replaces the + // rectangle r in dst with the result of drawing src on dst. + Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point) +} + +// FloydSteinberg is a Drawer that is the Src Op with Floyd-Steinberg error +// diffusion. +var FloydSteinberg Drawer = floydSteinberg{} + +type floydSteinberg struct{} + +func (floydSteinberg) Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point) { + draw.FloydSteinberg.Draw(dst, r, src, sp) +} + +// Image is an image.Image with a Set method to change a single pixel. +type Image interface { + image.Image + Set(x, y int, c color.Color) +} + +// Op is a Porter-Duff compositing operator. +type Op int + +const ( + // Over specifies ``(src in mask) over dst''. + Over Op = Op(draw.Over) + // Src specifies ``src in mask''. + Src Op = Op(draw.Src) +) + +// Draw implements the Drawer interface by calling the Draw function with +// this Op. +func (op Op) Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point) { + (draw.Op(op)).Draw(dst, r, src, sp) +} + +// Quantizer produces a palette for an image. +type Quantizer interface { + // Quantize appends up to cap(p) - len(p) colors to p and returns the + // updated palette suitable for converting m to a paletted image. + Quantize(p color.Palette, m image.Image) color.Palette +} diff --git a/src/golang.org/x/image/draw/example_test.go b/src/golang.org/x/image/draw/example_test.go new file mode 100644 index 0000000000..bcb46629cc --- /dev/null +++ b/src/golang.org/x/image/draw/example_test.go @@ -0,0 +1,118 @@ +// Copyright 2015 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. + +package draw_test + +import ( + "fmt" + "image" + "image/color" + "image/png" + "log" + "math" + "os" + + "golang.org/x/image/draw" + "golang.org/x/image/math/f64" +) + +func ExampleDraw() { + fSrc, err := os.Open("../testdata/blue-purple-pink.png") + if err != nil { + log.Fatal(err) + } + defer fSrc.Close() + src, err := png.Decode(fSrc) + if err != nil { + log.Fatal(err) + } + + dst := image.NewRGBA(image.Rect(0, 0, 400, 300)) + green := image.NewUniform(color.RGBA{0x00, 0x1f, 0x00, 0xff}) + draw.Copy(dst, image.Point{}, green, dst.Bounds(), draw.Src, nil) + qs := []draw.Interpolator{ + draw.NearestNeighbor, + draw.ApproxBiLinear, + draw.CatmullRom, + } + const cos60, sin60 = 0.5, 0.866025404 + t := f64.Aff3{ + +2 * cos60, -2 * sin60, 100, + +2 * sin60, +2 * cos60, 100, + } + + draw.Copy(dst, image.Point{20, 30}, src, src.Bounds(), draw.Over, nil) + for i, q := range qs { + q.Scale(dst, image.Rect(200+10*i, 100*i, 600+10*i, 150+100*i), src, src.Bounds(), draw.Over, nil) + } + draw.NearestNeighbor.Transform(dst, t, src, src.Bounds(), draw.Over, nil) + + red := image.NewNRGBA(image.Rect(0, 0, 16, 16)) + for y := 0; y < 16; y++ { + for x := 0; x < 16; x++ { + red.SetNRGBA(x, y, color.NRGBA{ + R: uint8(x * 0x11), + A: uint8(y * 0x11), + }) + } + } + red.SetNRGBA(0, 0, color.NRGBA{0xff, 0xff, 0x00, 0xff}) + red.SetNRGBA(15, 15, color.NRGBA{0xff, 0xff, 0x00, 0xff}) + + ops := []draw.Op{ + draw.Over, + draw.Src, + } + for i, op := range ops { + dr := image.Rect(120+10*i, 150+60*i, 170+10*i, 200+60*i) + draw.NearestNeighbor.Scale(dst, dr, red, red.Bounds(), op, nil) + t := f64.Aff3{ + +cos60, -sin60, float64(190 + 10*i), + +sin60, +cos60, float64(140 + 50*i), + } + draw.NearestNeighbor.Transform(dst, t, red, red.Bounds(), op, nil) + } + + dr := image.Rect(0, 0, 128, 128) + checkerboard := image.NewAlpha(dr) + for y := dr.Min.Y; y < dr.Max.Y; y++ { + for x := dr.Min.X; x < dr.Max.X; x++ { + if (x/20)%2 == (y/20)%2 { + checkerboard.SetAlpha(x, y, color.Alpha{0xff}) + } + } + } + sr := image.Rect(0, 0, 16, 16) + circle := image.NewAlpha(sr) + for y := sr.Min.Y; y < sr.Max.Y; y++ { + for x := sr.Min.X; x < sr.Max.X; x++ { + dx, dy := x-10, y-8 + if d := 32 * math.Sqrt(float64(dx*dx)+float64(dy*dy)); d < 0xff { + circle.SetAlpha(x, y, color.Alpha{0xff - uint8(d)}) + } + } + } + cyan := image.NewUniform(color.RGBA{0x00, 0xff, 0xff, 0xff}) + draw.NearestNeighbor.Scale(dst, dr, cyan, sr, draw.Over, &draw.Options{ + DstMask: checkerboard, + SrcMask: circle, + }) + + // Change false to true to write the resultant image to disk. + if false { + fDst, err := os.Create("out.png") + if err != nil { + log.Fatal(err) + } + defer fDst.Close() + err = png.Encode(fDst, dst) + if err != nil { + log.Fatal(err) + } + } + + fmt.Printf("dst has bounds %v.\n", dst.Bounds()) + // Output: + // dst has bounds (0,0)-(400,300). +} diff --git a/src/golang.org/x/image/draw/gen.go b/src/golang.org/x/image/draw/gen.go new file mode 100644 index 0000000000..0fed47437f --- /dev/null +++ b/src/golang.org/x/image/draw/gen.go @@ -0,0 +1,1403 @@ +// Copyright 2015 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 ignore + +package main + +import ( + "bytes" + "flag" + "fmt" + "go/format" + "io/ioutil" + "log" + "os" + "strings" +) + +var debug = flag.Bool("debug", false, "") + +func main() { + flag.Parse() + + w := new(bytes.Buffer) + w.WriteString("// generated by \"go run gen.go\". DO NOT EDIT.\n\n" + + "package draw\n\nimport (\n" + + "\"image\"\n" + + "\"image/color\"\n" + + "\"math\"\n" + + "\n" + + "\"golang.org/x/image/math/f64\"\n" + + ")\n") + + gen(w, "nnInterpolator", codeNNScaleLeaf, codeNNTransformLeaf) + gen(w, "ablInterpolator", codeABLScaleLeaf, codeABLTransformLeaf) + genKernel(w) + + if *debug { + os.Stdout.Write(w.Bytes()) + return + } + out, err := format.Source(w.Bytes()) + if err != nil { + log.Fatal(err) + } + if err := ioutil.WriteFile("impl.go", out, 0660); err != nil { + log.Fatal(err) + } +} + +var ( + // dsTypes are the (dst image type, src image type) pairs to generate + // scale_DType_SType implementations for. The last element in the slice + // should be the fallback pair ("Image", "image.Image"). + // + // TODO: add *image.CMYK src type after Go 1.5 is released. + // An *image.CMYK is also alwaysOpaque. + dsTypes = []struct{ dType, sType string }{ + {"*image.RGBA", "*image.Gray"}, + {"*image.RGBA", "*image.NRGBA"}, + {"*image.RGBA", "*image.RGBA"}, + {"*image.RGBA", "*image.YCbCr"}, + {"*image.RGBA", "image.Image"}, + {"Image", "image.Image"}, + } + dTypes, sTypes []string + sTypesForDType = map[string][]string{} + subsampleRatios = []string{ + "444", + "422", + "420", + "440", + } + ops = []string{"Over", "Src"} + // alwaysOpaque are those image.Image implementations that are always + // opaque. For these types, Over is equivalent to the faster Src, in the + // absence of a source mask. + alwaysOpaque = map[string]bool{ + "*image.Gray": true, + "*image.YCbCr": true, + } +) + +func init() { + dTypesSeen := map[string]bool{} + sTypesSeen := map[string]bool{} + for _, t := range dsTypes { + if !sTypesSeen[t.sType] { + sTypesSeen[t.sType] = true + sTypes = append(sTypes, t.sType) + } + if !dTypesSeen[t.dType] { + dTypesSeen[t.dType] = true + dTypes = append(dTypes, t.dType) + } + sTypesForDType[t.dType] = append(sTypesForDType[t.dType], t.sType) + } + sTypesForDType["anyDType"] = sTypes +} + +type data struct { + dType string + sType string + sratio string + receiver string + op string +} + +func gen(w *bytes.Buffer, receiver string, codes ...string) { + expn(w, codeRoot, &data{receiver: receiver}) + for _, code := range codes { + for _, t := range dsTypes { + for _, op := range ops { + if op == "Over" && alwaysOpaque[t.sType] { + continue + } + expn(w, code, &data{ + dType: t.dType, + sType: t.sType, + receiver: receiver, + op: op, + }) + } + } + } +} + +func genKernel(w *bytes.Buffer) { + expn(w, codeKernelRoot, &data{}) + for _, sType := range sTypes { + expn(w, codeKernelScaleLeafX, &data{ + sType: sType, + }) + } + for _, dType := range dTypes { + for _, op := range ops { + expn(w, codeKernelScaleLeafY, &data{ + dType: dType, + op: op, + }) + } + } + for _, t := range dsTypes { + for _, op := range ops { + if op == "Over" && alwaysOpaque[t.sType] { + continue + } + expn(w, codeKernelTransformLeaf, &data{ + dType: t.dType, + sType: t.sType, + op: op, + }) + } + } +} + +func expn(w *bytes.Buffer, code string, d *data) { + if d.sType == "*image.YCbCr" && d.sratio == "" { + for _, sratio := range subsampleRatios { + e := *d + e.sratio = sratio + expn(w, code, &e) + } + return + } + + for _, line := range strings.Split(code, "\n") { + line = expnLine(line, d) + if line == ";" { + continue + } + fmt.Fprintln(w, line) + } +} + +func expnLine(line string, d *data) string { + for { + i := strings.IndexByte(line, '$') + if i < 0 { + break + } + prefix, s := line[:i], line[i+1:] + + i = len(s) + for j, c := range s { + if !('A' <= c && c <= 'Z' || 'a' <= c && c <= 'z') { + i = j + break + } + } + dollar, suffix := s[:i], s[i:] + + e := expnDollar(prefix, dollar, suffix, d) + if e == "" { + log.Fatalf("couldn't expand %q", line) + } + line = e + } + return line +} + +// expnDollar expands a "$foo" fragment in a line of generated code. It returns +// the empty string if there was a problem. It returns ";" if the generated +// code is a no-op. +func expnDollar(prefix, dollar, suffix string, d *data) string { + switch dollar { + case "dType": + return prefix + d.dType + suffix + case "dTypeRN": + return prefix + relName(d.dType) + suffix + case "sratio": + return prefix + d.sratio + suffix + case "sType": + return prefix + d.sType + suffix + case "sTypeRN": + return prefix + relName(d.sType) + suffix + case "receiver": + return prefix + d.receiver + suffix + case "op": + return prefix + d.op + suffix + + case "switch": + return expnSwitch("", "", true, suffix) + case "switchD": + return expnSwitch("", "", false, suffix) + case "switchS": + return expnSwitch("", "anyDType", false, suffix) + + case "preOuter": + switch d.dType { + default: + return ";" + case "Image": + s := "" + if d.sType == "image.Image" { + s = "srcMask, smp := opts.SrcMask, opts.SrcMaskP\n" + } + return s + + "dstMask, dmp := opts.DstMask, opts.DstMaskP\n" + + "dstColorRGBA64 := &color.RGBA64{}\n" + + "dstColor := color.Color(dstColorRGBA64)" + } + + case "preInner": + switch d.dType { + default: + return ";" + case "*image.RGBA": + return "d := " + pixOffset("dst", "dr.Min.X+adr.Min.X", "dr.Min.Y+int(dy)", "*4", "*dst.Stride") + } + + case "preKernelOuter": + switch d.sType { + default: + return ";" + case "image.Image": + return "srcMask, smp := opts.SrcMask, opts.SrcMaskP" + } + + case "preKernelInner": + switch d.dType { + default: + return ";" + case "*image.RGBA": + return "d := " + pixOffset("dst", "dr.Min.X+int(dx)", "dr.Min.Y+adr.Min.Y", "*4", "*dst.Stride") + } + + case "blend": + args, _ := splitArgs(suffix) + if len(args) != 4 { + return "" + } + switch d.sType { + default: + return argf(args, ""+ + "$3r = $0*$1r + $2*$3r\n"+ + "$3g = $0*$1g + $2*$3g\n"+ + "$3b = $0*$1b + $2*$3b\n"+ + "$3a = $0*$1a + $2*$3a", + ) + case "*image.Gray": + return argf(args, ""+ + "$3r = $0*$1r + $2*$3r", + ) + case "*image.YCbCr": + return argf(args, ""+ + "$3r = $0*$1r + $2*$3r\n"+ + "$3g = $0*$1g + $2*$3g\n"+ + "$3b = $0*$1b + $2*$3b", + ) + } + + case "clampToAlpha": + if alwaysOpaque[d.sType] { + return ";" + } + // Go uses alpha-premultiplied color. The naive computation can lead to + // invalid colors, e.g. red > alpha, when some weights are negative. + return ` + if pr > pa { + pr = pa + } + if pg > pa { + pg = pa + } + if pb > pa { + pb = pa + } + ` + + case "convFtou": + args, _ := splitArgs(suffix) + if len(args) != 2 { + return "" + } + + switch d.sType { + default: + return argf(args, ""+ + "$0r := uint32($1r)\n"+ + "$0g := uint32($1g)\n"+ + "$0b := uint32($1b)\n"+ + "$0a := uint32($1a)", + ) + case "*image.Gray": + return argf(args, ""+ + "$0r := uint32($1r)", + ) + case "*image.YCbCr": + return argf(args, ""+ + "$0r := uint32($1r)\n"+ + "$0g := uint32($1g)\n"+ + "$0b := uint32($1b)", + ) + } + + case "outputu": + args, _ := splitArgs(suffix) + if len(args) != 3 { + return "" + } + + switch d.op { + case "Over": + switch d.dType { + default: + log.Fatalf("bad dType %q", d.dType) + case "Image": + return argf(args, ""+ + "qr, qg, qb, qa := dst.At($0, $1).RGBA()\n"+ + "if dstMask != nil {\n"+ + " _, _, _, ma := dstMask.At(dmp.X + $0, dmp.Y + $1).RGBA()\n"+ + " $2r = $2r * ma / 0xffff\n"+ + " $2g = $2g * ma / 0xffff\n"+ + " $2b = $2b * ma / 0xffff\n"+ + " $2a = $2a * ma / 0xffff\n"+ + "}\n"+ + "$2a1 := 0xffff - $2a\n"+ + "dstColorRGBA64.R = uint16(qr*$2a1/0xffff + $2r)\n"+ + "dstColorRGBA64.G = uint16(qg*$2a1/0xffff + $2g)\n"+ + "dstColorRGBA64.B = uint16(qb*$2a1/0xffff + $2b)\n"+ + "dstColorRGBA64.A = uint16(qa*$2a1/0xffff + $2a)\n"+ + "dst.Set($0, $1, dstColor)", + ) + case "*image.RGBA": + return argf(args, ""+ + "$2a1 := (0xffff - $2a) * 0x101\n"+ + "dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*$2a1/0xffff + $2r) >> 8)\n"+ + "dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*$2a1/0xffff + $2g) >> 8)\n"+ + "dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*$2a1/0xffff + $2b) >> 8)\n"+ + "dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*$2a1/0xffff + $2a) >> 8)", + ) + } + + case "Src": + switch d.dType { + default: + log.Fatalf("bad dType %q", d.dType) + case "Image": + return argf(args, ""+ + "if dstMask != nil {\n"+ + " qr, qg, qb, qa := dst.At($0, $1).RGBA()\n"+ + " _, _, _, ma := dstMask.At(dmp.X + $0, dmp.Y + $1).RGBA()\n"+ + " pr = pr * ma / 0xffff\n"+ + " pg = pg * ma / 0xffff\n"+ + " pb = pb * ma / 0xffff\n"+ + " pa = pa * ma / 0xffff\n"+ + " $2a1 := 0xffff - ma\n"+ // Note that this is ma, not $2a. + " dstColorRGBA64.R = uint16(qr*$2a1/0xffff + $2r)\n"+ + " dstColorRGBA64.G = uint16(qg*$2a1/0xffff + $2g)\n"+ + " dstColorRGBA64.B = uint16(qb*$2a1/0xffff + $2b)\n"+ + " dstColorRGBA64.A = uint16(qa*$2a1/0xffff + $2a)\n"+ + " dst.Set($0, $1, dstColor)\n"+ + "} else {\n"+ + " dstColorRGBA64.R = uint16($2r)\n"+ + " dstColorRGBA64.G = uint16($2g)\n"+ + " dstColorRGBA64.B = uint16($2b)\n"+ + " dstColorRGBA64.A = uint16($2a)\n"+ + " dst.Set($0, $1, dstColor)\n"+ + "}", + ) + case "*image.RGBA": + switch d.sType { + default: + return argf(args, ""+ + "dst.Pix[d+0] = uint8($2r >> 8)\n"+ + "dst.Pix[d+1] = uint8($2g >> 8)\n"+ + "dst.Pix[d+2] = uint8($2b >> 8)\n"+ + "dst.Pix[d+3] = uint8($2a >> 8)", + ) + case "*image.Gray": + return argf(args, ""+ + "out := uint8($2r >> 8)\n"+ + "dst.Pix[d+0] = out\n"+ + "dst.Pix[d+1] = out\n"+ + "dst.Pix[d+2] = out\n"+ + "dst.Pix[d+3] = 0xff", + ) + case "*image.YCbCr": + return argf(args, ""+ + "dst.Pix[d+0] = uint8($2r >> 8)\n"+ + "dst.Pix[d+1] = uint8($2g >> 8)\n"+ + "dst.Pix[d+2] = uint8($2b >> 8)\n"+ + "dst.Pix[d+3] = 0xff", + ) + } + } + } + + case "outputf": + args, _ := splitArgs(suffix) + if len(args) != 5 { + return "" + } + ret := "" + + switch d.op { + case "Over": + switch d.dType { + default: + log.Fatalf("bad dType %q", d.dType) + case "Image": + ret = argf(args, ""+ + "qr, qg, qb, qa := dst.At($0, $1).RGBA()\n"+ + "$3r0 := uint32($2($3r * $4))\n"+ + "$3g0 := uint32($2($3g * $4))\n"+ + "$3b0 := uint32($2($3b * $4))\n"+ + "$3a0 := uint32($2($3a * $4))\n"+ + "if dstMask != nil {\n"+ + " _, _, _, ma := dstMask.At(dmp.X + $0, dmp.Y + $1).RGBA()\n"+ + " $3r0 = $3r0 * ma / 0xffff\n"+ + " $3g0 = $3g0 * ma / 0xffff\n"+ + " $3b0 = $3b0 * ma / 0xffff\n"+ + " $3a0 = $3a0 * ma / 0xffff\n"+ + "}\n"+ + "$3a1 := 0xffff - $3a0\n"+ + "dstColorRGBA64.R = uint16(qr*$3a1/0xffff + $3r0)\n"+ + "dstColorRGBA64.G = uint16(qg*$3a1/0xffff + $3g0)\n"+ + "dstColorRGBA64.B = uint16(qb*$3a1/0xffff + $3b0)\n"+ + "dstColorRGBA64.A = uint16(qa*$3a1/0xffff + $3a0)\n"+ + "dst.Set($0, $1, dstColor)", + ) + case "*image.RGBA": + ret = argf(args, ""+ + "$3r0 := uint32($2($3r * $4))\n"+ + "$3g0 := uint32($2($3g * $4))\n"+ + "$3b0 := uint32($2($3b * $4))\n"+ + "$3a0 := uint32($2($3a * $4))\n"+ + "$3a1 := (0xffff - uint32($3a0)) * 0x101\n"+ + "dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*$3a1/0xffff + $3r0) >> 8)\n"+ + "dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*$3a1/0xffff + $3g0) >> 8)\n"+ + "dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*$3a1/0xffff + $3b0) >> 8)\n"+ + "dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*$3a1/0xffff + $3a0) >> 8)", + ) + } + + case "Src": + switch d.dType { + default: + log.Fatalf("bad dType %q", d.dType) + case "Image": + ret = argf(args, ""+ + "if dstMask != nil {\n"+ + " qr, qg, qb, qa := dst.At($0, $1).RGBA()\n"+ + " _, _, _, ma := dstMask.At(dmp.X + $0, dmp.Y + $1).RGBA()\n"+ + " pr := uint32($2($3r * $4)) * ma / 0xffff\n"+ + " pg := uint32($2($3g * $4)) * ma / 0xffff\n"+ + " pb := uint32($2($3b * $4)) * ma / 0xffff\n"+ + " pa := uint32($2($3a * $4)) * ma / 0xffff\n"+ + " pa1 := 0xffff - ma\n"+ // Note that this is ma, not pa. + " dstColorRGBA64.R = uint16(qr*pa1/0xffff + pr)\n"+ + " dstColorRGBA64.G = uint16(qg*pa1/0xffff + pg)\n"+ + " dstColorRGBA64.B = uint16(qb*pa1/0xffff + pb)\n"+ + " dstColorRGBA64.A = uint16(qa*pa1/0xffff + pa)\n"+ + " dst.Set($0, $1, dstColor)\n"+ + "} else {\n"+ + " dstColorRGBA64.R = $2($3r * $4)\n"+ + " dstColorRGBA64.G = $2($3g * $4)\n"+ + " dstColorRGBA64.B = $2($3b * $4)\n"+ + " dstColorRGBA64.A = $2($3a * $4)\n"+ + " dst.Set($0, $1, dstColor)\n"+ + "}", + ) + case "*image.RGBA": + switch d.sType { + default: + ret = argf(args, ""+ + "dst.Pix[d+0] = uint8($2($3r * $4) >> 8)\n"+ + "dst.Pix[d+1] = uint8($2($3g * $4) >> 8)\n"+ + "dst.Pix[d+2] = uint8($2($3b * $4) >> 8)\n"+ + "dst.Pix[d+3] = uint8($2($3a * $4) >> 8)", + ) + case "*image.Gray": + ret = argf(args, ""+ + "out := uint8($2($3r * $4) >> 8)\n"+ + "dst.Pix[d+0] = out\n"+ + "dst.Pix[d+1] = out\n"+ + "dst.Pix[d+2] = out\n"+ + "dst.Pix[d+3] = 0xff", + ) + case "*image.YCbCr": + ret = argf(args, ""+ + "dst.Pix[d+0] = uint8($2($3r * $4) >> 8)\n"+ + "dst.Pix[d+1] = uint8($2($3g * $4) >> 8)\n"+ + "dst.Pix[d+2] = uint8($2($3b * $4) >> 8)\n"+ + "dst.Pix[d+3] = 0xff", + ) + } + } + } + + return strings.Replace(ret, " * 1)", ")", -1) + + case "srcf", "srcu": + lhs, eqOp := splitEq(prefix) + if lhs == "" { + return "" + } + args, extra := splitArgs(suffix) + if len(args) != 2 { + return "" + } + + tmp := "" + if dollar == "srcf" { + tmp = "u" + } + + // TODO: there's no need to multiply by 0x101 in the switch below if + // the next thing we're going to do is shift right by 8. + + buf := new(bytes.Buffer) + switch d.sType { + default: + log.Fatalf("bad sType %q", d.sType) + case "image.Image": + fmt.Fprintf(buf, ""+ + "%sr%s, %sg%s, %sb%s, %sa%s := src.At(%s, %s).RGBA()\n", + lhs, tmp, lhs, tmp, lhs, tmp, lhs, tmp, args[0], args[1], + ) + if d.dType == "" || d.dType == "Image" { + fmt.Fprintf(buf, ""+ + "if srcMask != nil {\n"+ + " _, _, _, ma := srcMask.At(smp.X+%s, smp.Y+%s).RGBA()\n"+ + " %sr%s = %sr%s * ma / 0xffff\n"+ + " %sg%s = %sg%s * ma / 0xffff\n"+ + " %sb%s = %sb%s * ma / 0xffff\n"+ + " %sa%s = %sa%s * ma / 0xffff\n"+ + "}\n", + args[0], args[1], + lhs, tmp, lhs, tmp, + lhs, tmp, lhs, tmp, + lhs, tmp, lhs, tmp, + lhs, tmp, lhs, tmp, + ) + } + case "*image.Gray": + fmt.Fprintf(buf, ""+ + "%si := %s\n"+ + "%sr%s := uint32(src.Pix[%si]) * 0x101\n", + lhs, pixOffset("src", args[0], args[1], "", "*src.Stride"), + lhs, tmp, lhs, + ) + case "*image.NRGBA": + fmt.Fprintf(buf, ""+ + "%si := %s\n"+ + "%sa%s := uint32(src.Pix[%si+3]) * 0x101\n"+ + "%sr%s := uint32(src.Pix[%si+0]) * %sa%s / 0xff\n"+ + "%sg%s := uint32(src.Pix[%si+1]) * %sa%s / 0xff\n"+ + "%sb%s := uint32(src.Pix[%si+2]) * %sa%s / 0xff\n", + lhs, pixOffset("src", args[0], args[1], "*4", "*src.Stride"), + lhs, tmp, lhs, + lhs, tmp, lhs, lhs, tmp, + lhs, tmp, lhs, lhs, tmp, + lhs, tmp, lhs, lhs, tmp, + ) + case "*image.RGBA": + fmt.Fprintf(buf, ""+ + "%si := %s\n"+ + "%sr%s := uint32(src.Pix[%si+0]) * 0x101\n"+ + "%sg%s := uint32(src.Pix[%si+1]) * 0x101\n"+ + "%sb%s := uint32(src.Pix[%si+2]) * 0x101\n"+ + "%sa%s := uint32(src.Pix[%si+3]) * 0x101\n", + lhs, pixOffset("src", args[0], args[1], "*4", "*src.Stride"), + lhs, tmp, lhs, + lhs, tmp, lhs, + lhs, tmp, lhs, + lhs, tmp, lhs, + ) + case "*image.YCbCr": + fmt.Fprintf(buf, ""+ + "%si := %s\n"+ + "%sj := %s\n"+ + "%s\n", + lhs, pixOffset("src", args[0], args[1], "", "*src.YStride"), + lhs, cOffset(args[0], args[1], d.sratio), + ycbcrToRGB(lhs, tmp), + ) + } + + if dollar == "srcf" { + switch d.sType { + default: + fmt.Fprintf(buf, ""+ + "%sr %s float64(%sru)%s\n"+ + "%sg %s float64(%sgu)%s\n"+ + "%sb %s float64(%sbu)%s\n"+ + "%sa %s float64(%sau)%s\n", + lhs, eqOp, lhs, extra, + lhs, eqOp, lhs, extra, + lhs, eqOp, lhs, extra, + lhs, eqOp, lhs, extra, + ) + case "*image.Gray": + fmt.Fprintf(buf, ""+ + "%sr %s float64(%sru)%s\n", + lhs, eqOp, lhs, extra, + ) + case "*image.YCbCr": + fmt.Fprintf(buf, ""+ + "%sr %s float64(%sru)%s\n"+ + "%sg %s float64(%sgu)%s\n"+ + "%sb %s float64(%sbu)%s\n", + lhs, eqOp, lhs, extra, + lhs, eqOp, lhs, extra, + lhs, eqOp, lhs, extra, + ) + } + } + + return strings.TrimSpace(buf.String()) + + case "tweakD": + if d.dType == "*image.RGBA" { + return "d += dst.Stride" + } + return ";" + + case "tweakDx": + if d.dType == "*image.RGBA" { + return strings.Replace(prefix, "dx++", "dx, d = dx+1, d+4", 1) + } + return prefix + + case "tweakDy": + if d.dType == "*image.RGBA" { + return strings.Replace(prefix, "for dy, s", "for _, s", 1) + } + return prefix + + case "tweakP": + switch d.sType { + case "*image.Gray": + if strings.HasPrefix(strings.TrimSpace(prefix), "pa * ") { + return "1," + } + return "pr," + case "*image.YCbCr": + if strings.HasPrefix(strings.TrimSpace(prefix), "pa * ") { + return "1," + } + } + return prefix + + case "tweakPr": + if d.sType == "*image.Gray" { + return "pr *= s.invTotalWeightFFFF" + } + return ";" + + case "tweakVarP": + switch d.sType { + case "*image.Gray": + return strings.Replace(prefix, "var pr, pg, pb, pa", "var pr", 1) + case "*image.YCbCr": + return strings.Replace(prefix, "var pr, pg, pb, pa", "var pr, pg, pb", 1) + } + return prefix + } + return "" +} + +func expnSwitch(op, dType string, expandBoth bool, template string) string { + if op == "" && dType != "anyDType" { + lines := []string{"switch op {"} + for _, op = range ops { + lines = append(lines, + fmt.Sprintf("case %s:", op), + expnSwitch(op, dType, expandBoth, template), + ) + } + lines = append(lines, "}") + return strings.Join(lines, "\n") + } + + switchVar := "dst" + if dType != "" { + switchVar = "src" + } + lines := []string{fmt.Sprintf("switch %s := %s.(type) {", switchVar, switchVar)} + + fallback, values := "Image", dTypes + if dType != "" { + fallback, values = "image.Image", sTypesForDType[dType] + } + for _, v := range values { + if dType != "" { + // v is the sType. Skip those always-opaque sTypes, where Over is + // equivalent to Src. + if op == "Over" && alwaysOpaque[v] { + continue + } + } + + if v == fallback { + lines = append(lines, "default:") + } else { + lines = append(lines, fmt.Sprintf("case %s:", v)) + } + + if dType != "" { + if v == "*image.YCbCr" { + lines = append(lines, expnSwitchYCbCr(op, dType, template)) + } else { + lines = append(lines, expnLine(template, &data{dType: dType, sType: v, op: op})) + } + } else if !expandBoth { + lines = append(lines, expnLine(template, &data{dType: v, op: op})) + } else { + lines = append(lines, expnSwitch(op, v, false, template)) + } + } + + lines = append(lines, "}") + return strings.Join(lines, "\n") +} + +func expnSwitchYCbCr(op, dType, template string) string { + lines := []string{ + "switch src.SubsampleRatio {", + "default:", + expnLine(template, &data{dType: dType, sType: "image.Image", op: op}), + } + for _, sratio := range subsampleRatios { + lines = append(lines, + fmt.Sprintf("case image.YCbCrSubsampleRatio%s:", sratio), + expnLine(template, &data{dType: dType, sType: "*image.YCbCr", sratio: sratio, op: op}), + ) + } + lines = append(lines, "}") + return strings.Join(lines, "\n") +} + +func argf(args []string, s string) string { + if len(args) > 9 { + panic("too many args") + } + for i, a := range args { + old := fmt.Sprintf("$%d", i) + s = strings.Replace(s, old, a, -1) + } + return s +} + +func pixOffset(m, x, y, xstride, ystride string) string { + return fmt.Sprintf("(%s-%s.Rect.Min.Y)%s + (%s-%s.Rect.Min.X)%s", y, m, ystride, x, m, xstride) +} + +func cOffset(x, y, sratio string) string { + switch sratio { + case "444": + return fmt.Sprintf("( %s - src.Rect.Min.Y )*src.CStride + ( %s - src.Rect.Min.X )", y, x) + case "422": + return fmt.Sprintf("( %s - src.Rect.Min.Y )*src.CStride + ((%s)/2 - src.Rect.Min.X/2)", y, x) + case "420": + return fmt.Sprintf("((%s)/2 - src.Rect.Min.Y/2)*src.CStride + ((%s)/2 - src.Rect.Min.X/2)", y, x) + case "440": + return fmt.Sprintf("((%s)/2 - src.Rect.Min.Y/2)*src.CStride + ( %s - src.Rect.Min.X )", y, x) + } + return fmt.Sprintf("unsupported sratio %q", sratio) +} + +func ycbcrToRGB(lhs, tmp string) string { + s := ` + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + $yy1 := int(src.Y[$i]) * 0x10100 + $cb1 := int(src.Cb[$j]) - 128 + $cr1 := int(src.Cr[$j]) - 128 + $r@ := ($yy1 + 91881*$cr1) >> 8 + $g@ := ($yy1 - 22554*$cb1 - 46802*$cr1) >> 8 + $b@ := ($yy1 + 116130*$cb1) >> 8 + if $r@ < 0 { + $r@ = 0 + } else if $r@ > 0xffff { + $r@ = 0xffff + } + if $g@ < 0 { + $g@ = 0 + } else if $g@ > 0xffff { + $g@ = 0xffff + } + if $b@ < 0 { + $b@ = 0 + } else if $b@ > 0xffff { + $b@ = 0xffff + } + ` + s = strings.Replace(s, "$", lhs, -1) + s = strings.Replace(s, "@", tmp, -1) + return s +} + +func split(s, sep string) (string, string) { + if i := strings.Index(s, sep); i >= 0 { + return strings.TrimSpace(s[:i]), strings.TrimSpace(s[i+len(sep):]) + } + return "", "" +} + +func splitEq(s string) (lhs, eqOp string) { + s = strings.TrimSpace(s) + if lhs, _ = split(s, ":="); lhs != "" { + return lhs, ":=" + } + if lhs, _ = split(s, "+="); lhs != "" { + return lhs, "+=" + } + return "", "" +} + +func splitArgs(s string) (args []string, extra string) { + s = strings.TrimSpace(s) + if s == "" || s[0] != '[' { + return nil, "" + } + s = s[1:] + + i := strings.IndexByte(s, ']') + if i < 0 { + return nil, "" + } + args, extra = strings.Split(s[:i], ","), s[i+1:] + for i := range args { + args[i] = strings.TrimSpace(args[i]) + } + return args, extra +} + +func relName(s string) string { + if i := strings.LastIndex(s, "."); i >= 0 { + return s[i+1:] + } + return s +} + +const ( + codeRoot = ` + func (z $receiver) Scale(dst Image, dr image.Rectangle, src image.Image, sr image.Rectangle, op Op, opts *Options) { + // Try to simplify a Scale to a Copy. + if dr.Size() == sr.Size() { + Copy(dst, dr.Min, src, sr, op, opts) + return + } + + var o Options + if opts != nil { + o = *opts + } + + // adr is the affected destination pixels. + adr := dst.Bounds().Intersect(dr) + adr, o.DstMask = clipAffectedDestRect(adr, o.DstMask, o.DstMaskP) + if adr.Empty() || sr.Empty() { + return + } + // Make adr relative to dr.Min. + adr = adr.Sub(dr.Min) + if op == Over && o.SrcMask == nil && opaque(src) { + op = Src + } + + // sr is the source pixels. If it extends beyond the src bounds, + // we cannot use the type-specific fast paths, as they access + // the Pix fields directly without bounds checking. + // + // Similarly, the fast paths assume that the masks are nil. + if o.DstMask != nil || o.SrcMask != nil || !sr.In(src.Bounds()) { + switch op { + case Over: + z.scale_Image_Image_Over(dst, dr, adr, src, sr, &o) + case Src: + z.scale_Image_Image_Src(dst, dr, adr, src, sr, &o) + } + } else if _, ok := src.(*image.Uniform); ok { + Draw(dst, dr, src, src.Bounds().Min, op) + } else { + $switch z.scale_$dTypeRN_$sTypeRN$sratio_$op(dst, dr, adr, src, sr, &o) + } + } + + func (z $receiver) Transform(dst Image, s2d f64.Aff3, src image.Image, sr image.Rectangle, op Op, opts *Options) { + // Try to simplify a Transform to a Copy. + if s2d[0] == 1 && s2d[1] == 0 && s2d[3] == 0 && s2d[4] == 1 { + dx := int(s2d[2]) + dy := int(s2d[5]) + if float64(dx) == s2d[2] && float64(dy) == s2d[5] { + Copy(dst, image.Point{X: sr.Min.X + dx, Y: sr.Min.X + dy}, src, sr, op, opts) + return + } + } + + var o Options + if opts != nil { + o = *opts + } + + dr := transformRect(&s2d, &sr) + // adr is the affected destination pixels. + adr := dst.Bounds().Intersect(dr) + adr, o.DstMask = clipAffectedDestRect(adr, o.DstMask, o.DstMaskP) + if adr.Empty() || sr.Empty() { + return + } + if op == Over && o.SrcMask == nil && opaque(src) { + op = Src + } + + d2s := invert(&s2d) + // bias is a translation of the mapping from dst coordinates to src + // coordinates such that the latter temporarily have non-negative X + // and Y coordinates. This allows us to write int(f) instead of + // int(math.Floor(f)), since "round to zero" and "round down" are + // equivalent when f >= 0, but the former is much cheaper. The X-- + // and Y-- are because the TransformLeaf methods have a "sx -= 0.5" + // adjustment. + bias := transformRect(&d2s, &adr).Min + bias.X-- + bias.Y-- + d2s[2] -= float64(bias.X) + d2s[5] -= float64(bias.Y) + // Make adr relative to dr.Min. + adr = adr.Sub(dr.Min) + // sr is the source pixels. If it extends beyond the src bounds, + // we cannot use the type-specific fast paths, as they access + // the Pix fields directly without bounds checking. + // + // Similarly, the fast paths assume that the masks are nil. + if o.DstMask != nil || o.SrcMask != nil || !sr.In(src.Bounds()) { + switch op { + case Over: + z.transform_Image_Image_Over(dst, dr, adr, &d2s, src, sr, bias, &o) + case Src: + z.transform_Image_Image_Src(dst, dr, adr, &d2s, src, sr, bias, &o) + } + } else if u, ok := src.(*image.Uniform); ok { + transform_Uniform(dst, dr, adr, &d2s, u, sr, bias, op) + } else { + $switch z.transform_$dTypeRN_$sTypeRN$sratio_$op(dst, dr, adr, &d2s, src, sr, bias, &o) + } + } + ` + + codeNNScaleLeaf = ` + func (nnInterpolator) scale_$dTypeRN_$sTypeRN$sratio_$op(dst $dType, dr, adr image.Rectangle, src $sType, sr image.Rectangle, opts *Options) { + dw2 := uint64(dr.Dx()) * 2 + dh2 := uint64(dr.Dy()) * 2 + sw := uint64(sr.Dx()) + sh := uint64(sr.Dy()) + $preOuter + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (2*uint64(dy) + 1) * sh / dh2 + $preInner + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { $tweakDx + sx := (2*uint64(dx) + 1) * sw / dw2 + p := $srcu[sr.Min.X + int(sx), sr.Min.Y + int(sy)] + $outputu[dr.Min.X + int(dx), dr.Min.Y + int(dy), p] + } + } + } + ` + + codeNNTransformLeaf = ` + func (nnInterpolator) transform_$dTypeRN_$sTypeRN$sratio_$op(dst $dType, dr, adr image.Rectangle, d2s *f64.Aff3, src $sType, sr image.Rectangle, bias image.Point, opts *Options) { + $preOuter + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y + int(dy)) + 0.5 + $preInner + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { $tweakDx + dxf := float64(dr.Min.X + int(dx)) + 0.5 + sx0 := int(d2s[0]*dxf + d2s[1]*dyf + d2s[2]) + bias.X + sy0 := int(d2s[3]*dxf + d2s[4]*dyf + d2s[5]) + bias.Y + if !(image.Point{sx0, sy0}).In(sr) { + continue + } + p := $srcu[sx0, sy0] + $outputu[dr.Min.X + int(dx), dr.Min.Y + int(dy), p] + } + } + } + ` + + codeABLScaleLeaf = ` + func (ablInterpolator) scale_$dTypeRN_$sTypeRN$sratio_$op(dst $dType, dr, adr image.Rectangle, src $sType, sr image.Rectangle, opts *Options) { + sw := int32(sr.Dx()) + sh := int32(sr.Dy()) + yscale := float64(sh) / float64(dr.Dy()) + xscale := float64(sw) / float64(dr.Dx()) + swMinus1, shMinus1 := sw - 1, sh - 1 + $preOuter + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (float64(dy)+0.5)*yscale - 0.5 + // If sy < 0, we will clamp sy0 to 0 anyway, so it doesn't matter if + // we say int32(sy) instead of int32(math.Floor(sy)). Similarly for + // sx, below. + sy0 := int32(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy1 := sy0 + 1 + if sy < 0 { + sy0, sy1 = 0, 0 + yFrac0, yFrac1 = 0, 1 + } else if sy1 > shMinus1 { + sy0, sy1 = shMinus1, shMinus1 + yFrac0, yFrac1 = 1, 0 + } + $preInner + + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { $tweakDx + sx := (float64(dx)+0.5)*xscale - 0.5 + sx0 := int32(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx1 := sx0 + 1 + if sx < 0 { + sx0, sx1 = 0, 0 + xFrac0, xFrac1 = 0, 1 + } else if sx1 > swMinus1 { + sx0, sx1 = swMinus1, swMinus1 + xFrac0, xFrac1 = 1, 0 + } + + s00 := $srcf[sr.Min.X + int(sx0), sr.Min.Y + int(sy0)] + s10 := $srcf[sr.Min.X + int(sx1), sr.Min.Y + int(sy0)] + $blend[xFrac1, s00, xFrac0, s10] + s01 := $srcf[sr.Min.X + int(sx0), sr.Min.Y + int(sy1)] + s11 := $srcf[sr.Min.X + int(sx1), sr.Min.Y + int(sy1)] + $blend[xFrac1, s01, xFrac0, s11] + $blend[yFrac1, s10, yFrac0, s11] + $convFtou[p, s11] + $outputu[dr.Min.X + int(dx), dr.Min.Y + int(dy), p] + } + } + } + ` + + codeABLTransformLeaf = ` + func (ablInterpolator) transform_$dTypeRN_$sTypeRN$sratio_$op(dst $dType, dr, adr image.Rectangle, d2s *f64.Aff3, src $sType, sr image.Rectangle, bias image.Point, opts *Options) { + $preOuter + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y + int(dy)) + 0.5 + $preInner + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { $tweakDx + dxf := float64(dr.Min.X + int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + sx -= 0.5 + sx0 := int(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx0 += bias.X + sx1 := sx0 + 1 + if sx0 < sr.Min.X { + sx0, sx1 = sr.Min.X, sr.Min.X + xFrac0, xFrac1 = 0, 1 + } else if sx1 >= sr.Max.X { + sx0, sx1 = sr.Max.X-1, sr.Max.X-1 + xFrac0, xFrac1 = 1, 0 + } + + sy -= 0.5 + sy0 := int(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy0 += bias.Y + sy1 := sy0 + 1 + if sy0 < sr.Min.Y { + sy0, sy1 = sr.Min.Y, sr.Min.Y + yFrac0, yFrac1 = 0, 1 + } else if sy1 >= sr.Max.Y { + sy0, sy1 = sr.Max.Y-1, sr.Max.Y-1 + yFrac0, yFrac1 = 1, 0 + } + + s00 := $srcf[sx0, sy0] + s10 := $srcf[sx1, sy0] + $blend[xFrac1, s00, xFrac0, s10] + s01 := $srcf[sx0, sy1] + s11 := $srcf[sx1, sy1] + $blend[xFrac1, s01, xFrac0, s11] + $blend[yFrac1, s10, yFrac0, s11] + $convFtou[p, s11] + $outputu[dr.Min.X + int(dx), dr.Min.Y + int(dy), p] + } + } + } + ` + + codeKernelRoot = ` + func (z *kernelScaler) Scale(dst Image, dr image.Rectangle, src image.Image, sr image.Rectangle, op Op, opts *Options) { + if z.dw != int32(dr.Dx()) || z.dh != int32(dr.Dy()) || z.sw != int32(sr.Dx()) || z.sh != int32(sr.Dy()) { + z.kernel.Scale(dst, dr, src, sr, op, opts) + return + } + + var o Options + if opts != nil { + o = *opts + } + + // adr is the affected destination pixels. + adr := dst.Bounds().Intersect(dr) + adr, o.DstMask = clipAffectedDestRect(adr, o.DstMask, o.DstMaskP) + if adr.Empty() || sr.Empty() { + return + } + // Make adr relative to dr.Min. + adr = adr.Sub(dr.Min) + if op == Over && o.SrcMask == nil && opaque(src) { + op = Src + } + + if _, ok := src.(*image.Uniform); ok && o.DstMask == nil && o.SrcMask == nil && sr.In(src.Bounds()) { + Draw(dst, dr, src, src.Bounds().Min, op) + return + } + + // Create a temporary buffer: + // scaleX distributes the source image's columns over the temporary image. + // scaleY distributes the temporary image's rows over the destination image. + var tmp [][4]float64 + if z.pool.New != nil { + tmpp := z.pool.Get().(*[][4]float64) + defer z.pool.Put(tmpp) + tmp = *tmpp + } else { + tmp = z.makeTmpBuf() + } + + // sr is the source pixels. If it extends beyond the src bounds, + // we cannot use the type-specific fast paths, as they access + // the Pix fields directly without bounds checking. + // + // Similarly, the fast paths assume that the masks are nil. + if o.SrcMask != nil || !sr.In(src.Bounds()) { + z.scaleX_Image(tmp, src, sr, &o) + } else { + $switchS z.scaleX_$sTypeRN$sratio(tmp, src, sr, &o) + } + + if o.DstMask != nil { + switch op { + case Over: + z.scaleY_Image_Over(dst, dr, adr, tmp, &o) + case Src: + z.scaleY_Image_Src(dst, dr, adr, tmp, &o) + } + } else { + $switchD z.scaleY_$dTypeRN_$op(dst, dr, adr, tmp, &o) + } + } + + func (q *Kernel) Transform(dst Image, s2d f64.Aff3, src image.Image, sr image.Rectangle, op Op, opts *Options) { + var o Options + if opts != nil { + o = *opts + } + + dr := transformRect(&s2d, &sr) + // adr is the affected destination pixels. + adr := dst.Bounds().Intersect(dr) + adr, o.DstMask = clipAffectedDestRect(adr, o.DstMask, o.DstMaskP) + if adr.Empty() || sr.Empty() { + return + } + if op == Over && o.SrcMask == nil && opaque(src) { + op = Src + } + d2s := invert(&s2d) + // bias is a translation of the mapping from dst coordinates to src + // coordinates such that the latter temporarily have non-negative X + // and Y coordinates. This allows us to write int(f) instead of + // int(math.Floor(f)), since "round to zero" and "round down" are + // equivalent when f >= 0, but the former is much cheaper. The X-- + // and Y-- are because the TransformLeaf methods have a "sx -= 0.5" + // adjustment. + bias := transformRect(&d2s, &adr).Min + bias.X-- + bias.Y-- + d2s[2] -= float64(bias.X) + d2s[5] -= float64(bias.Y) + // Make adr relative to dr.Min. + adr = adr.Sub(dr.Min) + + if u, ok := src.(*image.Uniform); ok && o.DstMask != nil && o.SrcMask != nil && sr.In(src.Bounds()) { + transform_Uniform(dst, dr, adr, &d2s, u, sr, bias, op) + return + } + + xscale := abs(d2s[0]) + if s := abs(d2s[1]); xscale < s { + xscale = s + } + yscale := abs(d2s[3]) + if s := abs(d2s[4]); yscale < s { + yscale = s + } + + // sr is the source pixels. If it extends beyond the src bounds, + // we cannot use the type-specific fast paths, as they access + // the Pix fields directly without bounds checking. + // + // Similarly, the fast paths assume that the masks are nil. + if o.DstMask != nil || o.SrcMask != nil || !sr.In(src.Bounds()) { + switch op { + case Over: + q.transform_Image_Image_Over(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale, &o) + case Src: + q.transform_Image_Image_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale, &o) + } + } else { + $switch q.transform_$dTypeRN_$sTypeRN$sratio_$op(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale, &o) + } + } + ` + + codeKernelScaleLeafX = ` + func (z *kernelScaler) scaleX_$sTypeRN$sratio(tmp [][4]float64, src $sType, sr image.Rectangle, opts *Options) { + t := 0 + $preKernelOuter + for y := int32(0); y < z.sh; y++ { + for _, s := range z.horizontal.sources { + var pr, pg, pb, pa float64 $tweakVarP + for _, c := range z.horizontal.contribs[s.i:s.j] { + p += $srcf[sr.Min.X + int(c.coord), sr.Min.Y + int(y)] * c.weight + } + $tweakPr + tmp[t] = [4]float64{ + pr * s.invTotalWeightFFFF, $tweakP + pg * s.invTotalWeightFFFF, $tweakP + pb * s.invTotalWeightFFFF, $tweakP + pa * s.invTotalWeightFFFF, $tweakP + } + t++ + } + } + } + ` + + codeKernelScaleLeafY = ` + func (z *kernelScaler) scaleY_$dTypeRN_$op(dst $dType, dr, adr image.Rectangle, tmp [][4]float64, opts *Options) { + $preOuter + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { + $preKernelInner + for dy, s := range z.vertical.sources[adr.Min.Y:adr.Max.Y] { $tweakDy + var pr, pg, pb, pa float64 + for _, c := range z.vertical.contribs[s.i:s.j] { + p := &tmp[c.coord*z.dw+dx] + pr += p[0] * c.weight + pg += p[1] * c.weight + pb += p[2] * c.weight + pa += p[3] * c.weight + } + $clampToAlpha + $outputf[dr.Min.X + int(dx), dr.Min.Y + int(adr.Min.Y + dy), ftou, p, s.invTotalWeight] + $tweakD + } + } + } + ` + + codeKernelTransformLeaf = ` + func (q *Kernel) transform_$dTypeRN_$sTypeRN$sratio_$op(dst $dType, dr, adr image.Rectangle, d2s *f64.Aff3, src $sType, sr image.Rectangle, bias image.Point, xscale, yscale float64, opts *Options) { + // When shrinking, broaden the effective kernel support so that we still + // visit every source pixel. + xHalfWidth, xKernelArgScale := q.Support, 1.0 + if xscale > 1 { + xHalfWidth *= xscale + xKernelArgScale = 1 / xscale + } + yHalfWidth, yKernelArgScale := q.Support, 1.0 + if yscale > 1 { + yHalfWidth *= yscale + yKernelArgScale = 1 / yscale + } + + xWeights := make([]float64, 1 + 2*int(math.Ceil(xHalfWidth))) + yWeights := make([]float64, 1 + 2*int(math.Ceil(yHalfWidth))) + + $preOuter + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y + int(dy)) + 0.5 + $preInner + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { $tweakDx + dxf := float64(dr.Min.X + int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + // TODO: adjust the bias so that we can use int(f) instead + // of math.Floor(f) and math.Ceil(f). + sx += float64(bias.X) + sx -= 0.5 + ix := int(math.Floor(sx - xHalfWidth)) + if ix < sr.Min.X { + ix = sr.Min.X + } + jx := int(math.Ceil(sx + xHalfWidth)) + if jx > sr.Max.X { + jx = sr.Max.X + } + + totalXWeight := 0.0 + for kx := ix; kx < jx; kx++ { + xWeight := 0.0 + if t := abs((sx - float64(kx)) * xKernelArgScale); t < q.Support { + xWeight = q.At(t) + } + xWeights[kx - ix] = xWeight + totalXWeight += xWeight + } + for x := range xWeights[:jx-ix] { + xWeights[x] /= totalXWeight + } + + sy += float64(bias.Y) + sy -= 0.5 + iy := int(math.Floor(sy - yHalfWidth)) + if iy < sr.Min.Y { + iy = sr.Min.Y + } + jy := int(math.Ceil(sy + yHalfWidth)) + if jy > sr.Max.Y { + jy = sr.Max.Y + } + + totalYWeight := 0.0 + for ky := iy; ky < jy; ky++ { + yWeight := 0.0 + if t := abs((sy - float64(ky)) * yKernelArgScale); t < q.Support { + yWeight = q.At(t) + } + yWeights[ky - iy] = yWeight + totalYWeight += yWeight + } + for y := range yWeights[:jy-iy] { + yWeights[y] /= totalYWeight + } + + var pr, pg, pb, pa float64 $tweakVarP + for ky := iy; ky < jy; ky++ { + if yWeight := yWeights[ky - iy]; yWeight != 0 { + for kx := ix; kx < jx; kx++ { + if w := xWeights[kx - ix] * yWeight; w != 0 { + p += $srcf[kx, ky] * w + } + } + } + } + $clampToAlpha + $outputf[dr.Min.X + int(dx), dr.Min.Y + int(dy), fffftou, p, 1] + } + } + } + ` +) diff --git a/src/golang.org/x/image/draw/impl.go b/src/golang.org/x/image/draw/impl.go new file mode 100644 index 0000000000..d6484d7340 --- /dev/null +++ b/src/golang.org/x/image/draw/impl.go @@ -0,0 +1,6668 @@ +// generated by "go run gen.go". DO NOT EDIT. + +package draw + +import ( + "image" + "image/color" + "math" + + "golang.org/x/image/math/f64" +) + +func (z nnInterpolator) Scale(dst Image, dr image.Rectangle, src image.Image, sr image.Rectangle, op Op, opts *Options) { + // Try to simplify a Scale to a Copy. + if dr.Size() == sr.Size() { + Copy(dst, dr.Min, src, sr, op, opts) + return + } + + var o Options + if opts != nil { + o = *opts + } + + // adr is the affected destination pixels. + adr := dst.Bounds().Intersect(dr) + adr, o.DstMask = clipAffectedDestRect(adr, o.DstMask, o.DstMaskP) + if adr.Empty() || sr.Empty() { + return + } + // Make adr relative to dr.Min. + adr = adr.Sub(dr.Min) + if op == Over && o.SrcMask == nil && opaque(src) { + op = Src + } + + // sr is the source pixels. If it extends beyond the src bounds, + // we cannot use the type-specific fast paths, as they access + // the Pix fields directly without bounds checking. + // + // Similarly, the fast paths assume that the masks are nil. + if o.DstMask != nil || o.SrcMask != nil || !sr.In(src.Bounds()) { + switch op { + case Over: + z.scale_Image_Image_Over(dst, dr, adr, src, sr, &o) + case Src: + z.scale_Image_Image_Src(dst, dr, adr, src, sr, &o) + } + } else if _, ok := src.(*image.Uniform); ok { + Draw(dst, dr, src, src.Bounds().Min, op) + } else { + switch op { + case Over: + switch dst := dst.(type) { + case *image.RGBA: + switch src := src.(type) { + case *image.NRGBA: + z.scale_RGBA_NRGBA_Over(dst, dr, adr, src, sr, &o) + case *image.RGBA: + z.scale_RGBA_RGBA_Over(dst, dr, adr, src, sr, &o) + default: + z.scale_RGBA_Image_Over(dst, dr, adr, src, sr, &o) + } + default: + switch src := src.(type) { + default: + z.scale_Image_Image_Over(dst, dr, adr, src, sr, &o) + } + } + case Src: + switch dst := dst.(type) { + case *image.RGBA: + switch src := src.(type) { + case *image.Gray: + z.scale_RGBA_Gray_Src(dst, dr, adr, src, sr, &o) + case *image.NRGBA: + z.scale_RGBA_NRGBA_Src(dst, dr, adr, src, sr, &o) + case *image.RGBA: + z.scale_RGBA_RGBA_Src(dst, dr, adr, src, sr, &o) + case *image.YCbCr: + switch src.SubsampleRatio { + default: + z.scale_RGBA_Image_Src(dst, dr, adr, src, sr, &o) + case image.YCbCrSubsampleRatio444: + z.scale_RGBA_YCbCr444_Src(dst, dr, adr, src, sr, &o) + case image.YCbCrSubsampleRatio422: + z.scale_RGBA_YCbCr422_Src(dst, dr, adr, src, sr, &o) + case image.YCbCrSubsampleRatio420: + z.scale_RGBA_YCbCr420_Src(dst, dr, adr, src, sr, &o) + case image.YCbCrSubsampleRatio440: + z.scale_RGBA_YCbCr440_Src(dst, dr, adr, src, sr, &o) + } + default: + z.scale_RGBA_Image_Src(dst, dr, adr, src, sr, &o) + } + default: + switch src := src.(type) { + default: + z.scale_Image_Image_Src(dst, dr, adr, src, sr, &o) + } + } + } + } +} + +func (z nnInterpolator) Transform(dst Image, s2d f64.Aff3, src image.Image, sr image.Rectangle, op Op, opts *Options) { + // Try to simplify a Transform to a Copy. + if s2d[0] == 1 && s2d[1] == 0 && s2d[3] == 0 && s2d[4] == 1 { + dx := int(s2d[2]) + dy := int(s2d[5]) + if float64(dx) == s2d[2] && float64(dy) == s2d[5] { + Copy(dst, image.Point{X: sr.Min.X + dx, Y: sr.Min.X + dy}, src, sr, op, opts) + return + } + } + + var o Options + if opts != nil { + o = *opts + } + + dr := transformRect(&s2d, &sr) + // adr is the affected destination pixels. + adr := dst.Bounds().Intersect(dr) + adr, o.DstMask = clipAffectedDestRect(adr, o.DstMask, o.DstMaskP) + if adr.Empty() || sr.Empty() { + return + } + if op == Over && o.SrcMask == nil && opaque(src) { + op = Src + } + + d2s := invert(&s2d) + // bias is a translation of the mapping from dst coordinates to src + // coordinates such that the latter temporarily have non-negative X + // and Y coordinates. This allows us to write int(f) instead of + // int(math.Floor(f)), since "round to zero" and "round down" are + // equivalent when f >= 0, but the former is much cheaper. The X-- + // and Y-- are because the TransformLeaf methods have a "sx -= 0.5" + // adjustment. + bias := transformRect(&d2s, &adr).Min + bias.X-- + bias.Y-- + d2s[2] -= float64(bias.X) + d2s[5] -= float64(bias.Y) + // Make adr relative to dr.Min. + adr = adr.Sub(dr.Min) + // sr is the source pixels. If it extends beyond the src bounds, + // we cannot use the type-specific fast paths, as they access + // the Pix fields directly without bounds checking. + // + // Similarly, the fast paths assume that the masks are nil. + if o.DstMask != nil || o.SrcMask != nil || !sr.In(src.Bounds()) { + switch op { + case Over: + z.transform_Image_Image_Over(dst, dr, adr, &d2s, src, sr, bias, &o) + case Src: + z.transform_Image_Image_Src(dst, dr, adr, &d2s, src, sr, bias, &o) + } + } else if u, ok := src.(*image.Uniform); ok { + transform_Uniform(dst, dr, adr, &d2s, u, sr, bias, op) + } else { + switch op { + case Over: + switch dst := dst.(type) { + case *image.RGBA: + switch src := src.(type) { + case *image.NRGBA: + z.transform_RGBA_NRGBA_Over(dst, dr, adr, &d2s, src, sr, bias, &o) + case *image.RGBA: + z.transform_RGBA_RGBA_Over(dst, dr, adr, &d2s, src, sr, bias, &o) + default: + z.transform_RGBA_Image_Over(dst, dr, adr, &d2s, src, sr, bias, &o) + } + default: + switch src := src.(type) { + default: + z.transform_Image_Image_Over(dst, dr, adr, &d2s, src, sr, bias, &o) + } + } + case Src: + switch dst := dst.(type) { + case *image.RGBA: + switch src := src.(type) { + case *image.Gray: + z.transform_RGBA_Gray_Src(dst, dr, adr, &d2s, src, sr, bias, &o) + case *image.NRGBA: + z.transform_RGBA_NRGBA_Src(dst, dr, adr, &d2s, src, sr, bias, &o) + case *image.RGBA: + z.transform_RGBA_RGBA_Src(dst, dr, adr, &d2s, src, sr, bias, &o) + case *image.YCbCr: + switch src.SubsampleRatio { + default: + z.transform_RGBA_Image_Src(dst, dr, adr, &d2s, src, sr, bias, &o) + case image.YCbCrSubsampleRatio444: + z.transform_RGBA_YCbCr444_Src(dst, dr, adr, &d2s, src, sr, bias, &o) + case image.YCbCrSubsampleRatio422: + z.transform_RGBA_YCbCr422_Src(dst, dr, adr, &d2s, src, sr, bias, &o) + case image.YCbCrSubsampleRatio420: + z.transform_RGBA_YCbCr420_Src(dst, dr, adr, &d2s, src, sr, bias, &o) + case image.YCbCrSubsampleRatio440: + z.transform_RGBA_YCbCr440_Src(dst, dr, adr, &d2s, src, sr, bias, &o) + } + default: + z.transform_RGBA_Image_Src(dst, dr, adr, &d2s, src, sr, bias, &o) + } + default: + switch src := src.(type) { + default: + z.transform_Image_Image_Src(dst, dr, adr, &d2s, src, sr, bias, &o) + } + } + } + } +} + +func (nnInterpolator) scale_RGBA_Gray_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.Gray, sr image.Rectangle, opts *Options) { + dw2 := uint64(dr.Dx()) * 2 + dh2 := uint64(dr.Dy()) * 2 + sw := uint64(sr.Dx()) + sh := uint64(sr.Dy()) + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (2*uint64(dy) + 1) * sh / dh2 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + sx := (2*uint64(dx) + 1) * sw / dw2 + pi := (sr.Min.Y+int(sy)-src.Rect.Min.Y)*src.Stride + (sr.Min.X + int(sx) - src.Rect.Min.X) + pr := uint32(src.Pix[pi]) * 0x101 + out := uint8(pr >> 8) + dst.Pix[d+0] = out + dst.Pix[d+1] = out + dst.Pix[d+2] = out + dst.Pix[d+3] = 0xff + } + } +} + +func (nnInterpolator) scale_RGBA_NRGBA_Over(dst *image.RGBA, dr, adr image.Rectangle, src *image.NRGBA, sr image.Rectangle, opts *Options) { + dw2 := uint64(dr.Dx()) * 2 + dh2 := uint64(dr.Dy()) * 2 + sw := uint64(sr.Dx()) + sh := uint64(sr.Dy()) + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (2*uint64(dy) + 1) * sh / dh2 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + sx := (2*uint64(dx) + 1) * sw / dw2 + pi := (sr.Min.Y+int(sy)-src.Rect.Min.Y)*src.Stride + (sr.Min.X+int(sx)-src.Rect.Min.X)*4 + pa := uint32(src.Pix[pi+3]) * 0x101 + pr := uint32(src.Pix[pi+0]) * pa / 0xff + pg := uint32(src.Pix[pi+1]) * pa / 0xff + pb := uint32(src.Pix[pi+2]) * pa / 0xff + pa1 := (0xffff - pa) * 0x101 + dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*pa1/0xffff + pr) >> 8) + dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*pa1/0xffff + pg) >> 8) + dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*pa1/0xffff + pb) >> 8) + dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*pa1/0xffff + pa) >> 8) + } + } +} + +func (nnInterpolator) scale_RGBA_NRGBA_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.NRGBA, sr image.Rectangle, opts *Options) { + dw2 := uint64(dr.Dx()) * 2 + dh2 := uint64(dr.Dy()) * 2 + sw := uint64(sr.Dx()) + sh := uint64(sr.Dy()) + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (2*uint64(dy) + 1) * sh / dh2 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + sx := (2*uint64(dx) + 1) * sw / dw2 + pi := (sr.Min.Y+int(sy)-src.Rect.Min.Y)*src.Stride + (sr.Min.X+int(sx)-src.Rect.Min.X)*4 + pa := uint32(src.Pix[pi+3]) * 0x101 + pr := uint32(src.Pix[pi+0]) * pa / 0xff + pg := uint32(src.Pix[pi+1]) * pa / 0xff + pb := uint32(src.Pix[pi+2]) * pa / 0xff + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = uint8(pa >> 8) + } + } +} + +func (nnInterpolator) scale_RGBA_RGBA_Over(dst *image.RGBA, dr, adr image.Rectangle, src *image.RGBA, sr image.Rectangle, opts *Options) { + dw2 := uint64(dr.Dx()) * 2 + dh2 := uint64(dr.Dy()) * 2 + sw := uint64(sr.Dx()) + sh := uint64(sr.Dy()) + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (2*uint64(dy) + 1) * sh / dh2 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + sx := (2*uint64(dx) + 1) * sw / dw2 + pi := (sr.Min.Y+int(sy)-src.Rect.Min.Y)*src.Stride + (sr.Min.X+int(sx)-src.Rect.Min.X)*4 + pr := uint32(src.Pix[pi+0]) * 0x101 + pg := uint32(src.Pix[pi+1]) * 0x101 + pb := uint32(src.Pix[pi+2]) * 0x101 + pa := uint32(src.Pix[pi+3]) * 0x101 + pa1 := (0xffff - pa) * 0x101 + dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*pa1/0xffff + pr) >> 8) + dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*pa1/0xffff + pg) >> 8) + dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*pa1/0xffff + pb) >> 8) + dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*pa1/0xffff + pa) >> 8) + } + } +} + +func (nnInterpolator) scale_RGBA_RGBA_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.RGBA, sr image.Rectangle, opts *Options) { + dw2 := uint64(dr.Dx()) * 2 + dh2 := uint64(dr.Dy()) * 2 + sw := uint64(sr.Dx()) + sh := uint64(sr.Dy()) + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (2*uint64(dy) + 1) * sh / dh2 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + sx := (2*uint64(dx) + 1) * sw / dw2 + pi := (sr.Min.Y+int(sy)-src.Rect.Min.Y)*src.Stride + (sr.Min.X+int(sx)-src.Rect.Min.X)*4 + pr := uint32(src.Pix[pi+0]) * 0x101 + pg := uint32(src.Pix[pi+1]) * 0x101 + pb := uint32(src.Pix[pi+2]) * 0x101 + pa := uint32(src.Pix[pi+3]) * 0x101 + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = uint8(pa >> 8) + } + } +} + +func (nnInterpolator) scale_RGBA_YCbCr444_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.YCbCr, sr image.Rectangle, opts *Options) { + dw2 := uint64(dr.Dx()) * 2 + dh2 := uint64(dr.Dy()) * 2 + sw := uint64(sr.Dx()) + sh := uint64(sr.Dy()) + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (2*uint64(dy) + 1) * sh / dh2 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + sx := (2*uint64(dx) + 1) * sw / dw2 + pi := (sr.Min.Y+int(sy)-src.Rect.Min.Y)*src.YStride + (sr.Min.X + int(sx) - src.Rect.Min.X) + pj := (sr.Min.Y+int(sy)-src.Rect.Min.Y)*src.CStride + (sr.Min.X + int(sx) - src.Rect.Min.X) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + pyy1 := int(src.Y[pi]) * 0x10100 + pcb1 := int(src.Cb[pj]) - 128 + pcr1 := int(src.Cr[pj]) - 128 + pr := (pyy1 + 91881*pcr1) >> 8 + pg := (pyy1 - 22554*pcb1 - 46802*pcr1) >> 8 + pb := (pyy1 + 116130*pcb1) >> 8 + if pr < 0 { + pr = 0 + } else if pr > 0xffff { + pr = 0xffff + } + if pg < 0 { + pg = 0 + } else if pg > 0xffff { + pg = 0xffff + } + if pb < 0 { + pb = 0 + } else if pb > 0xffff { + pb = 0xffff + } + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = 0xff + } + } +} + +func (nnInterpolator) scale_RGBA_YCbCr422_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.YCbCr, sr image.Rectangle, opts *Options) { + dw2 := uint64(dr.Dx()) * 2 + dh2 := uint64(dr.Dy()) * 2 + sw := uint64(sr.Dx()) + sh := uint64(sr.Dy()) + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (2*uint64(dy) + 1) * sh / dh2 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + sx := (2*uint64(dx) + 1) * sw / dw2 + pi := (sr.Min.Y+int(sy)-src.Rect.Min.Y)*src.YStride + (sr.Min.X + int(sx) - src.Rect.Min.X) + pj := (sr.Min.Y+int(sy)-src.Rect.Min.Y)*src.CStride + ((sr.Min.X+int(sx))/2 - src.Rect.Min.X/2) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + pyy1 := int(src.Y[pi]) * 0x10100 + pcb1 := int(src.Cb[pj]) - 128 + pcr1 := int(src.Cr[pj]) - 128 + pr := (pyy1 + 91881*pcr1) >> 8 + pg := (pyy1 - 22554*pcb1 - 46802*pcr1) >> 8 + pb := (pyy1 + 116130*pcb1) >> 8 + if pr < 0 { + pr = 0 + } else if pr > 0xffff { + pr = 0xffff + } + if pg < 0 { + pg = 0 + } else if pg > 0xffff { + pg = 0xffff + } + if pb < 0 { + pb = 0 + } else if pb > 0xffff { + pb = 0xffff + } + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = 0xff + } + } +} + +func (nnInterpolator) scale_RGBA_YCbCr420_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.YCbCr, sr image.Rectangle, opts *Options) { + dw2 := uint64(dr.Dx()) * 2 + dh2 := uint64(dr.Dy()) * 2 + sw := uint64(sr.Dx()) + sh := uint64(sr.Dy()) + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (2*uint64(dy) + 1) * sh / dh2 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + sx := (2*uint64(dx) + 1) * sw / dw2 + pi := (sr.Min.Y+int(sy)-src.Rect.Min.Y)*src.YStride + (sr.Min.X + int(sx) - src.Rect.Min.X) + pj := ((sr.Min.Y+int(sy))/2-src.Rect.Min.Y/2)*src.CStride + ((sr.Min.X+int(sx))/2 - src.Rect.Min.X/2) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + pyy1 := int(src.Y[pi]) * 0x10100 + pcb1 := int(src.Cb[pj]) - 128 + pcr1 := int(src.Cr[pj]) - 128 + pr := (pyy1 + 91881*pcr1) >> 8 + pg := (pyy1 - 22554*pcb1 - 46802*pcr1) >> 8 + pb := (pyy1 + 116130*pcb1) >> 8 + if pr < 0 { + pr = 0 + } else if pr > 0xffff { + pr = 0xffff + } + if pg < 0 { + pg = 0 + } else if pg > 0xffff { + pg = 0xffff + } + if pb < 0 { + pb = 0 + } else if pb > 0xffff { + pb = 0xffff + } + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = 0xff + } + } +} + +func (nnInterpolator) scale_RGBA_YCbCr440_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.YCbCr, sr image.Rectangle, opts *Options) { + dw2 := uint64(dr.Dx()) * 2 + dh2 := uint64(dr.Dy()) * 2 + sw := uint64(sr.Dx()) + sh := uint64(sr.Dy()) + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (2*uint64(dy) + 1) * sh / dh2 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + sx := (2*uint64(dx) + 1) * sw / dw2 + pi := (sr.Min.Y+int(sy)-src.Rect.Min.Y)*src.YStride + (sr.Min.X + int(sx) - src.Rect.Min.X) + pj := ((sr.Min.Y+int(sy))/2-src.Rect.Min.Y/2)*src.CStride + (sr.Min.X + int(sx) - src.Rect.Min.X) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + pyy1 := int(src.Y[pi]) * 0x10100 + pcb1 := int(src.Cb[pj]) - 128 + pcr1 := int(src.Cr[pj]) - 128 + pr := (pyy1 + 91881*pcr1) >> 8 + pg := (pyy1 - 22554*pcb1 - 46802*pcr1) >> 8 + pb := (pyy1 + 116130*pcb1) >> 8 + if pr < 0 { + pr = 0 + } else if pr > 0xffff { + pr = 0xffff + } + if pg < 0 { + pg = 0 + } else if pg > 0xffff { + pg = 0xffff + } + if pb < 0 { + pb = 0 + } else if pb > 0xffff { + pb = 0xffff + } + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = 0xff + } + } +} + +func (nnInterpolator) scale_RGBA_Image_Over(dst *image.RGBA, dr, adr image.Rectangle, src image.Image, sr image.Rectangle, opts *Options) { + dw2 := uint64(dr.Dx()) * 2 + dh2 := uint64(dr.Dy()) * 2 + sw := uint64(sr.Dx()) + sh := uint64(sr.Dy()) + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (2*uint64(dy) + 1) * sh / dh2 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + sx := (2*uint64(dx) + 1) * sw / dw2 + pr, pg, pb, pa := src.At(sr.Min.X+int(sx), sr.Min.Y+int(sy)).RGBA() + pa1 := (0xffff - pa) * 0x101 + dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*pa1/0xffff + pr) >> 8) + dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*pa1/0xffff + pg) >> 8) + dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*pa1/0xffff + pb) >> 8) + dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*pa1/0xffff + pa) >> 8) + } + } +} + +func (nnInterpolator) scale_RGBA_Image_Src(dst *image.RGBA, dr, adr image.Rectangle, src image.Image, sr image.Rectangle, opts *Options) { + dw2 := uint64(dr.Dx()) * 2 + dh2 := uint64(dr.Dy()) * 2 + sw := uint64(sr.Dx()) + sh := uint64(sr.Dy()) + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (2*uint64(dy) + 1) * sh / dh2 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + sx := (2*uint64(dx) + 1) * sw / dw2 + pr, pg, pb, pa := src.At(sr.Min.X+int(sx), sr.Min.Y+int(sy)).RGBA() + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = uint8(pa >> 8) + } + } +} + +func (nnInterpolator) scale_Image_Image_Over(dst Image, dr, adr image.Rectangle, src image.Image, sr image.Rectangle, opts *Options) { + dw2 := uint64(dr.Dx()) * 2 + dh2 := uint64(dr.Dy()) * 2 + sw := uint64(sr.Dx()) + sh := uint64(sr.Dy()) + srcMask, smp := opts.SrcMask, opts.SrcMaskP + dstMask, dmp := opts.DstMask, opts.DstMaskP + dstColorRGBA64 := &color.RGBA64{} + dstColor := color.Color(dstColorRGBA64) + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (2*uint64(dy) + 1) * sh / dh2 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { + sx := (2*uint64(dx) + 1) * sw / dw2 + pr, pg, pb, pa := src.At(sr.Min.X+int(sx), sr.Min.Y+int(sy)).RGBA() + if srcMask != nil { + _, _, _, ma := srcMask.At(smp.X+sr.Min.X+int(sx), smp.Y+sr.Min.Y+int(sy)).RGBA() + pr = pr * ma / 0xffff + pg = pg * ma / 0xffff + pb = pb * ma / 0xffff + pa = pa * ma / 0xffff + } + qr, qg, qb, qa := dst.At(dr.Min.X+int(dx), dr.Min.Y+int(dy)).RGBA() + if dstMask != nil { + _, _, _, ma := dstMask.At(dmp.X+dr.Min.X+int(dx), dmp.Y+dr.Min.Y+int(dy)).RGBA() + pr = pr * ma / 0xffff + pg = pg * ma / 0xffff + pb = pb * ma / 0xffff + pa = pa * ma / 0xffff + } + pa1 := 0xffff - pa + dstColorRGBA64.R = uint16(qr*pa1/0xffff + pr) + dstColorRGBA64.G = uint16(qg*pa1/0xffff + pg) + dstColorRGBA64.B = uint16(qb*pa1/0xffff + pb) + dstColorRGBA64.A = uint16(qa*pa1/0xffff + pa) + dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor) + } + } +} + +func (nnInterpolator) scale_Image_Image_Src(dst Image, dr, adr image.Rectangle, src image.Image, sr image.Rectangle, opts *Options) { + dw2 := uint64(dr.Dx()) * 2 + dh2 := uint64(dr.Dy()) * 2 + sw := uint64(sr.Dx()) + sh := uint64(sr.Dy()) + srcMask, smp := opts.SrcMask, opts.SrcMaskP + dstMask, dmp := opts.DstMask, opts.DstMaskP + dstColorRGBA64 := &color.RGBA64{} + dstColor := color.Color(dstColorRGBA64) + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (2*uint64(dy) + 1) * sh / dh2 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { + sx := (2*uint64(dx) + 1) * sw / dw2 + pr, pg, pb, pa := src.At(sr.Min.X+int(sx), sr.Min.Y+int(sy)).RGBA() + if srcMask != nil { + _, _, _, ma := srcMask.At(smp.X+sr.Min.X+int(sx), smp.Y+sr.Min.Y+int(sy)).RGBA() + pr = pr * ma / 0xffff + pg = pg * ma / 0xffff + pb = pb * ma / 0xffff + pa = pa * ma / 0xffff + } + if dstMask != nil { + qr, qg, qb, qa := dst.At(dr.Min.X+int(dx), dr.Min.Y+int(dy)).RGBA() + _, _, _, ma := dstMask.At(dmp.X+dr.Min.X+int(dx), dmp.Y+dr.Min.Y+int(dy)).RGBA() + pr = pr * ma / 0xffff + pg = pg * ma / 0xffff + pb = pb * ma / 0xffff + pa = pa * ma / 0xffff + pa1 := 0xffff - ma + dstColorRGBA64.R = uint16(qr*pa1/0xffff + pr) + dstColorRGBA64.G = uint16(qg*pa1/0xffff + pg) + dstColorRGBA64.B = uint16(qb*pa1/0xffff + pb) + dstColorRGBA64.A = uint16(qa*pa1/0xffff + pa) + dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor) + } else { + dstColorRGBA64.R = uint16(pr) + dstColorRGBA64.G = uint16(pg) + dstColorRGBA64.B = uint16(pb) + dstColorRGBA64.A = uint16(pa) + dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor) + } + } + } +} + +func (nnInterpolator) transform_RGBA_Gray_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.Gray, sr image.Rectangle, bias image.Point, opts *Options) { + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X + sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y + if !(image.Point{sx0, sy0}).In(sr) { + continue + } + pi := (sy0-src.Rect.Min.Y)*src.Stride + (sx0 - src.Rect.Min.X) + pr := uint32(src.Pix[pi]) * 0x101 + out := uint8(pr >> 8) + dst.Pix[d+0] = out + dst.Pix[d+1] = out + dst.Pix[d+2] = out + dst.Pix[d+3] = 0xff + } + } +} + +func (nnInterpolator) transform_RGBA_NRGBA_Over(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.NRGBA, sr image.Rectangle, bias image.Point, opts *Options) { + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X + sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y + if !(image.Point{sx0, sy0}).In(sr) { + continue + } + pi := (sy0-src.Rect.Min.Y)*src.Stride + (sx0-src.Rect.Min.X)*4 + pa := uint32(src.Pix[pi+3]) * 0x101 + pr := uint32(src.Pix[pi+0]) * pa / 0xff + pg := uint32(src.Pix[pi+1]) * pa / 0xff + pb := uint32(src.Pix[pi+2]) * pa / 0xff + pa1 := (0xffff - pa) * 0x101 + dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*pa1/0xffff + pr) >> 8) + dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*pa1/0xffff + pg) >> 8) + dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*pa1/0xffff + pb) >> 8) + dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*pa1/0xffff + pa) >> 8) + } + } +} + +func (nnInterpolator) transform_RGBA_NRGBA_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.NRGBA, sr image.Rectangle, bias image.Point, opts *Options) { + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X + sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y + if !(image.Point{sx0, sy0}).In(sr) { + continue + } + pi := (sy0-src.Rect.Min.Y)*src.Stride + (sx0-src.Rect.Min.X)*4 + pa := uint32(src.Pix[pi+3]) * 0x101 + pr := uint32(src.Pix[pi+0]) * pa / 0xff + pg := uint32(src.Pix[pi+1]) * pa / 0xff + pb := uint32(src.Pix[pi+2]) * pa / 0xff + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = uint8(pa >> 8) + } + } +} + +func (nnInterpolator) transform_RGBA_RGBA_Over(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.RGBA, sr image.Rectangle, bias image.Point, opts *Options) { + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X + sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y + if !(image.Point{sx0, sy0}).In(sr) { + continue + } + pi := (sy0-src.Rect.Min.Y)*src.Stride + (sx0-src.Rect.Min.X)*4 + pr := uint32(src.Pix[pi+0]) * 0x101 + pg := uint32(src.Pix[pi+1]) * 0x101 + pb := uint32(src.Pix[pi+2]) * 0x101 + pa := uint32(src.Pix[pi+3]) * 0x101 + pa1 := (0xffff - pa) * 0x101 + dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*pa1/0xffff + pr) >> 8) + dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*pa1/0xffff + pg) >> 8) + dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*pa1/0xffff + pb) >> 8) + dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*pa1/0xffff + pa) >> 8) + } + } +} + +func (nnInterpolator) transform_RGBA_RGBA_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.RGBA, sr image.Rectangle, bias image.Point, opts *Options) { + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X + sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y + if !(image.Point{sx0, sy0}).In(sr) { + continue + } + pi := (sy0-src.Rect.Min.Y)*src.Stride + (sx0-src.Rect.Min.X)*4 + pr := uint32(src.Pix[pi+0]) * 0x101 + pg := uint32(src.Pix[pi+1]) * 0x101 + pb := uint32(src.Pix[pi+2]) * 0x101 + pa := uint32(src.Pix[pi+3]) * 0x101 + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = uint8(pa >> 8) + } + } +} + +func (nnInterpolator) transform_RGBA_YCbCr444_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point, opts *Options) { + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X + sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y + if !(image.Point{sx0, sy0}).In(sr) { + continue + } + pi := (sy0-src.Rect.Min.Y)*src.YStride + (sx0 - src.Rect.Min.X) + pj := (sy0-src.Rect.Min.Y)*src.CStride + (sx0 - src.Rect.Min.X) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + pyy1 := int(src.Y[pi]) * 0x10100 + pcb1 := int(src.Cb[pj]) - 128 + pcr1 := int(src.Cr[pj]) - 128 + pr := (pyy1 + 91881*pcr1) >> 8 + pg := (pyy1 - 22554*pcb1 - 46802*pcr1) >> 8 + pb := (pyy1 + 116130*pcb1) >> 8 + if pr < 0 { + pr = 0 + } else if pr > 0xffff { + pr = 0xffff + } + if pg < 0 { + pg = 0 + } else if pg > 0xffff { + pg = 0xffff + } + if pb < 0 { + pb = 0 + } else if pb > 0xffff { + pb = 0xffff + } + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = 0xff + } + } +} + +func (nnInterpolator) transform_RGBA_YCbCr422_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point, opts *Options) { + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X + sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y + if !(image.Point{sx0, sy0}).In(sr) { + continue + } + pi := (sy0-src.Rect.Min.Y)*src.YStride + (sx0 - src.Rect.Min.X) + pj := (sy0-src.Rect.Min.Y)*src.CStride + ((sx0)/2 - src.Rect.Min.X/2) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + pyy1 := int(src.Y[pi]) * 0x10100 + pcb1 := int(src.Cb[pj]) - 128 + pcr1 := int(src.Cr[pj]) - 128 + pr := (pyy1 + 91881*pcr1) >> 8 + pg := (pyy1 - 22554*pcb1 - 46802*pcr1) >> 8 + pb := (pyy1 + 116130*pcb1) >> 8 + if pr < 0 { + pr = 0 + } else if pr > 0xffff { + pr = 0xffff + } + if pg < 0 { + pg = 0 + } else if pg > 0xffff { + pg = 0xffff + } + if pb < 0 { + pb = 0 + } else if pb > 0xffff { + pb = 0xffff + } + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = 0xff + } + } +} + +func (nnInterpolator) transform_RGBA_YCbCr420_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point, opts *Options) { + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X + sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y + if !(image.Point{sx0, sy0}).In(sr) { + continue + } + pi := (sy0-src.Rect.Min.Y)*src.YStride + (sx0 - src.Rect.Min.X) + pj := ((sy0)/2-src.Rect.Min.Y/2)*src.CStride + ((sx0)/2 - src.Rect.Min.X/2) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + pyy1 := int(src.Y[pi]) * 0x10100 + pcb1 := int(src.Cb[pj]) - 128 + pcr1 := int(src.Cr[pj]) - 128 + pr := (pyy1 + 91881*pcr1) >> 8 + pg := (pyy1 - 22554*pcb1 - 46802*pcr1) >> 8 + pb := (pyy1 + 116130*pcb1) >> 8 + if pr < 0 { + pr = 0 + } else if pr > 0xffff { + pr = 0xffff + } + if pg < 0 { + pg = 0 + } else if pg > 0xffff { + pg = 0xffff + } + if pb < 0 { + pb = 0 + } else if pb > 0xffff { + pb = 0xffff + } + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = 0xff + } + } +} + +func (nnInterpolator) transform_RGBA_YCbCr440_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point, opts *Options) { + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X + sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y + if !(image.Point{sx0, sy0}).In(sr) { + continue + } + pi := (sy0-src.Rect.Min.Y)*src.YStride + (sx0 - src.Rect.Min.X) + pj := ((sy0)/2-src.Rect.Min.Y/2)*src.CStride + (sx0 - src.Rect.Min.X) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + pyy1 := int(src.Y[pi]) * 0x10100 + pcb1 := int(src.Cb[pj]) - 128 + pcr1 := int(src.Cr[pj]) - 128 + pr := (pyy1 + 91881*pcr1) >> 8 + pg := (pyy1 - 22554*pcb1 - 46802*pcr1) >> 8 + pb := (pyy1 + 116130*pcb1) >> 8 + if pr < 0 { + pr = 0 + } else if pr > 0xffff { + pr = 0xffff + } + if pg < 0 { + pg = 0 + } else if pg > 0xffff { + pg = 0xffff + } + if pb < 0 { + pb = 0 + } else if pb > 0xffff { + pb = 0xffff + } + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = 0xff + } + } +} + +func (nnInterpolator) transform_RGBA_Image_Over(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src image.Image, sr image.Rectangle, bias image.Point, opts *Options) { + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X + sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y + if !(image.Point{sx0, sy0}).In(sr) { + continue + } + pr, pg, pb, pa := src.At(sx0, sy0).RGBA() + pa1 := (0xffff - pa) * 0x101 + dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*pa1/0xffff + pr) >> 8) + dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*pa1/0xffff + pg) >> 8) + dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*pa1/0xffff + pb) >> 8) + dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*pa1/0xffff + pa) >> 8) + } + } +} + +func (nnInterpolator) transform_RGBA_Image_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src image.Image, sr image.Rectangle, bias image.Point, opts *Options) { + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X + sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y + if !(image.Point{sx0, sy0}).In(sr) { + continue + } + pr, pg, pb, pa := src.At(sx0, sy0).RGBA() + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = uint8(pa >> 8) + } + } +} + +func (nnInterpolator) transform_Image_Image_Over(dst Image, dr, adr image.Rectangle, d2s *f64.Aff3, src image.Image, sr image.Rectangle, bias image.Point, opts *Options) { + srcMask, smp := opts.SrcMask, opts.SrcMaskP + dstMask, dmp := opts.DstMask, opts.DstMaskP + dstColorRGBA64 := &color.RGBA64{} + dstColor := color.Color(dstColorRGBA64) + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X + sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y + if !(image.Point{sx0, sy0}).In(sr) { + continue + } + pr, pg, pb, pa := src.At(sx0, sy0).RGBA() + if srcMask != nil { + _, _, _, ma := srcMask.At(smp.X+sx0, smp.Y+sy0).RGBA() + pr = pr * ma / 0xffff + pg = pg * ma / 0xffff + pb = pb * ma / 0xffff + pa = pa * ma / 0xffff + } + qr, qg, qb, qa := dst.At(dr.Min.X+int(dx), dr.Min.Y+int(dy)).RGBA() + if dstMask != nil { + _, _, _, ma := dstMask.At(dmp.X+dr.Min.X+int(dx), dmp.Y+dr.Min.Y+int(dy)).RGBA() + pr = pr * ma / 0xffff + pg = pg * ma / 0xffff + pb = pb * ma / 0xffff + pa = pa * ma / 0xffff + } + pa1 := 0xffff - pa + dstColorRGBA64.R = uint16(qr*pa1/0xffff + pr) + dstColorRGBA64.G = uint16(qg*pa1/0xffff + pg) + dstColorRGBA64.B = uint16(qb*pa1/0xffff + pb) + dstColorRGBA64.A = uint16(qa*pa1/0xffff + pa) + dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor) + } + } +} + +func (nnInterpolator) transform_Image_Image_Src(dst Image, dr, adr image.Rectangle, d2s *f64.Aff3, src image.Image, sr image.Rectangle, bias image.Point, opts *Options) { + srcMask, smp := opts.SrcMask, opts.SrcMaskP + dstMask, dmp := opts.DstMask, opts.DstMaskP + dstColorRGBA64 := &color.RGBA64{} + dstColor := color.Color(dstColorRGBA64) + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X + sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y + if !(image.Point{sx0, sy0}).In(sr) { + continue + } + pr, pg, pb, pa := src.At(sx0, sy0).RGBA() + if srcMask != nil { + _, _, _, ma := srcMask.At(smp.X+sx0, smp.Y+sy0).RGBA() + pr = pr * ma / 0xffff + pg = pg * ma / 0xffff + pb = pb * ma / 0xffff + pa = pa * ma / 0xffff + } + if dstMask != nil { + qr, qg, qb, qa := dst.At(dr.Min.X+int(dx), dr.Min.Y+int(dy)).RGBA() + _, _, _, ma := dstMask.At(dmp.X+dr.Min.X+int(dx), dmp.Y+dr.Min.Y+int(dy)).RGBA() + pr = pr * ma / 0xffff + pg = pg * ma / 0xffff + pb = pb * ma / 0xffff + pa = pa * ma / 0xffff + pa1 := 0xffff - ma + dstColorRGBA64.R = uint16(qr*pa1/0xffff + pr) + dstColorRGBA64.G = uint16(qg*pa1/0xffff + pg) + dstColorRGBA64.B = uint16(qb*pa1/0xffff + pb) + dstColorRGBA64.A = uint16(qa*pa1/0xffff + pa) + dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor) + } else { + dstColorRGBA64.R = uint16(pr) + dstColorRGBA64.G = uint16(pg) + dstColorRGBA64.B = uint16(pb) + dstColorRGBA64.A = uint16(pa) + dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor) + } + } + } +} + +func (z ablInterpolator) Scale(dst Image, dr image.Rectangle, src image.Image, sr image.Rectangle, op Op, opts *Options) { + // Try to simplify a Scale to a Copy. + if dr.Size() == sr.Size() { + Copy(dst, dr.Min, src, sr, op, opts) + return + } + + var o Options + if opts != nil { + o = *opts + } + + // adr is the affected destination pixels. + adr := dst.Bounds().Intersect(dr) + adr, o.DstMask = clipAffectedDestRect(adr, o.DstMask, o.DstMaskP) + if adr.Empty() || sr.Empty() { + return + } + // Make adr relative to dr.Min. + adr = adr.Sub(dr.Min) + if op == Over && o.SrcMask == nil && opaque(src) { + op = Src + } + + // sr is the source pixels. If it extends beyond the src bounds, + // we cannot use the type-specific fast paths, as they access + // the Pix fields directly without bounds checking. + // + // Similarly, the fast paths assume that the masks are nil. + if o.DstMask != nil || o.SrcMask != nil || !sr.In(src.Bounds()) { + switch op { + case Over: + z.scale_Image_Image_Over(dst, dr, adr, src, sr, &o) + case Src: + z.scale_Image_Image_Src(dst, dr, adr, src, sr, &o) + } + } else if _, ok := src.(*image.Uniform); ok { + Draw(dst, dr, src, src.Bounds().Min, op) + } else { + switch op { + case Over: + switch dst := dst.(type) { + case *image.RGBA: + switch src := src.(type) { + case *image.NRGBA: + z.scale_RGBA_NRGBA_Over(dst, dr, adr, src, sr, &o) + case *image.RGBA: + z.scale_RGBA_RGBA_Over(dst, dr, adr, src, sr, &o) + default: + z.scale_RGBA_Image_Over(dst, dr, adr, src, sr, &o) + } + default: + switch src := src.(type) { + default: + z.scale_Image_Image_Over(dst, dr, adr, src, sr, &o) + } + } + case Src: + switch dst := dst.(type) { + case *image.RGBA: + switch src := src.(type) { + case *image.Gray: + z.scale_RGBA_Gray_Src(dst, dr, adr, src, sr, &o) + case *image.NRGBA: + z.scale_RGBA_NRGBA_Src(dst, dr, adr, src, sr, &o) + case *image.RGBA: + z.scale_RGBA_RGBA_Src(dst, dr, adr, src, sr, &o) + case *image.YCbCr: + switch src.SubsampleRatio { + default: + z.scale_RGBA_Image_Src(dst, dr, adr, src, sr, &o) + case image.YCbCrSubsampleRatio444: + z.scale_RGBA_YCbCr444_Src(dst, dr, adr, src, sr, &o) + case image.YCbCrSubsampleRatio422: + z.scale_RGBA_YCbCr422_Src(dst, dr, adr, src, sr, &o) + case image.YCbCrSubsampleRatio420: + z.scale_RGBA_YCbCr420_Src(dst, dr, adr, src, sr, &o) + case image.YCbCrSubsampleRatio440: + z.scale_RGBA_YCbCr440_Src(dst, dr, adr, src, sr, &o) + } + default: + z.scale_RGBA_Image_Src(dst, dr, adr, src, sr, &o) + } + default: + switch src := src.(type) { + default: + z.scale_Image_Image_Src(dst, dr, adr, src, sr, &o) + } + } + } + } +} + +func (z ablInterpolator) Transform(dst Image, s2d f64.Aff3, src image.Image, sr image.Rectangle, op Op, opts *Options) { + // Try to simplify a Transform to a Copy. + if s2d[0] == 1 && s2d[1] == 0 && s2d[3] == 0 && s2d[4] == 1 { + dx := int(s2d[2]) + dy := int(s2d[5]) + if float64(dx) == s2d[2] && float64(dy) == s2d[5] { + Copy(dst, image.Point{X: sr.Min.X + dx, Y: sr.Min.X + dy}, src, sr, op, opts) + return + } + } + + var o Options + if opts != nil { + o = *opts + } + + dr := transformRect(&s2d, &sr) + // adr is the affected destination pixels. + adr := dst.Bounds().Intersect(dr) + adr, o.DstMask = clipAffectedDestRect(adr, o.DstMask, o.DstMaskP) + if adr.Empty() || sr.Empty() { + return + } + if op == Over && o.SrcMask == nil && opaque(src) { + op = Src + } + + d2s := invert(&s2d) + // bias is a translation of the mapping from dst coordinates to src + // coordinates such that the latter temporarily have non-negative X + // and Y coordinates. This allows us to write int(f) instead of + // int(math.Floor(f)), since "round to zero" and "round down" are + // equivalent when f >= 0, but the former is much cheaper. The X-- + // and Y-- are because the TransformLeaf methods have a "sx -= 0.5" + // adjustment. + bias := transformRect(&d2s, &adr).Min + bias.X-- + bias.Y-- + d2s[2] -= float64(bias.X) + d2s[5] -= float64(bias.Y) + // Make adr relative to dr.Min. + adr = adr.Sub(dr.Min) + // sr is the source pixels. If it extends beyond the src bounds, + // we cannot use the type-specific fast paths, as they access + // the Pix fields directly without bounds checking. + // + // Similarly, the fast paths assume that the masks are nil. + if o.DstMask != nil || o.SrcMask != nil || !sr.In(src.Bounds()) { + switch op { + case Over: + z.transform_Image_Image_Over(dst, dr, adr, &d2s, src, sr, bias, &o) + case Src: + z.transform_Image_Image_Src(dst, dr, adr, &d2s, src, sr, bias, &o) + } + } else if u, ok := src.(*image.Uniform); ok { + transform_Uniform(dst, dr, adr, &d2s, u, sr, bias, op) + } else { + switch op { + case Over: + switch dst := dst.(type) { + case *image.RGBA: + switch src := src.(type) { + case *image.NRGBA: + z.transform_RGBA_NRGBA_Over(dst, dr, adr, &d2s, src, sr, bias, &o) + case *image.RGBA: + z.transform_RGBA_RGBA_Over(dst, dr, adr, &d2s, src, sr, bias, &o) + default: + z.transform_RGBA_Image_Over(dst, dr, adr, &d2s, src, sr, bias, &o) + } + default: + switch src := src.(type) { + default: + z.transform_Image_Image_Over(dst, dr, adr, &d2s, src, sr, bias, &o) + } + } + case Src: + switch dst := dst.(type) { + case *image.RGBA: + switch src := src.(type) { + case *image.Gray: + z.transform_RGBA_Gray_Src(dst, dr, adr, &d2s, src, sr, bias, &o) + case *image.NRGBA: + z.transform_RGBA_NRGBA_Src(dst, dr, adr, &d2s, src, sr, bias, &o) + case *image.RGBA: + z.transform_RGBA_RGBA_Src(dst, dr, adr, &d2s, src, sr, bias, &o) + case *image.YCbCr: + switch src.SubsampleRatio { + default: + z.transform_RGBA_Image_Src(dst, dr, adr, &d2s, src, sr, bias, &o) + case image.YCbCrSubsampleRatio444: + z.transform_RGBA_YCbCr444_Src(dst, dr, adr, &d2s, src, sr, bias, &o) + case image.YCbCrSubsampleRatio422: + z.transform_RGBA_YCbCr422_Src(dst, dr, adr, &d2s, src, sr, bias, &o) + case image.YCbCrSubsampleRatio420: + z.transform_RGBA_YCbCr420_Src(dst, dr, adr, &d2s, src, sr, bias, &o) + case image.YCbCrSubsampleRatio440: + z.transform_RGBA_YCbCr440_Src(dst, dr, adr, &d2s, src, sr, bias, &o) + } + default: + z.transform_RGBA_Image_Src(dst, dr, adr, &d2s, src, sr, bias, &o) + } + default: + switch src := src.(type) { + default: + z.transform_Image_Image_Src(dst, dr, adr, &d2s, src, sr, bias, &o) + } + } + } + } +} + +func (ablInterpolator) scale_RGBA_Gray_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.Gray, sr image.Rectangle, opts *Options) { + sw := int32(sr.Dx()) + sh := int32(sr.Dy()) + yscale := float64(sh) / float64(dr.Dy()) + xscale := float64(sw) / float64(dr.Dx()) + swMinus1, shMinus1 := sw-1, sh-1 + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (float64(dy)+0.5)*yscale - 0.5 + // If sy < 0, we will clamp sy0 to 0 anyway, so it doesn't matter if + // we say int32(sy) instead of int32(math.Floor(sy)). Similarly for + // sx, below. + sy0 := int32(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy1 := sy0 + 1 + if sy < 0 { + sy0, sy1 = 0, 0 + yFrac0, yFrac1 = 0, 1 + } else if sy1 > shMinus1 { + sy0, sy1 = shMinus1, shMinus1 + yFrac0, yFrac1 = 1, 0 + } + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + sx := (float64(dx)+0.5)*xscale - 0.5 + sx0 := int32(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx1 := sx0 + 1 + if sx < 0 { + sx0, sx1 = 0, 0 + xFrac0, xFrac1 = 0, 1 + } else if sx1 > swMinus1 { + sx0, sx1 = swMinus1, swMinus1 + xFrac0, xFrac1 = 1, 0 + } + + s00i := (sr.Min.Y+int(sy0)-src.Rect.Min.Y)*src.Stride + (sr.Min.X + int(sx0) - src.Rect.Min.X) + s00ru := uint32(src.Pix[s00i]) * 0x101 + s00r := float64(s00ru) + s10i := (sr.Min.Y+int(sy0)-src.Rect.Min.Y)*src.Stride + (sr.Min.X + int(sx1) - src.Rect.Min.X) + s10ru := uint32(src.Pix[s10i]) * 0x101 + s10r := float64(s10ru) + s10r = xFrac1*s00r + xFrac0*s10r + s01i := (sr.Min.Y+int(sy1)-src.Rect.Min.Y)*src.Stride + (sr.Min.X + int(sx0) - src.Rect.Min.X) + s01ru := uint32(src.Pix[s01i]) * 0x101 + s01r := float64(s01ru) + s11i := (sr.Min.Y+int(sy1)-src.Rect.Min.Y)*src.Stride + (sr.Min.X + int(sx1) - src.Rect.Min.X) + s11ru := uint32(src.Pix[s11i]) * 0x101 + s11r := float64(s11ru) + s11r = xFrac1*s01r + xFrac0*s11r + s11r = yFrac1*s10r + yFrac0*s11r + pr := uint32(s11r) + out := uint8(pr >> 8) + dst.Pix[d+0] = out + dst.Pix[d+1] = out + dst.Pix[d+2] = out + dst.Pix[d+3] = 0xff + } + } +} + +func (ablInterpolator) scale_RGBA_NRGBA_Over(dst *image.RGBA, dr, adr image.Rectangle, src *image.NRGBA, sr image.Rectangle, opts *Options) { + sw := int32(sr.Dx()) + sh := int32(sr.Dy()) + yscale := float64(sh) / float64(dr.Dy()) + xscale := float64(sw) / float64(dr.Dx()) + swMinus1, shMinus1 := sw-1, sh-1 + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (float64(dy)+0.5)*yscale - 0.5 + // If sy < 0, we will clamp sy0 to 0 anyway, so it doesn't matter if + // we say int32(sy) instead of int32(math.Floor(sy)). Similarly for + // sx, below. + sy0 := int32(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy1 := sy0 + 1 + if sy < 0 { + sy0, sy1 = 0, 0 + yFrac0, yFrac1 = 0, 1 + } else if sy1 > shMinus1 { + sy0, sy1 = shMinus1, shMinus1 + yFrac0, yFrac1 = 1, 0 + } + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + sx := (float64(dx)+0.5)*xscale - 0.5 + sx0 := int32(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx1 := sx0 + 1 + if sx < 0 { + sx0, sx1 = 0, 0 + xFrac0, xFrac1 = 0, 1 + } else if sx1 > swMinus1 { + sx0, sx1 = swMinus1, swMinus1 + xFrac0, xFrac1 = 1, 0 + } + + s00i := (sr.Min.Y+int(sy0)-src.Rect.Min.Y)*src.Stride + (sr.Min.X+int(sx0)-src.Rect.Min.X)*4 + s00au := uint32(src.Pix[s00i+3]) * 0x101 + s00ru := uint32(src.Pix[s00i+0]) * s00au / 0xff + s00gu := uint32(src.Pix[s00i+1]) * s00au / 0xff + s00bu := uint32(src.Pix[s00i+2]) * s00au / 0xff + s00r := float64(s00ru) + s00g := float64(s00gu) + s00b := float64(s00bu) + s00a := float64(s00au) + s10i := (sr.Min.Y+int(sy0)-src.Rect.Min.Y)*src.Stride + (sr.Min.X+int(sx1)-src.Rect.Min.X)*4 + s10au := uint32(src.Pix[s10i+3]) * 0x101 + s10ru := uint32(src.Pix[s10i+0]) * s10au / 0xff + s10gu := uint32(src.Pix[s10i+1]) * s10au / 0xff + s10bu := uint32(src.Pix[s10i+2]) * s10au / 0xff + s10r := float64(s10ru) + s10g := float64(s10gu) + s10b := float64(s10bu) + s10a := float64(s10au) + s10r = xFrac1*s00r + xFrac0*s10r + s10g = xFrac1*s00g + xFrac0*s10g + s10b = xFrac1*s00b + xFrac0*s10b + s10a = xFrac1*s00a + xFrac0*s10a + s01i := (sr.Min.Y+int(sy1)-src.Rect.Min.Y)*src.Stride + (sr.Min.X+int(sx0)-src.Rect.Min.X)*4 + s01au := uint32(src.Pix[s01i+3]) * 0x101 + s01ru := uint32(src.Pix[s01i+0]) * s01au / 0xff + s01gu := uint32(src.Pix[s01i+1]) * s01au / 0xff + s01bu := uint32(src.Pix[s01i+2]) * s01au / 0xff + s01r := float64(s01ru) + s01g := float64(s01gu) + s01b := float64(s01bu) + s01a := float64(s01au) + s11i := (sr.Min.Y+int(sy1)-src.Rect.Min.Y)*src.Stride + (sr.Min.X+int(sx1)-src.Rect.Min.X)*4 + s11au := uint32(src.Pix[s11i+3]) * 0x101 + s11ru := uint32(src.Pix[s11i+0]) * s11au / 0xff + s11gu := uint32(src.Pix[s11i+1]) * s11au / 0xff + s11bu := uint32(src.Pix[s11i+2]) * s11au / 0xff + s11r := float64(s11ru) + s11g := float64(s11gu) + s11b := float64(s11bu) + s11a := float64(s11au) + s11r = xFrac1*s01r + xFrac0*s11r + s11g = xFrac1*s01g + xFrac0*s11g + s11b = xFrac1*s01b + xFrac0*s11b + s11a = xFrac1*s01a + xFrac0*s11a + s11r = yFrac1*s10r + yFrac0*s11r + s11g = yFrac1*s10g + yFrac0*s11g + s11b = yFrac1*s10b + yFrac0*s11b + s11a = yFrac1*s10a + yFrac0*s11a + pr := uint32(s11r) + pg := uint32(s11g) + pb := uint32(s11b) + pa := uint32(s11a) + pa1 := (0xffff - pa) * 0x101 + dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*pa1/0xffff + pr) >> 8) + dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*pa1/0xffff + pg) >> 8) + dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*pa1/0xffff + pb) >> 8) + dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*pa1/0xffff + pa) >> 8) + } + } +} + +func (ablInterpolator) scale_RGBA_NRGBA_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.NRGBA, sr image.Rectangle, opts *Options) { + sw := int32(sr.Dx()) + sh := int32(sr.Dy()) + yscale := float64(sh) / float64(dr.Dy()) + xscale := float64(sw) / float64(dr.Dx()) + swMinus1, shMinus1 := sw-1, sh-1 + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (float64(dy)+0.5)*yscale - 0.5 + // If sy < 0, we will clamp sy0 to 0 anyway, so it doesn't matter if + // we say int32(sy) instead of int32(math.Floor(sy)). Similarly for + // sx, below. + sy0 := int32(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy1 := sy0 + 1 + if sy < 0 { + sy0, sy1 = 0, 0 + yFrac0, yFrac1 = 0, 1 + } else if sy1 > shMinus1 { + sy0, sy1 = shMinus1, shMinus1 + yFrac0, yFrac1 = 1, 0 + } + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + sx := (float64(dx)+0.5)*xscale - 0.5 + sx0 := int32(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx1 := sx0 + 1 + if sx < 0 { + sx0, sx1 = 0, 0 + xFrac0, xFrac1 = 0, 1 + } else if sx1 > swMinus1 { + sx0, sx1 = swMinus1, swMinus1 + xFrac0, xFrac1 = 1, 0 + } + + s00i := (sr.Min.Y+int(sy0)-src.Rect.Min.Y)*src.Stride + (sr.Min.X+int(sx0)-src.Rect.Min.X)*4 + s00au := uint32(src.Pix[s00i+3]) * 0x101 + s00ru := uint32(src.Pix[s00i+0]) * s00au / 0xff + s00gu := uint32(src.Pix[s00i+1]) * s00au / 0xff + s00bu := uint32(src.Pix[s00i+2]) * s00au / 0xff + s00r := float64(s00ru) + s00g := float64(s00gu) + s00b := float64(s00bu) + s00a := float64(s00au) + s10i := (sr.Min.Y+int(sy0)-src.Rect.Min.Y)*src.Stride + (sr.Min.X+int(sx1)-src.Rect.Min.X)*4 + s10au := uint32(src.Pix[s10i+3]) * 0x101 + s10ru := uint32(src.Pix[s10i+0]) * s10au / 0xff + s10gu := uint32(src.Pix[s10i+1]) * s10au / 0xff + s10bu := uint32(src.Pix[s10i+2]) * s10au / 0xff + s10r := float64(s10ru) + s10g := float64(s10gu) + s10b := float64(s10bu) + s10a := float64(s10au) + s10r = xFrac1*s00r + xFrac0*s10r + s10g = xFrac1*s00g + xFrac0*s10g + s10b = xFrac1*s00b + xFrac0*s10b + s10a = xFrac1*s00a + xFrac0*s10a + s01i := (sr.Min.Y+int(sy1)-src.Rect.Min.Y)*src.Stride + (sr.Min.X+int(sx0)-src.Rect.Min.X)*4 + s01au := uint32(src.Pix[s01i+3]) * 0x101 + s01ru := uint32(src.Pix[s01i+0]) * s01au / 0xff + s01gu := uint32(src.Pix[s01i+1]) * s01au / 0xff + s01bu := uint32(src.Pix[s01i+2]) * s01au / 0xff + s01r := float64(s01ru) + s01g := float64(s01gu) + s01b := float64(s01bu) + s01a := float64(s01au) + s11i := (sr.Min.Y+int(sy1)-src.Rect.Min.Y)*src.Stride + (sr.Min.X+int(sx1)-src.Rect.Min.X)*4 + s11au := uint32(src.Pix[s11i+3]) * 0x101 + s11ru := uint32(src.Pix[s11i+0]) * s11au / 0xff + s11gu := uint32(src.Pix[s11i+1]) * s11au / 0xff + s11bu := uint32(src.Pix[s11i+2]) * s11au / 0xff + s11r := float64(s11ru) + s11g := float64(s11gu) + s11b := float64(s11bu) + s11a := float64(s11au) + s11r = xFrac1*s01r + xFrac0*s11r + s11g = xFrac1*s01g + xFrac0*s11g + s11b = xFrac1*s01b + xFrac0*s11b + s11a = xFrac1*s01a + xFrac0*s11a + s11r = yFrac1*s10r + yFrac0*s11r + s11g = yFrac1*s10g + yFrac0*s11g + s11b = yFrac1*s10b + yFrac0*s11b + s11a = yFrac1*s10a + yFrac0*s11a + pr := uint32(s11r) + pg := uint32(s11g) + pb := uint32(s11b) + pa := uint32(s11a) + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = uint8(pa >> 8) + } + } +} + +func (ablInterpolator) scale_RGBA_RGBA_Over(dst *image.RGBA, dr, adr image.Rectangle, src *image.RGBA, sr image.Rectangle, opts *Options) { + sw := int32(sr.Dx()) + sh := int32(sr.Dy()) + yscale := float64(sh) / float64(dr.Dy()) + xscale := float64(sw) / float64(dr.Dx()) + swMinus1, shMinus1 := sw-1, sh-1 + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (float64(dy)+0.5)*yscale - 0.5 + // If sy < 0, we will clamp sy0 to 0 anyway, so it doesn't matter if + // we say int32(sy) instead of int32(math.Floor(sy)). Similarly for + // sx, below. + sy0 := int32(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy1 := sy0 + 1 + if sy < 0 { + sy0, sy1 = 0, 0 + yFrac0, yFrac1 = 0, 1 + } else if sy1 > shMinus1 { + sy0, sy1 = shMinus1, shMinus1 + yFrac0, yFrac1 = 1, 0 + } + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + sx := (float64(dx)+0.5)*xscale - 0.5 + sx0 := int32(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx1 := sx0 + 1 + if sx < 0 { + sx0, sx1 = 0, 0 + xFrac0, xFrac1 = 0, 1 + } else if sx1 > swMinus1 { + sx0, sx1 = swMinus1, swMinus1 + xFrac0, xFrac1 = 1, 0 + } + + s00i := (sr.Min.Y+int(sy0)-src.Rect.Min.Y)*src.Stride + (sr.Min.X+int(sx0)-src.Rect.Min.X)*4 + s00ru := uint32(src.Pix[s00i+0]) * 0x101 + s00gu := uint32(src.Pix[s00i+1]) * 0x101 + s00bu := uint32(src.Pix[s00i+2]) * 0x101 + s00au := uint32(src.Pix[s00i+3]) * 0x101 + s00r := float64(s00ru) + s00g := float64(s00gu) + s00b := float64(s00bu) + s00a := float64(s00au) + s10i := (sr.Min.Y+int(sy0)-src.Rect.Min.Y)*src.Stride + (sr.Min.X+int(sx1)-src.Rect.Min.X)*4 + s10ru := uint32(src.Pix[s10i+0]) * 0x101 + s10gu := uint32(src.Pix[s10i+1]) * 0x101 + s10bu := uint32(src.Pix[s10i+2]) * 0x101 + s10au := uint32(src.Pix[s10i+3]) * 0x101 + s10r := float64(s10ru) + s10g := float64(s10gu) + s10b := float64(s10bu) + s10a := float64(s10au) + s10r = xFrac1*s00r + xFrac0*s10r + s10g = xFrac1*s00g + xFrac0*s10g + s10b = xFrac1*s00b + xFrac0*s10b + s10a = xFrac1*s00a + xFrac0*s10a + s01i := (sr.Min.Y+int(sy1)-src.Rect.Min.Y)*src.Stride + (sr.Min.X+int(sx0)-src.Rect.Min.X)*4 + s01ru := uint32(src.Pix[s01i+0]) * 0x101 + s01gu := uint32(src.Pix[s01i+1]) * 0x101 + s01bu := uint32(src.Pix[s01i+2]) * 0x101 + s01au := uint32(src.Pix[s01i+3]) * 0x101 + s01r := float64(s01ru) + s01g := float64(s01gu) + s01b := float64(s01bu) + s01a := float64(s01au) + s11i := (sr.Min.Y+int(sy1)-src.Rect.Min.Y)*src.Stride + (sr.Min.X+int(sx1)-src.Rect.Min.X)*4 + s11ru := uint32(src.Pix[s11i+0]) * 0x101 + s11gu := uint32(src.Pix[s11i+1]) * 0x101 + s11bu := uint32(src.Pix[s11i+2]) * 0x101 + s11au := uint32(src.Pix[s11i+3]) * 0x101 + s11r := float64(s11ru) + s11g := float64(s11gu) + s11b := float64(s11bu) + s11a := float64(s11au) + s11r = xFrac1*s01r + xFrac0*s11r + s11g = xFrac1*s01g + xFrac0*s11g + s11b = xFrac1*s01b + xFrac0*s11b + s11a = xFrac1*s01a + xFrac0*s11a + s11r = yFrac1*s10r + yFrac0*s11r + s11g = yFrac1*s10g + yFrac0*s11g + s11b = yFrac1*s10b + yFrac0*s11b + s11a = yFrac1*s10a + yFrac0*s11a + pr := uint32(s11r) + pg := uint32(s11g) + pb := uint32(s11b) + pa := uint32(s11a) + pa1 := (0xffff - pa) * 0x101 + dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*pa1/0xffff + pr) >> 8) + dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*pa1/0xffff + pg) >> 8) + dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*pa1/0xffff + pb) >> 8) + dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*pa1/0xffff + pa) >> 8) + } + } +} + +func (ablInterpolator) scale_RGBA_RGBA_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.RGBA, sr image.Rectangle, opts *Options) { + sw := int32(sr.Dx()) + sh := int32(sr.Dy()) + yscale := float64(sh) / float64(dr.Dy()) + xscale := float64(sw) / float64(dr.Dx()) + swMinus1, shMinus1 := sw-1, sh-1 + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (float64(dy)+0.5)*yscale - 0.5 + // If sy < 0, we will clamp sy0 to 0 anyway, so it doesn't matter if + // we say int32(sy) instead of int32(math.Floor(sy)). Similarly for + // sx, below. + sy0 := int32(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy1 := sy0 + 1 + if sy < 0 { + sy0, sy1 = 0, 0 + yFrac0, yFrac1 = 0, 1 + } else if sy1 > shMinus1 { + sy0, sy1 = shMinus1, shMinus1 + yFrac0, yFrac1 = 1, 0 + } + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + sx := (float64(dx)+0.5)*xscale - 0.5 + sx0 := int32(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx1 := sx0 + 1 + if sx < 0 { + sx0, sx1 = 0, 0 + xFrac0, xFrac1 = 0, 1 + } else if sx1 > swMinus1 { + sx0, sx1 = swMinus1, swMinus1 + xFrac0, xFrac1 = 1, 0 + } + + s00i := (sr.Min.Y+int(sy0)-src.Rect.Min.Y)*src.Stride + (sr.Min.X+int(sx0)-src.Rect.Min.X)*4 + s00ru := uint32(src.Pix[s00i+0]) * 0x101 + s00gu := uint32(src.Pix[s00i+1]) * 0x101 + s00bu := uint32(src.Pix[s00i+2]) * 0x101 + s00au := uint32(src.Pix[s00i+3]) * 0x101 + s00r := float64(s00ru) + s00g := float64(s00gu) + s00b := float64(s00bu) + s00a := float64(s00au) + s10i := (sr.Min.Y+int(sy0)-src.Rect.Min.Y)*src.Stride + (sr.Min.X+int(sx1)-src.Rect.Min.X)*4 + s10ru := uint32(src.Pix[s10i+0]) * 0x101 + s10gu := uint32(src.Pix[s10i+1]) * 0x101 + s10bu := uint32(src.Pix[s10i+2]) * 0x101 + s10au := uint32(src.Pix[s10i+3]) * 0x101 + s10r := float64(s10ru) + s10g := float64(s10gu) + s10b := float64(s10bu) + s10a := float64(s10au) + s10r = xFrac1*s00r + xFrac0*s10r + s10g = xFrac1*s00g + xFrac0*s10g + s10b = xFrac1*s00b + xFrac0*s10b + s10a = xFrac1*s00a + xFrac0*s10a + s01i := (sr.Min.Y+int(sy1)-src.Rect.Min.Y)*src.Stride + (sr.Min.X+int(sx0)-src.Rect.Min.X)*4 + s01ru := uint32(src.Pix[s01i+0]) * 0x101 + s01gu := uint32(src.Pix[s01i+1]) * 0x101 + s01bu := uint32(src.Pix[s01i+2]) * 0x101 + s01au := uint32(src.Pix[s01i+3]) * 0x101 + s01r := float64(s01ru) + s01g := float64(s01gu) + s01b := float64(s01bu) + s01a := float64(s01au) + s11i := (sr.Min.Y+int(sy1)-src.Rect.Min.Y)*src.Stride + (sr.Min.X+int(sx1)-src.Rect.Min.X)*4 + s11ru := uint32(src.Pix[s11i+0]) * 0x101 + s11gu := uint32(src.Pix[s11i+1]) * 0x101 + s11bu := uint32(src.Pix[s11i+2]) * 0x101 + s11au := uint32(src.Pix[s11i+3]) * 0x101 + s11r := float64(s11ru) + s11g := float64(s11gu) + s11b := float64(s11bu) + s11a := float64(s11au) + s11r = xFrac1*s01r + xFrac0*s11r + s11g = xFrac1*s01g + xFrac0*s11g + s11b = xFrac1*s01b + xFrac0*s11b + s11a = xFrac1*s01a + xFrac0*s11a + s11r = yFrac1*s10r + yFrac0*s11r + s11g = yFrac1*s10g + yFrac0*s11g + s11b = yFrac1*s10b + yFrac0*s11b + s11a = yFrac1*s10a + yFrac0*s11a + pr := uint32(s11r) + pg := uint32(s11g) + pb := uint32(s11b) + pa := uint32(s11a) + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = uint8(pa >> 8) + } + } +} + +func (ablInterpolator) scale_RGBA_YCbCr444_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.YCbCr, sr image.Rectangle, opts *Options) { + sw := int32(sr.Dx()) + sh := int32(sr.Dy()) + yscale := float64(sh) / float64(dr.Dy()) + xscale := float64(sw) / float64(dr.Dx()) + swMinus1, shMinus1 := sw-1, sh-1 + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (float64(dy)+0.5)*yscale - 0.5 + // If sy < 0, we will clamp sy0 to 0 anyway, so it doesn't matter if + // we say int32(sy) instead of int32(math.Floor(sy)). Similarly for + // sx, below. + sy0 := int32(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy1 := sy0 + 1 + if sy < 0 { + sy0, sy1 = 0, 0 + yFrac0, yFrac1 = 0, 1 + } else if sy1 > shMinus1 { + sy0, sy1 = shMinus1, shMinus1 + yFrac0, yFrac1 = 1, 0 + } + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + sx := (float64(dx)+0.5)*xscale - 0.5 + sx0 := int32(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx1 := sx0 + 1 + if sx < 0 { + sx0, sx1 = 0, 0 + xFrac0, xFrac1 = 0, 1 + } else if sx1 > swMinus1 { + sx0, sx1 = swMinus1, swMinus1 + xFrac0, xFrac1 = 1, 0 + } + + s00i := (sr.Min.Y+int(sy0)-src.Rect.Min.Y)*src.YStride + (sr.Min.X + int(sx0) - src.Rect.Min.X) + s00j := (sr.Min.Y+int(sy0)-src.Rect.Min.Y)*src.CStride + (sr.Min.X + int(sx0) - src.Rect.Min.X) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s00yy1 := int(src.Y[s00i]) * 0x10100 + s00cb1 := int(src.Cb[s00j]) - 128 + s00cr1 := int(src.Cr[s00j]) - 128 + s00ru := (s00yy1 + 91881*s00cr1) >> 8 + s00gu := (s00yy1 - 22554*s00cb1 - 46802*s00cr1) >> 8 + s00bu := (s00yy1 + 116130*s00cb1) >> 8 + if s00ru < 0 { + s00ru = 0 + } else if s00ru > 0xffff { + s00ru = 0xffff + } + if s00gu < 0 { + s00gu = 0 + } else if s00gu > 0xffff { + s00gu = 0xffff + } + if s00bu < 0 { + s00bu = 0 + } else if s00bu > 0xffff { + s00bu = 0xffff + } + + s00r := float64(s00ru) + s00g := float64(s00gu) + s00b := float64(s00bu) + s10i := (sr.Min.Y+int(sy0)-src.Rect.Min.Y)*src.YStride + (sr.Min.X + int(sx1) - src.Rect.Min.X) + s10j := (sr.Min.Y+int(sy0)-src.Rect.Min.Y)*src.CStride + (sr.Min.X + int(sx1) - src.Rect.Min.X) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s10yy1 := int(src.Y[s10i]) * 0x10100 + s10cb1 := int(src.Cb[s10j]) - 128 + s10cr1 := int(src.Cr[s10j]) - 128 + s10ru := (s10yy1 + 91881*s10cr1) >> 8 + s10gu := (s10yy1 - 22554*s10cb1 - 46802*s10cr1) >> 8 + s10bu := (s10yy1 + 116130*s10cb1) >> 8 + if s10ru < 0 { + s10ru = 0 + } else if s10ru > 0xffff { + s10ru = 0xffff + } + if s10gu < 0 { + s10gu = 0 + } else if s10gu > 0xffff { + s10gu = 0xffff + } + if s10bu < 0 { + s10bu = 0 + } else if s10bu > 0xffff { + s10bu = 0xffff + } + + s10r := float64(s10ru) + s10g := float64(s10gu) + s10b := float64(s10bu) + s10r = xFrac1*s00r + xFrac0*s10r + s10g = xFrac1*s00g + xFrac0*s10g + s10b = xFrac1*s00b + xFrac0*s10b + s01i := (sr.Min.Y+int(sy1)-src.Rect.Min.Y)*src.YStride + (sr.Min.X + int(sx0) - src.Rect.Min.X) + s01j := (sr.Min.Y+int(sy1)-src.Rect.Min.Y)*src.CStride + (sr.Min.X + int(sx0) - src.Rect.Min.X) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s01yy1 := int(src.Y[s01i]) * 0x10100 + s01cb1 := int(src.Cb[s01j]) - 128 + s01cr1 := int(src.Cr[s01j]) - 128 + s01ru := (s01yy1 + 91881*s01cr1) >> 8 + s01gu := (s01yy1 - 22554*s01cb1 - 46802*s01cr1) >> 8 + s01bu := (s01yy1 + 116130*s01cb1) >> 8 + if s01ru < 0 { + s01ru = 0 + } else if s01ru > 0xffff { + s01ru = 0xffff + } + if s01gu < 0 { + s01gu = 0 + } else if s01gu > 0xffff { + s01gu = 0xffff + } + if s01bu < 0 { + s01bu = 0 + } else if s01bu > 0xffff { + s01bu = 0xffff + } + + s01r := float64(s01ru) + s01g := float64(s01gu) + s01b := float64(s01bu) + s11i := (sr.Min.Y+int(sy1)-src.Rect.Min.Y)*src.YStride + (sr.Min.X + int(sx1) - src.Rect.Min.X) + s11j := (sr.Min.Y+int(sy1)-src.Rect.Min.Y)*src.CStride + (sr.Min.X + int(sx1) - src.Rect.Min.X) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s11yy1 := int(src.Y[s11i]) * 0x10100 + s11cb1 := int(src.Cb[s11j]) - 128 + s11cr1 := int(src.Cr[s11j]) - 128 + s11ru := (s11yy1 + 91881*s11cr1) >> 8 + s11gu := (s11yy1 - 22554*s11cb1 - 46802*s11cr1) >> 8 + s11bu := (s11yy1 + 116130*s11cb1) >> 8 + if s11ru < 0 { + s11ru = 0 + } else if s11ru > 0xffff { + s11ru = 0xffff + } + if s11gu < 0 { + s11gu = 0 + } else if s11gu > 0xffff { + s11gu = 0xffff + } + if s11bu < 0 { + s11bu = 0 + } else if s11bu > 0xffff { + s11bu = 0xffff + } + + s11r := float64(s11ru) + s11g := float64(s11gu) + s11b := float64(s11bu) + s11r = xFrac1*s01r + xFrac0*s11r + s11g = xFrac1*s01g + xFrac0*s11g + s11b = xFrac1*s01b + xFrac0*s11b + s11r = yFrac1*s10r + yFrac0*s11r + s11g = yFrac1*s10g + yFrac0*s11g + s11b = yFrac1*s10b + yFrac0*s11b + pr := uint32(s11r) + pg := uint32(s11g) + pb := uint32(s11b) + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = 0xff + } + } +} + +func (ablInterpolator) scale_RGBA_YCbCr422_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.YCbCr, sr image.Rectangle, opts *Options) { + sw := int32(sr.Dx()) + sh := int32(sr.Dy()) + yscale := float64(sh) / float64(dr.Dy()) + xscale := float64(sw) / float64(dr.Dx()) + swMinus1, shMinus1 := sw-1, sh-1 + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (float64(dy)+0.5)*yscale - 0.5 + // If sy < 0, we will clamp sy0 to 0 anyway, so it doesn't matter if + // we say int32(sy) instead of int32(math.Floor(sy)). Similarly for + // sx, below. + sy0 := int32(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy1 := sy0 + 1 + if sy < 0 { + sy0, sy1 = 0, 0 + yFrac0, yFrac1 = 0, 1 + } else if sy1 > shMinus1 { + sy0, sy1 = shMinus1, shMinus1 + yFrac0, yFrac1 = 1, 0 + } + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + sx := (float64(dx)+0.5)*xscale - 0.5 + sx0 := int32(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx1 := sx0 + 1 + if sx < 0 { + sx0, sx1 = 0, 0 + xFrac0, xFrac1 = 0, 1 + } else if sx1 > swMinus1 { + sx0, sx1 = swMinus1, swMinus1 + xFrac0, xFrac1 = 1, 0 + } + + s00i := (sr.Min.Y+int(sy0)-src.Rect.Min.Y)*src.YStride + (sr.Min.X + int(sx0) - src.Rect.Min.X) + s00j := (sr.Min.Y+int(sy0)-src.Rect.Min.Y)*src.CStride + ((sr.Min.X+int(sx0))/2 - src.Rect.Min.X/2) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s00yy1 := int(src.Y[s00i]) * 0x10100 + s00cb1 := int(src.Cb[s00j]) - 128 + s00cr1 := int(src.Cr[s00j]) - 128 + s00ru := (s00yy1 + 91881*s00cr1) >> 8 + s00gu := (s00yy1 - 22554*s00cb1 - 46802*s00cr1) >> 8 + s00bu := (s00yy1 + 116130*s00cb1) >> 8 + if s00ru < 0 { + s00ru = 0 + } else if s00ru > 0xffff { + s00ru = 0xffff + } + if s00gu < 0 { + s00gu = 0 + } else if s00gu > 0xffff { + s00gu = 0xffff + } + if s00bu < 0 { + s00bu = 0 + } else if s00bu > 0xffff { + s00bu = 0xffff + } + + s00r := float64(s00ru) + s00g := float64(s00gu) + s00b := float64(s00bu) + s10i := (sr.Min.Y+int(sy0)-src.Rect.Min.Y)*src.YStride + (sr.Min.X + int(sx1) - src.Rect.Min.X) + s10j := (sr.Min.Y+int(sy0)-src.Rect.Min.Y)*src.CStride + ((sr.Min.X+int(sx1))/2 - src.Rect.Min.X/2) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s10yy1 := int(src.Y[s10i]) * 0x10100 + s10cb1 := int(src.Cb[s10j]) - 128 + s10cr1 := int(src.Cr[s10j]) - 128 + s10ru := (s10yy1 + 91881*s10cr1) >> 8 + s10gu := (s10yy1 - 22554*s10cb1 - 46802*s10cr1) >> 8 + s10bu := (s10yy1 + 116130*s10cb1) >> 8 + if s10ru < 0 { + s10ru = 0 + } else if s10ru > 0xffff { + s10ru = 0xffff + } + if s10gu < 0 { + s10gu = 0 + } else if s10gu > 0xffff { + s10gu = 0xffff + } + if s10bu < 0 { + s10bu = 0 + } else if s10bu > 0xffff { + s10bu = 0xffff + } + + s10r := float64(s10ru) + s10g := float64(s10gu) + s10b := float64(s10bu) + s10r = xFrac1*s00r + xFrac0*s10r + s10g = xFrac1*s00g + xFrac0*s10g + s10b = xFrac1*s00b + xFrac0*s10b + s01i := (sr.Min.Y+int(sy1)-src.Rect.Min.Y)*src.YStride + (sr.Min.X + int(sx0) - src.Rect.Min.X) + s01j := (sr.Min.Y+int(sy1)-src.Rect.Min.Y)*src.CStride + ((sr.Min.X+int(sx0))/2 - src.Rect.Min.X/2) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s01yy1 := int(src.Y[s01i]) * 0x10100 + s01cb1 := int(src.Cb[s01j]) - 128 + s01cr1 := int(src.Cr[s01j]) - 128 + s01ru := (s01yy1 + 91881*s01cr1) >> 8 + s01gu := (s01yy1 - 22554*s01cb1 - 46802*s01cr1) >> 8 + s01bu := (s01yy1 + 116130*s01cb1) >> 8 + if s01ru < 0 { + s01ru = 0 + } else if s01ru > 0xffff { + s01ru = 0xffff + } + if s01gu < 0 { + s01gu = 0 + } else if s01gu > 0xffff { + s01gu = 0xffff + } + if s01bu < 0 { + s01bu = 0 + } else if s01bu > 0xffff { + s01bu = 0xffff + } + + s01r := float64(s01ru) + s01g := float64(s01gu) + s01b := float64(s01bu) + s11i := (sr.Min.Y+int(sy1)-src.Rect.Min.Y)*src.YStride + (sr.Min.X + int(sx1) - src.Rect.Min.X) + s11j := (sr.Min.Y+int(sy1)-src.Rect.Min.Y)*src.CStride + ((sr.Min.X+int(sx1))/2 - src.Rect.Min.X/2) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s11yy1 := int(src.Y[s11i]) * 0x10100 + s11cb1 := int(src.Cb[s11j]) - 128 + s11cr1 := int(src.Cr[s11j]) - 128 + s11ru := (s11yy1 + 91881*s11cr1) >> 8 + s11gu := (s11yy1 - 22554*s11cb1 - 46802*s11cr1) >> 8 + s11bu := (s11yy1 + 116130*s11cb1) >> 8 + if s11ru < 0 { + s11ru = 0 + } else if s11ru > 0xffff { + s11ru = 0xffff + } + if s11gu < 0 { + s11gu = 0 + } else if s11gu > 0xffff { + s11gu = 0xffff + } + if s11bu < 0 { + s11bu = 0 + } else if s11bu > 0xffff { + s11bu = 0xffff + } + + s11r := float64(s11ru) + s11g := float64(s11gu) + s11b := float64(s11bu) + s11r = xFrac1*s01r + xFrac0*s11r + s11g = xFrac1*s01g + xFrac0*s11g + s11b = xFrac1*s01b + xFrac0*s11b + s11r = yFrac1*s10r + yFrac0*s11r + s11g = yFrac1*s10g + yFrac0*s11g + s11b = yFrac1*s10b + yFrac0*s11b + pr := uint32(s11r) + pg := uint32(s11g) + pb := uint32(s11b) + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = 0xff + } + } +} + +func (ablInterpolator) scale_RGBA_YCbCr420_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.YCbCr, sr image.Rectangle, opts *Options) { + sw := int32(sr.Dx()) + sh := int32(sr.Dy()) + yscale := float64(sh) / float64(dr.Dy()) + xscale := float64(sw) / float64(dr.Dx()) + swMinus1, shMinus1 := sw-1, sh-1 + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (float64(dy)+0.5)*yscale - 0.5 + // If sy < 0, we will clamp sy0 to 0 anyway, so it doesn't matter if + // we say int32(sy) instead of int32(math.Floor(sy)). Similarly for + // sx, below. + sy0 := int32(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy1 := sy0 + 1 + if sy < 0 { + sy0, sy1 = 0, 0 + yFrac0, yFrac1 = 0, 1 + } else if sy1 > shMinus1 { + sy0, sy1 = shMinus1, shMinus1 + yFrac0, yFrac1 = 1, 0 + } + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + sx := (float64(dx)+0.5)*xscale - 0.5 + sx0 := int32(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx1 := sx0 + 1 + if sx < 0 { + sx0, sx1 = 0, 0 + xFrac0, xFrac1 = 0, 1 + } else if sx1 > swMinus1 { + sx0, sx1 = swMinus1, swMinus1 + xFrac0, xFrac1 = 1, 0 + } + + s00i := (sr.Min.Y+int(sy0)-src.Rect.Min.Y)*src.YStride + (sr.Min.X + int(sx0) - src.Rect.Min.X) + s00j := ((sr.Min.Y+int(sy0))/2-src.Rect.Min.Y/2)*src.CStride + ((sr.Min.X+int(sx0))/2 - src.Rect.Min.X/2) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s00yy1 := int(src.Y[s00i]) * 0x10100 + s00cb1 := int(src.Cb[s00j]) - 128 + s00cr1 := int(src.Cr[s00j]) - 128 + s00ru := (s00yy1 + 91881*s00cr1) >> 8 + s00gu := (s00yy1 - 22554*s00cb1 - 46802*s00cr1) >> 8 + s00bu := (s00yy1 + 116130*s00cb1) >> 8 + if s00ru < 0 { + s00ru = 0 + } else if s00ru > 0xffff { + s00ru = 0xffff + } + if s00gu < 0 { + s00gu = 0 + } else if s00gu > 0xffff { + s00gu = 0xffff + } + if s00bu < 0 { + s00bu = 0 + } else if s00bu > 0xffff { + s00bu = 0xffff + } + + s00r := float64(s00ru) + s00g := float64(s00gu) + s00b := float64(s00bu) + s10i := (sr.Min.Y+int(sy0)-src.Rect.Min.Y)*src.YStride + (sr.Min.X + int(sx1) - src.Rect.Min.X) + s10j := ((sr.Min.Y+int(sy0))/2-src.Rect.Min.Y/2)*src.CStride + ((sr.Min.X+int(sx1))/2 - src.Rect.Min.X/2) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s10yy1 := int(src.Y[s10i]) * 0x10100 + s10cb1 := int(src.Cb[s10j]) - 128 + s10cr1 := int(src.Cr[s10j]) - 128 + s10ru := (s10yy1 + 91881*s10cr1) >> 8 + s10gu := (s10yy1 - 22554*s10cb1 - 46802*s10cr1) >> 8 + s10bu := (s10yy1 + 116130*s10cb1) >> 8 + if s10ru < 0 { + s10ru = 0 + } else if s10ru > 0xffff { + s10ru = 0xffff + } + if s10gu < 0 { + s10gu = 0 + } else if s10gu > 0xffff { + s10gu = 0xffff + } + if s10bu < 0 { + s10bu = 0 + } else if s10bu > 0xffff { + s10bu = 0xffff + } + + s10r := float64(s10ru) + s10g := float64(s10gu) + s10b := float64(s10bu) + s10r = xFrac1*s00r + xFrac0*s10r + s10g = xFrac1*s00g + xFrac0*s10g + s10b = xFrac1*s00b + xFrac0*s10b + s01i := (sr.Min.Y+int(sy1)-src.Rect.Min.Y)*src.YStride + (sr.Min.X + int(sx0) - src.Rect.Min.X) + s01j := ((sr.Min.Y+int(sy1))/2-src.Rect.Min.Y/2)*src.CStride + ((sr.Min.X+int(sx0))/2 - src.Rect.Min.X/2) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s01yy1 := int(src.Y[s01i]) * 0x10100 + s01cb1 := int(src.Cb[s01j]) - 128 + s01cr1 := int(src.Cr[s01j]) - 128 + s01ru := (s01yy1 + 91881*s01cr1) >> 8 + s01gu := (s01yy1 - 22554*s01cb1 - 46802*s01cr1) >> 8 + s01bu := (s01yy1 + 116130*s01cb1) >> 8 + if s01ru < 0 { + s01ru = 0 + } else if s01ru > 0xffff { + s01ru = 0xffff + } + if s01gu < 0 { + s01gu = 0 + } else if s01gu > 0xffff { + s01gu = 0xffff + } + if s01bu < 0 { + s01bu = 0 + } else if s01bu > 0xffff { + s01bu = 0xffff + } + + s01r := float64(s01ru) + s01g := float64(s01gu) + s01b := float64(s01bu) + s11i := (sr.Min.Y+int(sy1)-src.Rect.Min.Y)*src.YStride + (sr.Min.X + int(sx1) - src.Rect.Min.X) + s11j := ((sr.Min.Y+int(sy1))/2-src.Rect.Min.Y/2)*src.CStride + ((sr.Min.X+int(sx1))/2 - src.Rect.Min.X/2) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s11yy1 := int(src.Y[s11i]) * 0x10100 + s11cb1 := int(src.Cb[s11j]) - 128 + s11cr1 := int(src.Cr[s11j]) - 128 + s11ru := (s11yy1 + 91881*s11cr1) >> 8 + s11gu := (s11yy1 - 22554*s11cb1 - 46802*s11cr1) >> 8 + s11bu := (s11yy1 + 116130*s11cb1) >> 8 + if s11ru < 0 { + s11ru = 0 + } else if s11ru > 0xffff { + s11ru = 0xffff + } + if s11gu < 0 { + s11gu = 0 + } else if s11gu > 0xffff { + s11gu = 0xffff + } + if s11bu < 0 { + s11bu = 0 + } else if s11bu > 0xffff { + s11bu = 0xffff + } + + s11r := float64(s11ru) + s11g := float64(s11gu) + s11b := float64(s11bu) + s11r = xFrac1*s01r + xFrac0*s11r + s11g = xFrac1*s01g + xFrac0*s11g + s11b = xFrac1*s01b + xFrac0*s11b + s11r = yFrac1*s10r + yFrac0*s11r + s11g = yFrac1*s10g + yFrac0*s11g + s11b = yFrac1*s10b + yFrac0*s11b + pr := uint32(s11r) + pg := uint32(s11g) + pb := uint32(s11b) + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = 0xff + } + } +} + +func (ablInterpolator) scale_RGBA_YCbCr440_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.YCbCr, sr image.Rectangle, opts *Options) { + sw := int32(sr.Dx()) + sh := int32(sr.Dy()) + yscale := float64(sh) / float64(dr.Dy()) + xscale := float64(sw) / float64(dr.Dx()) + swMinus1, shMinus1 := sw-1, sh-1 + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (float64(dy)+0.5)*yscale - 0.5 + // If sy < 0, we will clamp sy0 to 0 anyway, so it doesn't matter if + // we say int32(sy) instead of int32(math.Floor(sy)). Similarly for + // sx, below. + sy0 := int32(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy1 := sy0 + 1 + if sy < 0 { + sy0, sy1 = 0, 0 + yFrac0, yFrac1 = 0, 1 + } else if sy1 > shMinus1 { + sy0, sy1 = shMinus1, shMinus1 + yFrac0, yFrac1 = 1, 0 + } + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + sx := (float64(dx)+0.5)*xscale - 0.5 + sx0 := int32(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx1 := sx0 + 1 + if sx < 0 { + sx0, sx1 = 0, 0 + xFrac0, xFrac1 = 0, 1 + } else if sx1 > swMinus1 { + sx0, sx1 = swMinus1, swMinus1 + xFrac0, xFrac1 = 1, 0 + } + + s00i := (sr.Min.Y+int(sy0)-src.Rect.Min.Y)*src.YStride + (sr.Min.X + int(sx0) - src.Rect.Min.X) + s00j := ((sr.Min.Y+int(sy0))/2-src.Rect.Min.Y/2)*src.CStride + (sr.Min.X + int(sx0) - src.Rect.Min.X) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s00yy1 := int(src.Y[s00i]) * 0x10100 + s00cb1 := int(src.Cb[s00j]) - 128 + s00cr1 := int(src.Cr[s00j]) - 128 + s00ru := (s00yy1 + 91881*s00cr1) >> 8 + s00gu := (s00yy1 - 22554*s00cb1 - 46802*s00cr1) >> 8 + s00bu := (s00yy1 + 116130*s00cb1) >> 8 + if s00ru < 0 { + s00ru = 0 + } else if s00ru > 0xffff { + s00ru = 0xffff + } + if s00gu < 0 { + s00gu = 0 + } else if s00gu > 0xffff { + s00gu = 0xffff + } + if s00bu < 0 { + s00bu = 0 + } else if s00bu > 0xffff { + s00bu = 0xffff + } + + s00r := float64(s00ru) + s00g := float64(s00gu) + s00b := float64(s00bu) + s10i := (sr.Min.Y+int(sy0)-src.Rect.Min.Y)*src.YStride + (sr.Min.X + int(sx1) - src.Rect.Min.X) + s10j := ((sr.Min.Y+int(sy0))/2-src.Rect.Min.Y/2)*src.CStride + (sr.Min.X + int(sx1) - src.Rect.Min.X) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s10yy1 := int(src.Y[s10i]) * 0x10100 + s10cb1 := int(src.Cb[s10j]) - 128 + s10cr1 := int(src.Cr[s10j]) - 128 + s10ru := (s10yy1 + 91881*s10cr1) >> 8 + s10gu := (s10yy1 - 22554*s10cb1 - 46802*s10cr1) >> 8 + s10bu := (s10yy1 + 116130*s10cb1) >> 8 + if s10ru < 0 { + s10ru = 0 + } else if s10ru > 0xffff { + s10ru = 0xffff + } + if s10gu < 0 { + s10gu = 0 + } else if s10gu > 0xffff { + s10gu = 0xffff + } + if s10bu < 0 { + s10bu = 0 + } else if s10bu > 0xffff { + s10bu = 0xffff + } + + s10r := float64(s10ru) + s10g := float64(s10gu) + s10b := float64(s10bu) + s10r = xFrac1*s00r + xFrac0*s10r + s10g = xFrac1*s00g + xFrac0*s10g + s10b = xFrac1*s00b + xFrac0*s10b + s01i := (sr.Min.Y+int(sy1)-src.Rect.Min.Y)*src.YStride + (sr.Min.X + int(sx0) - src.Rect.Min.X) + s01j := ((sr.Min.Y+int(sy1))/2-src.Rect.Min.Y/2)*src.CStride + (sr.Min.X + int(sx0) - src.Rect.Min.X) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s01yy1 := int(src.Y[s01i]) * 0x10100 + s01cb1 := int(src.Cb[s01j]) - 128 + s01cr1 := int(src.Cr[s01j]) - 128 + s01ru := (s01yy1 + 91881*s01cr1) >> 8 + s01gu := (s01yy1 - 22554*s01cb1 - 46802*s01cr1) >> 8 + s01bu := (s01yy1 + 116130*s01cb1) >> 8 + if s01ru < 0 { + s01ru = 0 + } else if s01ru > 0xffff { + s01ru = 0xffff + } + if s01gu < 0 { + s01gu = 0 + } else if s01gu > 0xffff { + s01gu = 0xffff + } + if s01bu < 0 { + s01bu = 0 + } else if s01bu > 0xffff { + s01bu = 0xffff + } + + s01r := float64(s01ru) + s01g := float64(s01gu) + s01b := float64(s01bu) + s11i := (sr.Min.Y+int(sy1)-src.Rect.Min.Y)*src.YStride + (sr.Min.X + int(sx1) - src.Rect.Min.X) + s11j := ((sr.Min.Y+int(sy1))/2-src.Rect.Min.Y/2)*src.CStride + (sr.Min.X + int(sx1) - src.Rect.Min.X) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s11yy1 := int(src.Y[s11i]) * 0x10100 + s11cb1 := int(src.Cb[s11j]) - 128 + s11cr1 := int(src.Cr[s11j]) - 128 + s11ru := (s11yy1 + 91881*s11cr1) >> 8 + s11gu := (s11yy1 - 22554*s11cb1 - 46802*s11cr1) >> 8 + s11bu := (s11yy1 + 116130*s11cb1) >> 8 + if s11ru < 0 { + s11ru = 0 + } else if s11ru > 0xffff { + s11ru = 0xffff + } + if s11gu < 0 { + s11gu = 0 + } else if s11gu > 0xffff { + s11gu = 0xffff + } + if s11bu < 0 { + s11bu = 0 + } else if s11bu > 0xffff { + s11bu = 0xffff + } + + s11r := float64(s11ru) + s11g := float64(s11gu) + s11b := float64(s11bu) + s11r = xFrac1*s01r + xFrac0*s11r + s11g = xFrac1*s01g + xFrac0*s11g + s11b = xFrac1*s01b + xFrac0*s11b + s11r = yFrac1*s10r + yFrac0*s11r + s11g = yFrac1*s10g + yFrac0*s11g + s11b = yFrac1*s10b + yFrac0*s11b + pr := uint32(s11r) + pg := uint32(s11g) + pb := uint32(s11b) + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = 0xff + } + } +} + +func (ablInterpolator) scale_RGBA_Image_Over(dst *image.RGBA, dr, adr image.Rectangle, src image.Image, sr image.Rectangle, opts *Options) { + sw := int32(sr.Dx()) + sh := int32(sr.Dy()) + yscale := float64(sh) / float64(dr.Dy()) + xscale := float64(sw) / float64(dr.Dx()) + swMinus1, shMinus1 := sw-1, sh-1 + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (float64(dy)+0.5)*yscale - 0.5 + // If sy < 0, we will clamp sy0 to 0 anyway, so it doesn't matter if + // we say int32(sy) instead of int32(math.Floor(sy)). Similarly for + // sx, below. + sy0 := int32(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy1 := sy0 + 1 + if sy < 0 { + sy0, sy1 = 0, 0 + yFrac0, yFrac1 = 0, 1 + } else if sy1 > shMinus1 { + sy0, sy1 = shMinus1, shMinus1 + yFrac0, yFrac1 = 1, 0 + } + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + sx := (float64(dx)+0.5)*xscale - 0.5 + sx0 := int32(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx1 := sx0 + 1 + if sx < 0 { + sx0, sx1 = 0, 0 + xFrac0, xFrac1 = 0, 1 + } else if sx1 > swMinus1 { + sx0, sx1 = swMinus1, swMinus1 + xFrac0, xFrac1 = 1, 0 + } + + s00ru, s00gu, s00bu, s00au := src.At(sr.Min.X+int(sx0), sr.Min.Y+int(sy0)).RGBA() + s00r := float64(s00ru) + s00g := float64(s00gu) + s00b := float64(s00bu) + s00a := float64(s00au) + s10ru, s10gu, s10bu, s10au := src.At(sr.Min.X+int(sx1), sr.Min.Y+int(sy0)).RGBA() + s10r := float64(s10ru) + s10g := float64(s10gu) + s10b := float64(s10bu) + s10a := float64(s10au) + s10r = xFrac1*s00r + xFrac0*s10r + s10g = xFrac1*s00g + xFrac0*s10g + s10b = xFrac1*s00b + xFrac0*s10b + s10a = xFrac1*s00a + xFrac0*s10a + s01ru, s01gu, s01bu, s01au := src.At(sr.Min.X+int(sx0), sr.Min.Y+int(sy1)).RGBA() + s01r := float64(s01ru) + s01g := float64(s01gu) + s01b := float64(s01bu) + s01a := float64(s01au) + s11ru, s11gu, s11bu, s11au := src.At(sr.Min.X+int(sx1), sr.Min.Y+int(sy1)).RGBA() + s11r := float64(s11ru) + s11g := float64(s11gu) + s11b := float64(s11bu) + s11a := float64(s11au) + s11r = xFrac1*s01r + xFrac0*s11r + s11g = xFrac1*s01g + xFrac0*s11g + s11b = xFrac1*s01b + xFrac0*s11b + s11a = xFrac1*s01a + xFrac0*s11a + s11r = yFrac1*s10r + yFrac0*s11r + s11g = yFrac1*s10g + yFrac0*s11g + s11b = yFrac1*s10b + yFrac0*s11b + s11a = yFrac1*s10a + yFrac0*s11a + pr := uint32(s11r) + pg := uint32(s11g) + pb := uint32(s11b) + pa := uint32(s11a) + pa1 := (0xffff - pa) * 0x101 + dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*pa1/0xffff + pr) >> 8) + dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*pa1/0xffff + pg) >> 8) + dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*pa1/0xffff + pb) >> 8) + dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*pa1/0xffff + pa) >> 8) + } + } +} + +func (ablInterpolator) scale_RGBA_Image_Src(dst *image.RGBA, dr, adr image.Rectangle, src image.Image, sr image.Rectangle, opts *Options) { + sw := int32(sr.Dx()) + sh := int32(sr.Dy()) + yscale := float64(sh) / float64(dr.Dy()) + xscale := float64(sw) / float64(dr.Dx()) + swMinus1, shMinus1 := sw-1, sh-1 + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (float64(dy)+0.5)*yscale - 0.5 + // If sy < 0, we will clamp sy0 to 0 anyway, so it doesn't matter if + // we say int32(sy) instead of int32(math.Floor(sy)). Similarly for + // sx, below. + sy0 := int32(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy1 := sy0 + 1 + if sy < 0 { + sy0, sy1 = 0, 0 + yFrac0, yFrac1 = 0, 1 + } else if sy1 > shMinus1 { + sy0, sy1 = shMinus1, shMinus1 + yFrac0, yFrac1 = 1, 0 + } + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + sx := (float64(dx)+0.5)*xscale - 0.5 + sx0 := int32(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx1 := sx0 + 1 + if sx < 0 { + sx0, sx1 = 0, 0 + xFrac0, xFrac1 = 0, 1 + } else if sx1 > swMinus1 { + sx0, sx1 = swMinus1, swMinus1 + xFrac0, xFrac1 = 1, 0 + } + + s00ru, s00gu, s00bu, s00au := src.At(sr.Min.X+int(sx0), sr.Min.Y+int(sy0)).RGBA() + s00r := float64(s00ru) + s00g := float64(s00gu) + s00b := float64(s00bu) + s00a := float64(s00au) + s10ru, s10gu, s10bu, s10au := src.At(sr.Min.X+int(sx1), sr.Min.Y+int(sy0)).RGBA() + s10r := float64(s10ru) + s10g := float64(s10gu) + s10b := float64(s10bu) + s10a := float64(s10au) + s10r = xFrac1*s00r + xFrac0*s10r + s10g = xFrac1*s00g + xFrac0*s10g + s10b = xFrac1*s00b + xFrac0*s10b + s10a = xFrac1*s00a + xFrac0*s10a + s01ru, s01gu, s01bu, s01au := src.At(sr.Min.X+int(sx0), sr.Min.Y+int(sy1)).RGBA() + s01r := float64(s01ru) + s01g := float64(s01gu) + s01b := float64(s01bu) + s01a := float64(s01au) + s11ru, s11gu, s11bu, s11au := src.At(sr.Min.X+int(sx1), sr.Min.Y+int(sy1)).RGBA() + s11r := float64(s11ru) + s11g := float64(s11gu) + s11b := float64(s11bu) + s11a := float64(s11au) + s11r = xFrac1*s01r + xFrac0*s11r + s11g = xFrac1*s01g + xFrac0*s11g + s11b = xFrac1*s01b + xFrac0*s11b + s11a = xFrac1*s01a + xFrac0*s11a + s11r = yFrac1*s10r + yFrac0*s11r + s11g = yFrac1*s10g + yFrac0*s11g + s11b = yFrac1*s10b + yFrac0*s11b + s11a = yFrac1*s10a + yFrac0*s11a + pr := uint32(s11r) + pg := uint32(s11g) + pb := uint32(s11b) + pa := uint32(s11a) + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = uint8(pa >> 8) + } + } +} + +func (ablInterpolator) scale_Image_Image_Over(dst Image, dr, adr image.Rectangle, src image.Image, sr image.Rectangle, opts *Options) { + sw := int32(sr.Dx()) + sh := int32(sr.Dy()) + yscale := float64(sh) / float64(dr.Dy()) + xscale := float64(sw) / float64(dr.Dx()) + swMinus1, shMinus1 := sw-1, sh-1 + srcMask, smp := opts.SrcMask, opts.SrcMaskP + dstMask, dmp := opts.DstMask, opts.DstMaskP + dstColorRGBA64 := &color.RGBA64{} + dstColor := color.Color(dstColorRGBA64) + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (float64(dy)+0.5)*yscale - 0.5 + // If sy < 0, we will clamp sy0 to 0 anyway, so it doesn't matter if + // we say int32(sy) instead of int32(math.Floor(sy)). Similarly for + // sx, below. + sy0 := int32(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy1 := sy0 + 1 + if sy < 0 { + sy0, sy1 = 0, 0 + yFrac0, yFrac1 = 0, 1 + } else if sy1 > shMinus1 { + sy0, sy1 = shMinus1, shMinus1 + yFrac0, yFrac1 = 1, 0 + } + + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { + sx := (float64(dx)+0.5)*xscale - 0.5 + sx0 := int32(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx1 := sx0 + 1 + if sx < 0 { + sx0, sx1 = 0, 0 + xFrac0, xFrac1 = 0, 1 + } else if sx1 > swMinus1 { + sx0, sx1 = swMinus1, swMinus1 + xFrac0, xFrac1 = 1, 0 + } + + s00ru, s00gu, s00bu, s00au := src.At(sr.Min.X+int(sx0), sr.Min.Y+int(sy0)).RGBA() + if srcMask != nil { + _, _, _, ma := srcMask.At(smp.X+sr.Min.X+int(sx0), smp.Y+sr.Min.Y+int(sy0)).RGBA() + s00ru = s00ru * ma / 0xffff + s00gu = s00gu * ma / 0xffff + s00bu = s00bu * ma / 0xffff + s00au = s00au * ma / 0xffff + } + s00r := float64(s00ru) + s00g := float64(s00gu) + s00b := float64(s00bu) + s00a := float64(s00au) + s10ru, s10gu, s10bu, s10au := src.At(sr.Min.X+int(sx1), sr.Min.Y+int(sy0)).RGBA() + if srcMask != nil { + _, _, _, ma := srcMask.At(smp.X+sr.Min.X+int(sx1), smp.Y+sr.Min.Y+int(sy0)).RGBA() + s10ru = s10ru * ma / 0xffff + s10gu = s10gu * ma / 0xffff + s10bu = s10bu * ma / 0xffff + s10au = s10au * ma / 0xffff + } + s10r := float64(s10ru) + s10g := float64(s10gu) + s10b := float64(s10bu) + s10a := float64(s10au) + s10r = xFrac1*s00r + xFrac0*s10r + s10g = xFrac1*s00g + xFrac0*s10g + s10b = xFrac1*s00b + xFrac0*s10b + s10a = xFrac1*s00a + xFrac0*s10a + s01ru, s01gu, s01bu, s01au := src.At(sr.Min.X+int(sx0), sr.Min.Y+int(sy1)).RGBA() + if srcMask != nil { + _, _, _, ma := srcMask.At(smp.X+sr.Min.X+int(sx0), smp.Y+sr.Min.Y+int(sy1)).RGBA() + s01ru = s01ru * ma / 0xffff + s01gu = s01gu * ma / 0xffff + s01bu = s01bu * ma / 0xffff + s01au = s01au * ma / 0xffff + } + s01r := float64(s01ru) + s01g := float64(s01gu) + s01b := float64(s01bu) + s01a := float64(s01au) + s11ru, s11gu, s11bu, s11au := src.At(sr.Min.X+int(sx1), sr.Min.Y+int(sy1)).RGBA() + if srcMask != nil { + _, _, _, ma := srcMask.At(smp.X+sr.Min.X+int(sx1), smp.Y+sr.Min.Y+int(sy1)).RGBA() + s11ru = s11ru * ma / 0xffff + s11gu = s11gu * ma / 0xffff + s11bu = s11bu * ma / 0xffff + s11au = s11au * ma / 0xffff + } + s11r := float64(s11ru) + s11g := float64(s11gu) + s11b := float64(s11bu) + s11a := float64(s11au) + s11r = xFrac1*s01r + xFrac0*s11r + s11g = xFrac1*s01g + xFrac0*s11g + s11b = xFrac1*s01b + xFrac0*s11b + s11a = xFrac1*s01a + xFrac0*s11a + s11r = yFrac1*s10r + yFrac0*s11r + s11g = yFrac1*s10g + yFrac0*s11g + s11b = yFrac1*s10b + yFrac0*s11b + s11a = yFrac1*s10a + yFrac0*s11a + pr := uint32(s11r) + pg := uint32(s11g) + pb := uint32(s11b) + pa := uint32(s11a) + qr, qg, qb, qa := dst.At(dr.Min.X+int(dx), dr.Min.Y+int(dy)).RGBA() + if dstMask != nil { + _, _, _, ma := dstMask.At(dmp.X+dr.Min.X+int(dx), dmp.Y+dr.Min.Y+int(dy)).RGBA() + pr = pr * ma / 0xffff + pg = pg * ma / 0xffff + pb = pb * ma / 0xffff + pa = pa * ma / 0xffff + } + pa1 := 0xffff - pa + dstColorRGBA64.R = uint16(qr*pa1/0xffff + pr) + dstColorRGBA64.G = uint16(qg*pa1/0xffff + pg) + dstColorRGBA64.B = uint16(qb*pa1/0xffff + pb) + dstColorRGBA64.A = uint16(qa*pa1/0xffff + pa) + dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor) + } + } +} + +func (ablInterpolator) scale_Image_Image_Src(dst Image, dr, adr image.Rectangle, src image.Image, sr image.Rectangle, opts *Options) { + sw := int32(sr.Dx()) + sh := int32(sr.Dy()) + yscale := float64(sh) / float64(dr.Dy()) + xscale := float64(sw) / float64(dr.Dx()) + swMinus1, shMinus1 := sw-1, sh-1 + srcMask, smp := opts.SrcMask, opts.SrcMaskP + dstMask, dmp := opts.DstMask, opts.DstMaskP + dstColorRGBA64 := &color.RGBA64{} + dstColor := color.Color(dstColorRGBA64) + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + sy := (float64(dy)+0.5)*yscale - 0.5 + // If sy < 0, we will clamp sy0 to 0 anyway, so it doesn't matter if + // we say int32(sy) instead of int32(math.Floor(sy)). Similarly for + // sx, below. + sy0 := int32(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy1 := sy0 + 1 + if sy < 0 { + sy0, sy1 = 0, 0 + yFrac0, yFrac1 = 0, 1 + } else if sy1 > shMinus1 { + sy0, sy1 = shMinus1, shMinus1 + yFrac0, yFrac1 = 1, 0 + } + + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { + sx := (float64(dx)+0.5)*xscale - 0.5 + sx0 := int32(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx1 := sx0 + 1 + if sx < 0 { + sx0, sx1 = 0, 0 + xFrac0, xFrac1 = 0, 1 + } else if sx1 > swMinus1 { + sx0, sx1 = swMinus1, swMinus1 + xFrac0, xFrac1 = 1, 0 + } + + s00ru, s00gu, s00bu, s00au := src.At(sr.Min.X+int(sx0), sr.Min.Y+int(sy0)).RGBA() + if srcMask != nil { + _, _, _, ma := srcMask.At(smp.X+sr.Min.X+int(sx0), smp.Y+sr.Min.Y+int(sy0)).RGBA() + s00ru = s00ru * ma / 0xffff + s00gu = s00gu * ma / 0xffff + s00bu = s00bu * ma / 0xffff + s00au = s00au * ma / 0xffff + } + s00r := float64(s00ru) + s00g := float64(s00gu) + s00b := float64(s00bu) + s00a := float64(s00au) + s10ru, s10gu, s10bu, s10au := src.At(sr.Min.X+int(sx1), sr.Min.Y+int(sy0)).RGBA() + if srcMask != nil { + _, _, _, ma := srcMask.At(smp.X+sr.Min.X+int(sx1), smp.Y+sr.Min.Y+int(sy0)).RGBA() + s10ru = s10ru * ma / 0xffff + s10gu = s10gu * ma / 0xffff + s10bu = s10bu * ma / 0xffff + s10au = s10au * ma / 0xffff + } + s10r := float64(s10ru) + s10g := float64(s10gu) + s10b := float64(s10bu) + s10a := float64(s10au) + s10r = xFrac1*s00r + xFrac0*s10r + s10g = xFrac1*s00g + xFrac0*s10g + s10b = xFrac1*s00b + xFrac0*s10b + s10a = xFrac1*s00a + xFrac0*s10a + s01ru, s01gu, s01bu, s01au := src.At(sr.Min.X+int(sx0), sr.Min.Y+int(sy1)).RGBA() + if srcMask != nil { + _, _, _, ma := srcMask.At(smp.X+sr.Min.X+int(sx0), smp.Y+sr.Min.Y+int(sy1)).RGBA() + s01ru = s01ru * ma / 0xffff + s01gu = s01gu * ma / 0xffff + s01bu = s01bu * ma / 0xffff + s01au = s01au * ma / 0xffff + } + s01r := float64(s01ru) + s01g := float64(s01gu) + s01b := float64(s01bu) + s01a := float64(s01au) + s11ru, s11gu, s11bu, s11au := src.At(sr.Min.X+int(sx1), sr.Min.Y+int(sy1)).RGBA() + if srcMask != nil { + _, _, _, ma := srcMask.At(smp.X+sr.Min.X+int(sx1), smp.Y+sr.Min.Y+int(sy1)).RGBA() + s11ru = s11ru * ma / 0xffff + s11gu = s11gu * ma / 0xffff + s11bu = s11bu * ma / 0xffff + s11au = s11au * ma / 0xffff + } + s11r := float64(s11ru) + s11g := float64(s11gu) + s11b := float64(s11bu) + s11a := float64(s11au) + s11r = xFrac1*s01r + xFrac0*s11r + s11g = xFrac1*s01g + xFrac0*s11g + s11b = xFrac1*s01b + xFrac0*s11b + s11a = xFrac1*s01a + xFrac0*s11a + s11r = yFrac1*s10r + yFrac0*s11r + s11g = yFrac1*s10g + yFrac0*s11g + s11b = yFrac1*s10b + yFrac0*s11b + s11a = yFrac1*s10a + yFrac0*s11a + pr := uint32(s11r) + pg := uint32(s11g) + pb := uint32(s11b) + pa := uint32(s11a) + if dstMask != nil { + qr, qg, qb, qa := dst.At(dr.Min.X+int(dx), dr.Min.Y+int(dy)).RGBA() + _, _, _, ma := dstMask.At(dmp.X+dr.Min.X+int(dx), dmp.Y+dr.Min.Y+int(dy)).RGBA() + pr = pr * ma / 0xffff + pg = pg * ma / 0xffff + pb = pb * ma / 0xffff + pa = pa * ma / 0xffff + pa1 := 0xffff - ma + dstColorRGBA64.R = uint16(qr*pa1/0xffff + pr) + dstColorRGBA64.G = uint16(qg*pa1/0xffff + pg) + dstColorRGBA64.B = uint16(qb*pa1/0xffff + pb) + dstColorRGBA64.A = uint16(qa*pa1/0xffff + pa) + dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor) + } else { + dstColorRGBA64.R = uint16(pr) + dstColorRGBA64.G = uint16(pg) + dstColorRGBA64.B = uint16(pb) + dstColorRGBA64.A = uint16(pa) + dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor) + } + } + } +} + +func (ablInterpolator) transform_RGBA_Gray_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.Gray, sr image.Rectangle, bias image.Point, opts *Options) { + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + sx -= 0.5 + sx0 := int(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx0 += bias.X + sx1 := sx0 + 1 + if sx0 < sr.Min.X { + sx0, sx1 = sr.Min.X, sr.Min.X + xFrac0, xFrac1 = 0, 1 + } else if sx1 >= sr.Max.X { + sx0, sx1 = sr.Max.X-1, sr.Max.X-1 + xFrac0, xFrac1 = 1, 0 + } + + sy -= 0.5 + sy0 := int(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy0 += bias.Y + sy1 := sy0 + 1 + if sy0 < sr.Min.Y { + sy0, sy1 = sr.Min.Y, sr.Min.Y + yFrac0, yFrac1 = 0, 1 + } else if sy1 >= sr.Max.Y { + sy0, sy1 = sr.Max.Y-1, sr.Max.Y-1 + yFrac0, yFrac1 = 1, 0 + } + + s00i := (sy0-src.Rect.Min.Y)*src.Stride + (sx0 - src.Rect.Min.X) + s00ru := uint32(src.Pix[s00i]) * 0x101 + s00r := float64(s00ru) + s10i := (sy0-src.Rect.Min.Y)*src.Stride + (sx1 - src.Rect.Min.X) + s10ru := uint32(src.Pix[s10i]) * 0x101 + s10r := float64(s10ru) + s10r = xFrac1*s00r + xFrac0*s10r + s01i := (sy1-src.Rect.Min.Y)*src.Stride + (sx0 - src.Rect.Min.X) + s01ru := uint32(src.Pix[s01i]) * 0x101 + s01r := float64(s01ru) + s11i := (sy1-src.Rect.Min.Y)*src.Stride + (sx1 - src.Rect.Min.X) + s11ru := uint32(src.Pix[s11i]) * 0x101 + s11r := float64(s11ru) + s11r = xFrac1*s01r + xFrac0*s11r + s11r = yFrac1*s10r + yFrac0*s11r + pr := uint32(s11r) + out := uint8(pr >> 8) + dst.Pix[d+0] = out + dst.Pix[d+1] = out + dst.Pix[d+2] = out + dst.Pix[d+3] = 0xff + } + } +} + +func (ablInterpolator) transform_RGBA_NRGBA_Over(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.NRGBA, sr image.Rectangle, bias image.Point, opts *Options) { + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + sx -= 0.5 + sx0 := int(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx0 += bias.X + sx1 := sx0 + 1 + if sx0 < sr.Min.X { + sx0, sx1 = sr.Min.X, sr.Min.X + xFrac0, xFrac1 = 0, 1 + } else if sx1 >= sr.Max.X { + sx0, sx1 = sr.Max.X-1, sr.Max.X-1 + xFrac0, xFrac1 = 1, 0 + } + + sy -= 0.5 + sy0 := int(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy0 += bias.Y + sy1 := sy0 + 1 + if sy0 < sr.Min.Y { + sy0, sy1 = sr.Min.Y, sr.Min.Y + yFrac0, yFrac1 = 0, 1 + } else if sy1 >= sr.Max.Y { + sy0, sy1 = sr.Max.Y-1, sr.Max.Y-1 + yFrac0, yFrac1 = 1, 0 + } + + s00i := (sy0-src.Rect.Min.Y)*src.Stride + (sx0-src.Rect.Min.X)*4 + s00au := uint32(src.Pix[s00i+3]) * 0x101 + s00ru := uint32(src.Pix[s00i+0]) * s00au / 0xff + s00gu := uint32(src.Pix[s00i+1]) * s00au / 0xff + s00bu := uint32(src.Pix[s00i+2]) * s00au / 0xff + s00r := float64(s00ru) + s00g := float64(s00gu) + s00b := float64(s00bu) + s00a := float64(s00au) + s10i := (sy0-src.Rect.Min.Y)*src.Stride + (sx1-src.Rect.Min.X)*4 + s10au := uint32(src.Pix[s10i+3]) * 0x101 + s10ru := uint32(src.Pix[s10i+0]) * s10au / 0xff + s10gu := uint32(src.Pix[s10i+1]) * s10au / 0xff + s10bu := uint32(src.Pix[s10i+2]) * s10au / 0xff + s10r := float64(s10ru) + s10g := float64(s10gu) + s10b := float64(s10bu) + s10a := float64(s10au) + s10r = xFrac1*s00r + xFrac0*s10r + s10g = xFrac1*s00g + xFrac0*s10g + s10b = xFrac1*s00b + xFrac0*s10b + s10a = xFrac1*s00a + xFrac0*s10a + s01i := (sy1-src.Rect.Min.Y)*src.Stride + (sx0-src.Rect.Min.X)*4 + s01au := uint32(src.Pix[s01i+3]) * 0x101 + s01ru := uint32(src.Pix[s01i+0]) * s01au / 0xff + s01gu := uint32(src.Pix[s01i+1]) * s01au / 0xff + s01bu := uint32(src.Pix[s01i+2]) * s01au / 0xff + s01r := float64(s01ru) + s01g := float64(s01gu) + s01b := float64(s01bu) + s01a := float64(s01au) + s11i := (sy1-src.Rect.Min.Y)*src.Stride + (sx1-src.Rect.Min.X)*4 + s11au := uint32(src.Pix[s11i+3]) * 0x101 + s11ru := uint32(src.Pix[s11i+0]) * s11au / 0xff + s11gu := uint32(src.Pix[s11i+1]) * s11au / 0xff + s11bu := uint32(src.Pix[s11i+2]) * s11au / 0xff + s11r := float64(s11ru) + s11g := float64(s11gu) + s11b := float64(s11bu) + s11a := float64(s11au) + s11r = xFrac1*s01r + xFrac0*s11r + s11g = xFrac1*s01g + xFrac0*s11g + s11b = xFrac1*s01b + xFrac0*s11b + s11a = xFrac1*s01a + xFrac0*s11a + s11r = yFrac1*s10r + yFrac0*s11r + s11g = yFrac1*s10g + yFrac0*s11g + s11b = yFrac1*s10b + yFrac0*s11b + s11a = yFrac1*s10a + yFrac0*s11a + pr := uint32(s11r) + pg := uint32(s11g) + pb := uint32(s11b) + pa := uint32(s11a) + pa1 := (0xffff - pa) * 0x101 + dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*pa1/0xffff + pr) >> 8) + dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*pa1/0xffff + pg) >> 8) + dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*pa1/0xffff + pb) >> 8) + dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*pa1/0xffff + pa) >> 8) + } + } +} + +func (ablInterpolator) transform_RGBA_NRGBA_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.NRGBA, sr image.Rectangle, bias image.Point, opts *Options) { + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + sx -= 0.5 + sx0 := int(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx0 += bias.X + sx1 := sx0 + 1 + if sx0 < sr.Min.X { + sx0, sx1 = sr.Min.X, sr.Min.X + xFrac0, xFrac1 = 0, 1 + } else if sx1 >= sr.Max.X { + sx0, sx1 = sr.Max.X-1, sr.Max.X-1 + xFrac0, xFrac1 = 1, 0 + } + + sy -= 0.5 + sy0 := int(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy0 += bias.Y + sy1 := sy0 + 1 + if sy0 < sr.Min.Y { + sy0, sy1 = sr.Min.Y, sr.Min.Y + yFrac0, yFrac1 = 0, 1 + } else if sy1 >= sr.Max.Y { + sy0, sy1 = sr.Max.Y-1, sr.Max.Y-1 + yFrac0, yFrac1 = 1, 0 + } + + s00i := (sy0-src.Rect.Min.Y)*src.Stride + (sx0-src.Rect.Min.X)*4 + s00au := uint32(src.Pix[s00i+3]) * 0x101 + s00ru := uint32(src.Pix[s00i+0]) * s00au / 0xff + s00gu := uint32(src.Pix[s00i+1]) * s00au / 0xff + s00bu := uint32(src.Pix[s00i+2]) * s00au / 0xff + s00r := float64(s00ru) + s00g := float64(s00gu) + s00b := float64(s00bu) + s00a := float64(s00au) + s10i := (sy0-src.Rect.Min.Y)*src.Stride + (sx1-src.Rect.Min.X)*4 + s10au := uint32(src.Pix[s10i+3]) * 0x101 + s10ru := uint32(src.Pix[s10i+0]) * s10au / 0xff + s10gu := uint32(src.Pix[s10i+1]) * s10au / 0xff + s10bu := uint32(src.Pix[s10i+2]) * s10au / 0xff + s10r := float64(s10ru) + s10g := float64(s10gu) + s10b := float64(s10bu) + s10a := float64(s10au) + s10r = xFrac1*s00r + xFrac0*s10r + s10g = xFrac1*s00g + xFrac0*s10g + s10b = xFrac1*s00b + xFrac0*s10b + s10a = xFrac1*s00a + xFrac0*s10a + s01i := (sy1-src.Rect.Min.Y)*src.Stride + (sx0-src.Rect.Min.X)*4 + s01au := uint32(src.Pix[s01i+3]) * 0x101 + s01ru := uint32(src.Pix[s01i+0]) * s01au / 0xff + s01gu := uint32(src.Pix[s01i+1]) * s01au / 0xff + s01bu := uint32(src.Pix[s01i+2]) * s01au / 0xff + s01r := float64(s01ru) + s01g := float64(s01gu) + s01b := float64(s01bu) + s01a := float64(s01au) + s11i := (sy1-src.Rect.Min.Y)*src.Stride + (sx1-src.Rect.Min.X)*4 + s11au := uint32(src.Pix[s11i+3]) * 0x101 + s11ru := uint32(src.Pix[s11i+0]) * s11au / 0xff + s11gu := uint32(src.Pix[s11i+1]) * s11au / 0xff + s11bu := uint32(src.Pix[s11i+2]) * s11au / 0xff + s11r := float64(s11ru) + s11g := float64(s11gu) + s11b := float64(s11bu) + s11a := float64(s11au) + s11r = xFrac1*s01r + xFrac0*s11r + s11g = xFrac1*s01g + xFrac0*s11g + s11b = xFrac1*s01b + xFrac0*s11b + s11a = xFrac1*s01a + xFrac0*s11a + s11r = yFrac1*s10r + yFrac0*s11r + s11g = yFrac1*s10g + yFrac0*s11g + s11b = yFrac1*s10b + yFrac0*s11b + s11a = yFrac1*s10a + yFrac0*s11a + pr := uint32(s11r) + pg := uint32(s11g) + pb := uint32(s11b) + pa := uint32(s11a) + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = uint8(pa >> 8) + } + } +} + +func (ablInterpolator) transform_RGBA_RGBA_Over(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.RGBA, sr image.Rectangle, bias image.Point, opts *Options) { + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + sx -= 0.5 + sx0 := int(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx0 += bias.X + sx1 := sx0 + 1 + if sx0 < sr.Min.X { + sx0, sx1 = sr.Min.X, sr.Min.X + xFrac0, xFrac1 = 0, 1 + } else if sx1 >= sr.Max.X { + sx0, sx1 = sr.Max.X-1, sr.Max.X-1 + xFrac0, xFrac1 = 1, 0 + } + + sy -= 0.5 + sy0 := int(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy0 += bias.Y + sy1 := sy0 + 1 + if sy0 < sr.Min.Y { + sy0, sy1 = sr.Min.Y, sr.Min.Y + yFrac0, yFrac1 = 0, 1 + } else if sy1 >= sr.Max.Y { + sy0, sy1 = sr.Max.Y-1, sr.Max.Y-1 + yFrac0, yFrac1 = 1, 0 + } + + s00i := (sy0-src.Rect.Min.Y)*src.Stride + (sx0-src.Rect.Min.X)*4 + s00ru := uint32(src.Pix[s00i+0]) * 0x101 + s00gu := uint32(src.Pix[s00i+1]) * 0x101 + s00bu := uint32(src.Pix[s00i+2]) * 0x101 + s00au := uint32(src.Pix[s00i+3]) * 0x101 + s00r := float64(s00ru) + s00g := float64(s00gu) + s00b := float64(s00bu) + s00a := float64(s00au) + s10i := (sy0-src.Rect.Min.Y)*src.Stride + (sx1-src.Rect.Min.X)*4 + s10ru := uint32(src.Pix[s10i+0]) * 0x101 + s10gu := uint32(src.Pix[s10i+1]) * 0x101 + s10bu := uint32(src.Pix[s10i+2]) * 0x101 + s10au := uint32(src.Pix[s10i+3]) * 0x101 + s10r := float64(s10ru) + s10g := float64(s10gu) + s10b := float64(s10bu) + s10a := float64(s10au) + s10r = xFrac1*s00r + xFrac0*s10r + s10g = xFrac1*s00g + xFrac0*s10g + s10b = xFrac1*s00b + xFrac0*s10b + s10a = xFrac1*s00a + xFrac0*s10a + s01i := (sy1-src.Rect.Min.Y)*src.Stride + (sx0-src.Rect.Min.X)*4 + s01ru := uint32(src.Pix[s01i+0]) * 0x101 + s01gu := uint32(src.Pix[s01i+1]) * 0x101 + s01bu := uint32(src.Pix[s01i+2]) * 0x101 + s01au := uint32(src.Pix[s01i+3]) * 0x101 + s01r := float64(s01ru) + s01g := float64(s01gu) + s01b := float64(s01bu) + s01a := float64(s01au) + s11i := (sy1-src.Rect.Min.Y)*src.Stride + (sx1-src.Rect.Min.X)*4 + s11ru := uint32(src.Pix[s11i+0]) * 0x101 + s11gu := uint32(src.Pix[s11i+1]) * 0x101 + s11bu := uint32(src.Pix[s11i+2]) * 0x101 + s11au := uint32(src.Pix[s11i+3]) * 0x101 + s11r := float64(s11ru) + s11g := float64(s11gu) + s11b := float64(s11bu) + s11a := float64(s11au) + s11r = xFrac1*s01r + xFrac0*s11r + s11g = xFrac1*s01g + xFrac0*s11g + s11b = xFrac1*s01b + xFrac0*s11b + s11a = xFrac1*s01a + xFrac0*s11a + s11r = yFrac1*s10r + yFrac0*s11r + s11g = yFrac1*s10g + yFrac0*s11g + s11b = yFrac1*s10b + yFrac0*s11b + s11a = yFrac1*s10a + yFrac0*s11a + pr := uint32(s11r) + pg := uint32(s11g) + pb := uint32(s11b) + pa := uint32(s11a) + pa1 := (0xffff - pa) * 0x101 + dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*pa1/0xffff + pr) >> 8) + dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*pa1/0xffff + pg) >> 8) + dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*pa1/0xffff + pb) >> 8) + dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*pa1/0xffff + pa) >> 8) + } + } +} + +func (ablInterpolator) transform_RGBA_RGBA_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.RGBA, sr image.Rectangle, bias image.Point, opts *Options) { + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + sx -= 0.5 + sx0 := int(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx0 += bias.X + sx1 := sx0 + 1 + if sx0 < sr.Min.X { + sx0, sx1 = sr.Min.X, sr.Min.X + xFrac0, xFrac1 = 0, 1 + } else if sx1 >= sr.Max.X { + sx0, sx1 = sr.Max.X-1, sr.Max.X-1 + xFrac0, xFrac1 = 1, 0 + } + + sy -= 0.5 + sy0 := int(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy0 += bias.Y + sy1 := sy0 + 1 + if sy0 < sr.Min.Y { + sy0, sy1 = sr.Min.Y, sr.Min.Y + yFrac0, yFrac1 = 0, 1 + } else if sy1 >= sr.Max.Y { + sy0, sy1 = sr.Max.Y-1, sr.Max.Y-1 + yFrac0, yFrac1 = 1, 0 + } + + s00i := (sy0-src.Rect.Min.Y)*src.Stride + (sx0-src.Rect.Min.X)*4 + s00ru := uint32(src.Pix[s00i+0]) * 0x101 + s00gu := uint32(src.Pix[s00i+1]) * 0x101 + s00bu := uint32(src.Pix[s00i+2]) * 0x101 + s00au := uint32(src.Pix[s00i+3]) * 0x101 + s00r := float64(s00ru) + s00g := float64(s00gu) + s00b := float64(s00bu) + s00a := float64(s00au) + s10i := (sy0-src.Rect.Min.Y)*src.Stride + (sx1-src.Rect.Min.X)*4 + s10ru := uint32(src.Pix[s10i+0]) * 0x101 + s10gu := uint32(src.Pix[s10i+1]) * 0x101 + s10bu := uint32(src.Pix[s10i+2]) * 0x101 + s10au := uint32(src.Pix[s10i+3]) * 0x101 + s10r := float64(s10ru) + s10g := float64(s10gu) + s10b := float64(s10bu) + s10a := float64(s10au) + s10r = xFrac1*s00r + xFrac0*s10r + s10g = xFrac1*s00g + xFrac0*s10g + s10b = xFrac1*s00b + xFrac0*s10b + s10a = xFrac1*s00a + xFrac0*s10a + s01i := (sy1-src.Rect.Min.Y)*src.Stride + (sx0-src.Rect.Min.X)*4 + s01ru := uint32(src.Pix[s01i+0]) * 0x101 + s01gu := uint32(src.Pix[s01i+1]) * 0x101 + s01bu := uint32(src.Pix[s01i+2]) * 0x101 + s01au := uint32(src.Pix[s01i+3]) * 0x101 + s01r := float64(s01ru) + s01g := float64(s01gu) + s01b := float64(s01bu) + s01a := float64(s01au) + s11i := (sy1-src.Rect.Min.Y)*src.Stride + (sx1-src.Rect.Min.X)*4 + s11ru := uint32(src.Pix[s11i+0]) * 0x101 + s11gu := uint32(src.Pix[s11i+1]) * 0x101 + s11bu := uint32(src.Pix[s11i+2]) * 0x101 + s11au := uint32(src.Pix[s11i+3]) * 0x101 + s11r := float64(s11ru) + s11g := float64(s11gu) + s11b := float64(s11bu) + s11a := float64(s11au) + s11r = xFrac1*s01r + xFrac0*s11r + s11g = xFrac1*s01g + xFrac0*s11g + s11b = xFrac1*s01b + xFrac0*s11b + s11a = xFrac1*s01a + xFrac0*s11a + s11r = yFrac1*s10r + yFrac0*s11r + s11g = yFrac1*s10g + yFrac0*s11g + s11b = yFrac1*s10b + yFrac0*s11b + s11a = yFrac1*s10a + yFrac0*s11a + pr := uint32(s11r) + pg := uint32(s11g) + pb := uint32(s11b) + pa := uint32(s11a) + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = uint8(pa >> 8) + } + } +} + +func (ablInterpolator) transform_RGBA_YCbCr444_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point, opts *Options) { + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + sx -= 0.5 + sx0 := int(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx0 += bias.X + sx1 := sx0 + 1 + if sx0 < sr.Min.X { + sx0, sx1 = sr.Min.X, sr.Min.X + xFrac0, xFrac1 = 0, 1 + } else if sx1 >= sr.Max.X { + sx0, sx1 = sr.Max.X-1, sr.Max.X-1 + xFrac0, xFrac1 = 1, 0 + } + + sy -= 0.5 + sy0 := int(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy0 += bias.Y + sy1 := sy0 + 1 + if sy0 < sr.Min.Y { + sy0, sy1 = sr.Min.Y, sr.Min.Y + yFrac0, yFrac1 = 0, 1 + } else if sy1 >= sr.Max.Y { + sy0, sy1 = sr.Max.Y-1, sr.Max.Y-1 + yFrac0, yFrac1 = 1, 0 + } + + s00i := (sy0-src.Rect.Min.Y)*src.YStride + (sx0 - src.Rect.Min.X) + s00j := (sy0-src.Rect.Min.Y)*src.CStride + (sx0 - src.Rect.Min.X) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s00yy1 := int(src.Y[s00i]) * 0x10100 + s00cb1 := int(src.Cb[s00j]) - 128 + s00cr1 := int(src.Cr[s00j]) - 128 + s00ru := (s00yy1 + 91881*s00cr1) >> 8 + s00gu := (s00yy1 - 22554*s00cb1 - 46802*s00cr1) >> 8 + s00bu := (s00yy1 + 116130*s00cb1) >> 8 + if s00ru < 0 { + s00ru = 0 + } else if s00ru > 0xffff { + s00ru = 0xffff + } + if s00gu < 0 { + s00gu = 0 + } else if s00gu > 0xffff { + s00gu = 0xffff + } + if s00bu < 0 { + s00bu = 0 + } else if s00bu > 0xffff { + s00bu = 0xffff + } + + s00r := float64(s00ru) + s00g := float64(s00gu) + s00b := float64(s00bu) + s10i := (sy0-src.Rect.Min.Y)*src.YStride + (sx1 - src.Rect.Min.X) + s10j := (sy0-src.Rect.Min.Y)*src.CStride + (sx1 - src.Rect.Min.X) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s10yy1 := int(src.Y[s10i]) * 0x10100 + s10cb1 := int(src.Cb[s10j]) - 128 + s10cr1 := int(src.Cr[s10j]) - 128 + s10ru := (s10yy1 + 91881*s10cr1) >> 8 + s10gu := (s10yy1 - 22554*s10cb1 - 46802*s10cr1) >> 8 + s10bu := (s10yy1 + 116130*s10cb1) >> 8 + if s10ru < 0 { + s10ru = 0 + } else if s10ru > 0xffff { + s10ru = 0xffff + } + if s10gu < 0 { + s10gu = 0 + } else if s10gu > 0xffff { + s10gu = 0xffff + } + if s10bu < 0 { + s10bu = 0 + } else if s10bu > 0xffff { + s10bu = 0xffff + } + + s10r := float64(s10ru) + s10g := float64(s10gu) + s10b := float64(s10bu) + s10r = xFrac1*s00r + xFrac0*s10r + s10g = xFrac1*s00g + xFrac0*s10g + s10b = xFrac1*s00b + xFrac0*s10b + s01i := (sy1-src.Rect.Min.Y)*src.YStride + (sx0 - src.Rect.Min.X) + s01j := (sy1-src.Rect.Min.Y)*src.CStride + (sx0 - src.Rect.Min.X) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s01yy1 := int(src.Y[s01i]) * 0x10100 + s01cb1 := int(src.Cb[s01j]) - 128 + s01cr1 := int(src.Cr[s01j]) - 128 + s01ru := (s01yy1 + 91881*s01cr1) >> 8 + s01gu := (s01yy1 - 22554*s01cb1 - 46802*s01cr1) >> 8 + s01bu := (s01yy1 + 116130*s01cb1) >> 8 + if s01ru < 0 { + s01ru = 0 + } else if s01ru > 0xffff { + s01ru = 0xffff + } + if s01gu < 0 { + s01gu = 0 + } else if s01gu > 0xffff { + s01gu = 0xffff + } + if s01bu < 0 { + s01bu = 0 + } else if s01bu > 0xffff { + s01bu = 0xffff + } + + s01r := float64(s01ru) + s01g := float64(s01gu) + s01b := float64(s01bu) + s11i := (sy1-src.Rect.Min.Y)*src.YStride + (sx1 - src.Rect.Min.X) + s11j := (sy1-src.Rect.Min.Y)*src.CStride + (sx1 - src.Rect.Min.X) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s11yy1 := int(src.Y[s11i]) * 0x10100 + s11cb1 := int(src.Cb[s11j]) - 128 + s11cr1 := int(src.Cr[s11j]) - 128 + s11ru := (s11yy1 + 91881*s11cr1) >> 8 + s11gu := (s11yy1 - 22554*s11cb1 - 46802*s11cr1) >> 8 + s11bu := (s11yy1 + 116130*s11cb1) >> 8 + if s11ru < 0 { + s11ru = 0 + } else if s11ru > 0xffff { + s11ru = 0xffff + } + if s11gu < 0 { + s11gu = 0 + } else if s11gu > 0xffff { + s11gu = 0xffff + } + if s11bu < 0 { + s11bu = 0 + } else if s11bu > 0xffff { + s11bu = 0xffff + } + + s11r := float64(s11ru) + s11g := float64(s11gu) + s11b := float64(s11bu) + s11r = xFrac1*s01r + xFrac0*s11r + s11g = xFrac1*s01g + xFrac0*s11g + s11b = xFrac1*s01b + xFrac0*s11b + s11r = yFrac1*s10r + yFrac0*s11r + s11g = yFrac1*s10g + yFrac0*s11g + s11b = yFrac1*s10b + yFrac0*s11b + pr := uint32(s11r) + pg := uint32(s11g) + pb := uint32(s11b) + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = 0xff + } + } +} + +func (ablInterpolator) transform_RGBA_YCbCr422_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point, opts *Options) { + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + sx -= 0.5 + sx0 := int(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx0 += bias.X + sx1 := sx0 + 1 + if sx0 < sr.Min.X { + sx0, sx1 = sr.Min.X, sr.Min.X + xFrac0, xFrac1 = 0, 1 + } else if sx1 >= sr.Max.X { + sx0, sx1 = sr.Max.X-1, sr.Max.X-1 + xFrac0, xFrac1 = 1, 0 + } + + sy -= 0.5 + sy0 := int(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy0 += bias.Y + sy1 := sy0 + 1 + if sy0 < sr.Min.Y { + sy0, sy1 = sr.Min.Y, sr.Min.Y + yFrac0, yFrac1 = 0, 1 + } else if sy1 >= sr.Max.Y { + sy0, sy1 = sr.Max.Y-1, sr.Max.Y-1 + yFrac0, yFrac1 = 1, 0 + } + + s00i := (sy0-src.Rect.Min.Y)*src.YStride + (sx0 - src.Rect.Min.X) + s00j := (sy0-src.Rect.Min.Y)*src.CStride + ((sx0)/2 - src.Rect.Min.X/2) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s00yy1 := int(src.Y[s00i]) * 0x10100 + s00cb1 := int(src.Cb[s00j]) - 128 + s00cr1 := int(src.Cr[s00j]) - 128 + s00ru := (s00yy1 + 91881*s00cr1) >> 8 + s00gu := (s00yy1 - 22554*s00cb1 - 46802*s00cr1) >> 8 + s00bu := (s00yy1 + 116130*s00cb1) >> 8 + if s00ru < 0 { + s00ru = 0 + } else if s00ru > 0xffff { + s00ru = 0xffff + } + if s00gu < 0 { + s00gu = 0 + } else if s00gu > 0xffff { + s00gu = 0xffff + } + if s00bu < 0 { + s00bu = 0 + } else if s00bu > 0xffff { + s00bu = 0xffff + } + + s00r := float64(s00ru) + s00g := float64(s00gu) + s00b := float64(s00bu) + s10i := (sy0-src.Rect.Min.Y)*src.YStride + (sx1 - src.Rect.Min.X) + s10j := (sy0-src.Rect.Min.Y)*src.CStride + ((sx1)/2 - src.Rect.Min.X/2) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s10yy1 := int(src.Y[s10i]) * 0x10100 + s10cb1 := int(src.Cb[s10j]) - 128 + s10cr1 := int(src.Cr[s10j]) - 128 + s10ru := (s10yy1 + 91881*s10cr1) >> 8 + s10gu := (s10yy1 - 22554*s10cb1 - 46802*s10cr1) >> 8 + s10bu := (s10yy1 + 116130*s10cb1) >> 8 + if s10ru < 0 { + s10ru = 0 + } else if s10ru > 0xffff { + s10ru = 0xffff + } + if s10gu < 0 { + s10gu = 0 + } else if s10gu > 0xffff { + s10gu = 0xffff + } + if s10bu < 0 { + s10bu = 0 + } else if s10bu > 0xffff { + s10bu = 0xffff + } + + s10r := float64(s10ru) + s10g := float64(s10gu) + s10b := float64(s10bu) + s10r = xFrac1*s00r + xFrac0*s10r + s10g = xFrac1*s00g + xFrac0*s10g + s10b = xFrac1*s00b + xFrac0*s10b + s01i := (sy1-src.Rect.Min.Y)*src.YStride + (sx0 - src.Rect.Min.X) + s01j := (sy1-src.Rect.Min.Y)*src.CStride + ((sx0)/2 - src.Rect.Min.X/2) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s01yy1 := int(src.Y[s01i]) * 0x10100 + s01cb1 := int(src.Cb[s01j]) - 128 + s01cr1 := int(src.Cr[s01j]) - 128 + s01ru := (s01yy1 + 91881*s01cr1) >> 8 + s01gu := (s01yy1 - 22554*s01cb1 - 46802*s01cr1) >> 8 + s01bu := (s01yy1 + 116130*s01cb1) >> 8 + if s01ru < 0 { + s01ru = 0 + } else if s01ru > 0xffff { + s01ru = 0xffff + } + if s01gu < 0 { + s01gu = 0 + } else if s01gu > 0xffff { + s01gu = 0xffff + } + if s01bu < 0 { + s01bu = 0 + } else if s01bu > 0xffff { + s01bu = 0xffff + } + + s01r := float64(s01ru) + s01g := float64(s01gu) + s01b := float64(s01bu) + s11i := (sy1-src.Rect.Min.Y)*src.YStride + (sx1 - src.Rect.Min.X) + s11j := (sy1-src.Rect.Min.Y)*src.CStride + ((sx1)/2 - src.Rect.Min.X/2) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s11yy1 := int(src.Y[s11i]) * 0x10100 + s11cb1 := int(src.Cb[s11j]) - 128 + s11cr1 := int(src.Cr[s11j]) - 128 + s11ru := (s11yy1 + 91881*s11cr1) >> 8 + s11gu := (s11yy1 - 22554*s11cb1 - 46802*s11cr1) >> 8 + s11bu := (s11yy1 + 116130*s11cb1) >> 8 + if s11ru < 0 { + s11ru = 0 + } else if s11ru > 0xffff { + s11ru = 0xffff + } + if s11gu < 0 { + s11gu = 0 + } else if s11gu > 0xffff { + s11gu = 0xffff + } + if s11bu < 0 { + s11bu = 0 + } else if s11bu > 0xffff { + s11bu = 0xffff + } + + s11r := float64(s11ru) + s11g := float64(s11gu) + s11b := float64(s11bu) + s11r = xFrac1*s01r + xFrac0*s11r + s11g = xFrac1*s01g + xFrac0*s11g + s11b = xFrac1*s01b + xFrac0*s11b + s11r = yFrac1*s10r + yFrac0*s11r + s11g = yFrac1*s10g + yFrac0*s11g + s11b = yFrac1*s10b + yFrac0*s11b + pr := uint32(s11r) + pg := uint32(s11g) + pb := uint32(s11b) + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = 0xff + } + } +} + +func (ablInterpolator) transform_RGBA_YCbCr420_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point, opts *Options) { + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + sx -= 0.5 + sx0 := int(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx0 += bias.X + sx1 := sx0 + 1 + if sx0 < sr.Min.X { + sx0, sx1 = sr.Min.X, sr.Min.X + xFrac0, xFrac1 = 0, 1 + } else if sx1 >= sr.Max.X { + sx0, sx1 = sr.Max.X-1, sr.Max.X-1 + xFrac0, xFrac1 = 1, 0 + } + + sy -= 0.5 + sy0 := int(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy0 += bias.Y + sy1 := sy0 + 1 + if sy0 < sr.Min.Y { + sy0, sy1 = sr.Min.Y, sr.Min.Y + yFrac0, yFrac1 = 0, 1 + } else if sy1 >= sr.Max.Y { + sy0, sy1 = sr.Max.Y-1, sr.Max.Y-1 + yFrac0, yFrac1 = 1, 0 + } + + s00i := (sy0-src.Rect.Min.Y)*src.YStride + (sx0 - src.Rect.Min.X) + s00j := ((sy0)/2-src.Rect.Min.Y/2)*src.CStride + ((sx0)/2 - src.Rect.Min.X/2) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s00yy1 := int(src.Y[s00i]) * 0x10100 + s00cb1 := int(src.Cb[s00j]) - 128 + s00cr1 := int(src.Cr[s00j]) - 128 + s00ru := (s00yy1 + 91881*s00cr1) >> 8 + s00gu := (s00yy1 - 22554*s00cb1 - 46802*s00cr1) >> 8 + s00bu := (s00yy1 + 116130*s00cb1) >> 8 + if s00ru < 0 { + s00ru = 0 + } else if s00ru > 0xffff { + s00ru = 0xffff + } + if s00gu < 0 { + s00gu = 0 + } else if s00gu > 0xffff { + s00gu = 0xffff + } + if s00bu < 0 { + s00bu = 0 + } else if s00bu > 0xffff { + s00bu = 0xffff + } + + s00r := float64(s00ru) + s00g := float64(s00gu) + s00b := float64(s00bu) + s10i := (sy0-src.Rect.Min.Y)*src.YStride + (sx1 - src.Rect.Min.X) + s10j := ((sy0)/2-src.Rect.Min.Y/2)*src.CStride + ((sx1)/2 - src.Rect.Min.X/2) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s10yy1 := int(src.Y[s10i]) * 0x10100 + s10cb1 := int(src.Cb[s10j]) - 128 + s10cr1 := int(src.Cr[s10j]) - 128 + s10ru := (s10yy1 + 91881*s10cr1) >> 8 + s10gu := (s10yy1 - 22554*s10cb1 - 46802*s10cr1) >> 8 + s10bu := (s10yy1 + 116130*s10cb1) >> 8 + if s10ru < 0 { + s10ru = 0 + } else if s10ru > 0xffff { + s10ru = 0xffff + } + if s10gu < 0 { + s10gu = 0 + } else if s10gu > 0xffff { + s10gu = 0xffff + } + if s10bu < 0 { + s10bu = 0 + } else if s10bu > 0xffff { + s10bu = 0xffff + } + + s10r := float64(s10ru) + s10g := float64(s10gu) + s10b := float64(s10bu) + s10r = xFrac1*s00r + xFrac0*s10r + s10g = xFrac1*s00g + xFrac0*s10g + s10b = xFrac1*s00b + xFrac0*s10b + s01i := (sy1-src.Rect.Min.Y)*src.YStride + (sx0 - src.Rect.Min.X) + s01j := ((sy1)/2-src.Rect.Min.Y/2)*src.CStride + ((sx0)/2 - src.Rect.Min.X/2) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s01yy1 := int(src.Y[s01i]) * 0x10100 + s01cb1 := int(src.Cb[s01j]) - 128 + s01cr1 := int(src.Cr[s01j]) - 128 + s01ru := (s01yy1 + 91881*s01cr1) >> 8 + s01gu := (s01yy1 - 22554*s01cb1 - 46802*s01cr1) >> 8 + s01bu := (s01yy1 + 116130*s01cb1) >> 8 + if s01ru < 0 { + s01ru = 0 + } else if s01ru > 0xffff { + s01ru = 0xffff + } + if s01gu < 0 { + s01gu = 0 + } else if s01gu > 0xffff { + s01gu = 0xffff + } + if s01bu < 0 { + s01bu = 0 + } else if s01bu > 0xffff { + s01bu = 0xffff + } + + s01r := float64(s01ru) + s01g := float64(s01gu) + s01b := float64(s01bu) + s11i := (sy1-src.Rect.Min.Y)*src.YStride + (sx1 - src.Rect.Min.X) + s11j := ((sy1)/2-src.Rect.Min.Y/2)*src.CStride + ((sx1)/2 - src.Rect.Min.X/2) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s11yy1 := int(src.Y[s11i]) * 0x10100 + s11cb1 := int(src.Cb[s11j]) - 128 + s11cr1 := int(src.Cr[s11j]) - 128 + s11ru := (s11yy1 + 91881*s11cr1) >> 8 + s11gu := (s11yy1 - 22554*s11cb1 - 46802*s11cr1) >> 8 + s11bu := (s11yy1 + 116130*s11cb1) >> 8 + if s11ru < 0 { + s11ru = 0 + } else if s11ru > 0xffff { + s11ru = 0xffff + } + if s11gu < 0 { + s11gu = 0 + } else if s11gu > 0xffff { + s11gu = 0xffff + } + if s11bu < 0 { + s11bu = 0 + } else if s11bu > 0xffff { + s11bu = 0xffff + } + + s11r := float64(s11ru) + s11g := float64(s11gu) + s11b := float64(s11bu) + s11r = xFrac1*s01r + xFrac0*s11r + s11g = xFrac1*s01g + xFrac0*s11g + s11b = xFrac1*s01b + xFrac0*s11b + s11r = yFrac1*s10r + yFrac0*s11r + s11g = yFrac1*s10g + yFrac0*s11g + s11b = yFrac1*s10b + yFrac0*s11b + pr := uint32(s11r) + pg := uint32(s11g) + pb := uint32(s11b) + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = 0xff + } + } +} + +func (ablInterpolator) transform_RGBA_YCbCr440_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point, opts *Options) { + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + sx -= 0.5 + sx0 := int(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx0 += bias.X + sx1 := sx0 + 1 + if sx0 < sr.Min.X { + sx0, sx1 = sr.Min.X, sr.Min.X + xFrac0, xFrac1 = 0, 1 + } else if sx1 >= sr.Max.X { + sx0, sx1 = sr.Max.X-1, sr.Max.X-1 + xFrac0, xFrac1 = 1, 0 + } + + sy -= 0.5 + sy0 := int(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy0 += bias.Y + sy1 := sy0 + 1 + if sy0 < sr.Min.Y { + sy0, sy1 = sr.Min.Y, sr.Min.Y + yFrac0, yFrac1 = 0, 1 + } else if sy1 >= sr.Max.Y { + sy0, sy1 = sr.Max.Y-1, sr.Max.Y-1 + yFrac0, yFrac1 = 1, 0 + } + + s00i := (sy0-src.Rect.Min.Y)*src.YStride + (sx0 - src.Rect.Min.X) + s00j := ((sy0)/2-src.Rect.Min.Y/2)*src.CStride + (sx0 - src.Rect.Min.X) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s00yy1 := int(src.Y[s00i]) * 0x10100 + s00cb1 := int(src.Cb[s00j]) - 128 + s00cr1 := int(src.Cr[s00j]) - 128 + s00ru := (s00yy1 + 91881*s00cr1) >> 8 + s00gu := (s00yy1 - 22554*s00cb1 - 46802*s00cr1) >> 8 + s00bu := (s00yy1 + 116130*s00cb1) >> 8 + if s00ru < 0 { + s00ru = 0 + } else if s00ru > 0xffff { + s00ru = 0xffff + } + if s00gu < 0 { + s00gu = 0 + } else if s00gu > 0xffff { + s00gu = 0xffff + } + if s00bu < 0 { + s00bu = 0 + } else if s00bu > 0xffff { + s00bu = 0xffff + } + + s00r := float64(s00ru) + s00g := float64(s00gu) + s00b := float64(s00bu) + s10i := (sy0-src.Rect.Min.Y)*src.YStride + (sx1 - src.Rect.Min.X) + s10j := ((sy0)/2-src.Rect.Min.Y/2)*src.CStride + (sx1 - src.Rect.Min.X) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s10yy1 := int(src.Y[s10i]) * 0x10100 + s10cb1 := int(src.Cb[s10j]) - 128 + s10cr1 := int(src.Cr[s10j]) - 128 + s10ru := (s10yy1 + 91881*s10cr1) >> 8 + s10gu := (s10yy1 - 22554*s10cb1 - 46802*s10cr1) >> 8 + s10bu := (s10yy1 + 116130*s10cb1) >> 8 + if s10ru < 0 { + s10ru = 0 + } else if s10ru > 0xffff { + s10ru = 0xffff + } + if s10gu < 0 { + s10gu = 0 + } else if s10gu > 0xffff { + s10gu = 0xffff + } + if s10bu < 0 { + s10bu = 0 + } else if s10bu > 0xffff { + s10bu = 0xffff + } + + s10r := float64(s10ru) + s10g := float64(s10gu) + s10b := float64(s10bu) + s10r = xFrac1*s00r + xFrac0*s10r + s10g = xFrac1*s00g + xFrac0*s10g + s10b = xFrac1*s00b + xFrac0*s10b + s01i := (sy1-src.Rect.Min.Y)*src.YStride + (sx0 - src.Rect.Min.X) + s01j := ((sy1)/2-src.Rect.Min.Y/2)*src.CStride + (sx0 - src.Rect.Min.X) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s01yy1 := int(src.Y[s01i]) * 0x10100 + s01cb1 := int(src.Cb[s01j]) - 128 + s01cr1 := int(src.Cr[s01j]) - 128 + s01ru := (s01yy1 + 91881*s01cr1) >> 8 + s01gu := (s01yy1 - 22554*s01cb1 - 46802*s01cr1) >> 8 + s01bu := (s01yy1 + 116130*s01cb1) >> 8 + if s01ru < 0 { + s01ru = 0 + } else if s01ru > 0xffff { + s01ru = 0xffff + } + if s01gu < 0 { + s01gu = 0 + } else if s01gu > 0xffff { + s01gu = 0xffff + } + if s01bu < 0 { + s01bu = 0 + } else if s01bu > 0xffff { + s01bu = 0xffff + } + + s01r := float64(s01ru) + s01g := float64(s01gu) + s01b := float64(s01bu) + s11i := (sy1-src.Rect.Min.Y)*src.YStride + (sx1 - src.Rect.Min.X) + s11j := ((sy1)/2-src.Rect.Min.Y/2)*src.CStride + (sx1 - src.Rect.Min.X) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + s11yy1 := int(src.Y[s11i]) * 0x10100 + s11cb1 := int(src.Cb[s11j]) - 128 + s11cr1 := int(src.Cr[s11j]) - 128 + s11ru := (s11yy1 + 91881*s11cr1) >> 8 + s11gu := (s11yy1 - 22554*s11cb1 - 46802*s11cr1) >> 8 + s11bu := (s11yy1 + 116130*s11cb1) >> 8 + if s11ru < 0 { + s11ru = 0 + } else if s11ru > 0xffff { + s11ru = 0xffff + } + if s11gu < 0 { + s11gu = 0 + } else if s11gu > 0xffff { + s11gu = 0xffff + } + if s11bu < 0 { + s11bu = 0 + } else if s11bu > 0xffff { + s11bu = 0xffff + } + + s11r := float64(s11ru) + s11g := float64(s11gu) + s11b := float64(s11bu) + s11r = xFrac1*s01r + xFrac0*s11r + s11g = xFrac1*s01g + xFrac0*s11g + s11b = xFrac1*s01b + xFrac0*s11b + s11r = yFrac1*s10r + yFrac0*s11r + s11g = yFrac1*s10g + yFrac0*s11g + s11b = yFrac1*s10b + yFrac0*s11b + pr := uint32(s11r) + pg := uint32(s11g) + pb := uint32(s11b) + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = 0xff + } + } +} + +func (ablInterpolator) transform_RGBA_Image_Over(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src image.Image, sr image.Rectangle, bias image.Point, opts *Options) { + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + sx -= 0.5 + sx0 := int(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx0 += bias.X + sx1 := sx0 + 1 + if sx0 < sr.Min.X { + sx0, sx1 = sr.Min.X, sr.Min.X + xFrac0, xFrac1 = 0, 1 + } else if sx1 >= sr.Max.X { + sx0, sx1 = sr.Max.X-1, sr.Max.X-1 + xFrac0, xFrac1 = 1, 0 + } + + sy -= 0.5 + sy0 := int(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy0 += bias.Y + sy1 := sy0 + 1 + if sy0 < sr.Min.Y { + sy0, sy1 = sr.Min.Y, sr.Min.Y + yFrac0, yFrac1 = 0, 1 + } else if sy1 >= sr.Max.Y { + sy0, sy1 = sr.Max.Y-1, sr.Max.Y-1 + yFrac0, yFrac1 = 1, 0 + } + + s00ru, s00gu, s00bu, s00au := src.At(sx0, sy0).RGBA() + s00r := float64(s00ru) + s00g := float64(s00gu) + s00b := float64(s00bu) + s00a := float64(s00au) + s10ru, s10gu, s10bu, s10au := src.At(sx1, sy0).RGBA() + s10r := float64(s10ru) + s10g := float64(s10gu) + s10b := float64(s10bu) + s10a := float64(s10au) + s10r = xFrac1*s00r + xFrac0*s10r + s10g = xFrac1*s00g + xFrac0*s10g + s10b = xFrac1*s00b + xFrac0*s10b + s10a = xFrac1*s00a + xFrac0*s10a + s01ru, s01gu, s01bu, s01au := src.At(sx0, sy1).RGBA() + s01r := float64(s01ru) + s01g := float64(s01gu) + s01b := float64(s01bu) + s01a := float64(s01au) + s11ru, s11gu, s11bu, s11au := src.At(sx1, sy1).RGBA() + s11r := float64(s11ru) + s11g := float64(s11gu) + s11b := float64(s11bu) + s11a := float64(s11au) + s11r = xFrac1*s01r + xFrac0*s11r + s11g = xFrac1*s01g + xFrac0*s11g + s11b = xFrac1*s01b + xFrac0*s11b + s11a = xFrac1*s01a + xFrac0*s11a + s11r = yFrac1*s10r + yFrac0*s11r + s11g = yFrac1*s10g + yFrac0*s11g + s11b = yFrac1*s10b + yFrac0*s11b + s11a = yFrac1*s10a + yFrac0*s11a + pr := uint32(s11r) + pg := uint32(s11g) + pb := uint32(s11b) + pa := uint32(s11a) + pa1 := (0xffff - pa) * 0x101 + dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*pa1/0xffff + pr) >> 8) + dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*pa1/0xffff + pg) >> 8) + dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*pa1/0xffff + pb) >> 8) + dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*pa1/0xffff + pa) >> 8) + } + } +} + +func (ablInterpolator) transform_RGBA_Image_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src image.Image, sr image.Rectangle, bias image.Point, opts *Options) { + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + sx -= 0.5 + sx0 := int(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx0 += bias.X + sx1 := sx0 + 1 + if sx0 < sr.Min.X { + sx0, sx1 = sr.Min.X, sr.Min.X + xFrac0, xFrac1 = 0, 1 + } else if sx1 >= sr.Max.X { + sx0, sx1 = sr.Max.X-1, sr.Max.X-1 + xFrac0, xFrac1 = 1, 0 + } + + sy -= 0.5 + sy0 := int(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy0 += bias.Y + sy1 := sy0 + 1 + if sy0 < sr.Min.Y { + sy0, sy1 = sr.Min.Y, sr.Min.Y + yFrac0, yFrac1 = 0, 1 + } else if sy1 >= sr.Max.Y { + sy0, sy1 = sr.Max.Y-1, sr.Max.Y-1 + yFrac0, yFrac1 = 1, 0 + } + + s00ru, s00gu, s00bu, s00au := src.At(sx0, sy0).RGBA() + s00r := float64(s00ru) + s00g := float64(s00gu) + s00b := float64(s00bu) + s00a := float64(s00au) + s10ru, s10gu, s10bu, s10au := src.At(sx1, sy0).RGBA() + s10r := float64(s10ru) + s10g := float64(s10gu) + s10b := float64(s10bu) + s10a := float64(s10au) + s10r = xFrac1*s00r + xFrac0*s10r + s10g = xFrac1*s00g + xFrac0*s10g + s10b = xFrac1*s00b + xFrac0*s10b + s10a = xFrac1*s00a + xFrac0*s10a + s01ru, s01gu, s01bu, s01au := src.At(sx0, sy1).RGBA() + s01r := float64(s01ru) + s01g := float64(s01gu) + s01b := float64(s01bu) + s01a := float64(s01au) + s11ru, s11gu, s11bu, s11au := src.At(sx1, sy1).RGBA() + s11r := float64(s11ru) + s11g := float64(s11gu) + s11b := float64(s11bu) + s11a := float64(s11au) + s11r = xFrac1*s01r + xFrac0*s11r + s11g = xFrac1*s01g + xFrac0*s11g + s11b = xFrac1*s01b + xFrac0*s11b + s11a = xFrac1*s01a + xFrac0*s11a + s11r = yFrac1*s10r + yFrac0*s11r + s11g = yFrac1*s10g + yFrac0*s11g + s11b = yFrac1*s10b + yFrac0*s11b + s11a = yFrac1*s10a + yFrac0*s11a + pr := uint32(s11r) + pg := uint32(s11g) + pb := uint32(s11b) + pa := uint32(s11a) + dst.Pix[d+0] = uint8(pr >> 8) + dst.Pix[d+1] = uint8(pg >> 8) + dst.Pix[d+2] = uint8(pb >> 8) + dst.Pix[d+3] = uint8(pa >> 8) + } + } +} + +func (ablInterpolator) transform_Image_Image_Over(dst Image, dr, adr image.Rectangle, d2s *f64.Aff3, src image.Image, sr image.Rectangle, bias image.Point, opts *Options) { + srcMask, smp := opts.SrcMask, opts.SrcMaskP + dstMask, dmp := opts.DstMask, opts.DstMaskP + dstColorRGBA64 := &color.RGBA64{} + dstColor := color.Color(dstColorRGBA64) + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + sx -= 0.5 + sx0 := int(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx0 += bias.X + sx1 := sx0 + 1 + if sx0 < sr.Min.X { + sx0, sx1 = sr.Min.X, sr.Min.X + xFrac0, xFrac1 = 0, 1 + } else if sx1 >= sr.Max.X { + sx0, sx1 = sr.Max.X-1, sr.Max.X-1 + xFrac0, xFrac1 = 1, 0 + } + + sy -= 0.5 + sy0 := int(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy0 += bias.Y + sy1 := sy0 + 1 + if sy0 < sr.Min.Y { + sy0, sy1 = sr.Min.Y, sr.Min.Y + yFrac0, yFrac1 = 0, 1 + } else if sy1 >= sr.Max.Y { + sy0, sy1 = sr.Max.Y-1, sr.Max.Y-1 + yFrac0, yFrac1 = 1, 0 + } + + s00ru, s00gu, s00bu, s00au := src.At(sx0, sy0).RGBA() + if srcMask != nil { + _, _, _, ma := srcMask.At(smp.X+sx0, smp.Y+sy0).RGBA() + s00ru = s00ru * ma / 0xffff + s00gu = s00gu * ma / 0xffff + s00bu = s00bu * ma / 0xffff + s00au = s00au * ma / 0xffff + } + s00r := float64(s00ru) + s00g := float64(s00gu) + s00b := float64(s00bu) + s00a := float64(s00au) + s10ru, s10gu, s10bu, s10au := src.At(sx1, sy0).RGBA() + if srcMask != nil { + _, _, _, ma := srcMask.At(smp.X+sx1, smp.Y+sy0).RGBA() + s10ru = s10ru * ma / 0xffff + s10gu = s10gu * ma / 0xffff + s10bu = s10bu * ma / 0xffff + s10au = s10au * ma / 0xffff + } + s10r := float64(s10ru) + s10g := float64(s10gu) + s10b := float64(s10bu) + s10a := float64(s10au) + s10r = xFrac1*s00r + xFrac0*s10r + s10g = xFrac1*s00g + xFrac0*s10g + s10b = xFrac1*s00b + xFrac0*s10b + s10a = xFrac1*s00a + xFrac0*s10a + s01ru, s01gu, s01bu, s01au := src.At(sx0, sy1).RGBA() + if srcMask != nil { + _, _, _, ma := srcMask.At(smp.X+sx0, smp.Y+sy1).RGBA() + s01ru = s01ru * ma / 0xffff + s01gu = s01gu * ma / 0xffff + s01bu = s01bu * ma / 0xffff + s01au = s01au * ma / 0xffff + } + s01r := float64(s01ru) + s01g := float64(s01gu) + s01b := float64(s01bu) + s01a := float64(s01au) + s11ru, s11gu, s11bu, s11au := src.At(sx1, sy1).RGBA() + if srcMask != nil { + _, _, _, ma := srcMask.At(smp.X+sx1, smp.Y+sy1).RGBA() + s11ru = s11ru * ma / 0xffff + s11gu = s11gu * ma / 0xffff + s11bu = s11bu * ma / 0xffff + s11au = s11au * ma / 0xffff + } + s11r := float64(s11ru) + s11g := float64(s11gu) + s11b := float64(s11bu) + s11a := float64(s11au) + s11r = xFrac1*s01r + xFrac0*s11r + s11g = xFrac1*s01g + xFrac0*s11g + s11b = xFrac1*s01b + xFrac0*s11b + s11a = xFrac1*s01a + xFrac0*s11a + s11r = yFrac1*s10r + yFrac0*s11r + s11g = yFrac1*s10g + yFrac0*s11g + s11b = yFrac1*s10b + yFrac0*s11b + s11a = yFrac1*s10a + yFrac0*s11a + pr := uint32(s11r) + pg := uint32(s11g) + pb := uint32(s11b) + pa := uint32(s11a) + qr, qg, qb, qa := dst.At(dr.Min.X+int(dx), dr.Min.Y+int(dy)).RGBA() + if dstMask != nil { + _, _, _, ma := dstMask.At(dmp.X+dr.Min.X+int(dx), dmp.Y+dr.Min.Y+int(dy)).RGBA() + pr = pr * ma / 0xffff + pg = pg * ma / 0xffff + pb = pb * ma / 0xffff + pa = pa * ma / 0xffff + } + pa1 := 0xffff - pa + dstColorRGBA64.R = uint16(qr*pa1/0xffff + pr) + dstColorRGBA64.G = uint16(qg*pa1/0xffff + pg) + dstColorRGBA64.B = uint16(qb*pa1/0xffff + pb) + dstColorRGBA64.A = uint16(qa*pa1/0xffff + pa) + dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor) + } + } +} + +func (ablInterpolator) transform_Image_Image_Src(dst Image, dr, adr image.Rectangle, d2s *f64.Aff3, src image.Image, sr image.Rectangle, bias image.Point, opts *Options) { + srcMask, smp := opts.SrcMask, opts.SrcMaskP + dstMask, dmp := opts.DstMask, opts.DstMaskP + dstColorRGBA64 := &color.RGBA64{} + dstColor := color.Color(dstColorRGBA64) + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + sx -= 0.5 + sx0 := int(sx) + xFrac0 := sx - float64(sx0) + xFrac1 := 1 - xFrac0 + sx0 += bias.X + sx1 := sx0 + 1 + if sx0 < sr.Min.X { + sx0, sx1 = sr.Min.X, sr.Min.X + xFrac0, xFrac1 = 0, 1 + } else if sx1 >= sr.Max.X { + sx0, sx1 = sr.Max.X-1, sr.Max.X-1 + xFrac0, xFrac1 = 1, 0 + } + + sy -= 0.5 + sy0 := int(sy) + yFrac0 := sy - float64(sy0) + yFrac1 := 1 - yFrac0 + sy0 += bias.Y + sy1 := sy0 + 1 + if sy0 < sr.Min.Y { + sy0, sy1 = sr.Min.Y, sr.Min.Y + yFrac0, yFrac1 = 0, 1 + } else if sy1 >= sr.Max.Y { + sy0, sy1 = sr.Max.Y-1, sr.Max.Y-1 + yFrac0, yFrac1 = 1, 0 + } + + s00ru, s00gu, s00bu, s00au := src.At(sx0, sy0).RGBA() + if srcMask != nil { + _, _, _, ma := srcMask.At(smp.X+sx0, smp.Y+sy0).RGBA() + s00ru = s00ru * ma / 0xffff + s00gu = s00gu * ma / 0xffff + s00bu = s00bu * ma / 0xffff + s00au = s00au * ma / 0xffff + } + s00r := float64(s00ru) + s00g := float64(s00gu) + s00b := float64(s00bu) + s00a := float64(s00au) + s10ru, s10gu, s10bu, s10au := src.At(sx1, sy0).RGBA() + if srcMask != nil { + _, _, _, ma := srcMask.At(smp.X+sx1, smp.Y+sy0).RGBA() + s10ru = s10ru * ma / 0xffff + s10gu = s10gu * ma / 0xffff + s10bu = s10bu * ma / 0xffff + s10au = s10au * ma / 0xffff + } + s10r := float64(s10ru) + s10g := float64(s10gu) + s10b := float64(s10bu) + s10a := float64(s10au) + s10r = xFrac1*s00r + xFrac0*s10r + s10g = xFrac1*s00g + xFrac0*s10g + s10b = xFrac1*s00b + xFrac0*s10b + s10a = xFrac1*s00a + xFrac0*s10a + s01ru, s01gu, s01bu, s01au := src.At(sx0, sy1).RGBA() + if srcMask != nil { + _, _, _, ma := srcMask.At(smp.X+sx0, smp.Y+sy1).RGBA() + s01ru = s01ru * ma / 0xffff + s01gu = s01gu * ma / 0xffff + s01bu = s01bu * ma / 0xffff + s01au = s01au * ma / 0xffff + } + s01r := float64(s01ru) + s01g := float64(s01gu) + s01b := float64(s01bu) + s01a := float64(s01au) + s11ru, s11gu, s11bu, s11au := src.At(sx1, sy1).RGBA() + if srcMask != nil { + _, _, _, ma := srcMask.At(smp.X+sx1, smp.Y+sy1).RGBA() + s11ru = s11ru * ma / 0xffff + s11gu = s11gu * ma / 0xffff + s11bu = s11bu * ma / 0xffff + s11au = s11au * ma / 0xffff + } + s11r := float64(s11ru) + s11g := float64(s11gu) + s11b := float64(s11bu) + s11a := float64(s11au) + s11r = xFrac1*s01r + xFrac0*s11r + s11g = xFrac1*s01g + xFrac0*s11g + s11b = xFrac1*s01b + xFrac0*s11b + s11a = xFrac1*s01a + xFrac0*s11a + s11r = yFrac1*s10r + yFrac0*s11r + s11g = yFrac1*s10g + yFrac0*s11g + s11b = yFrac1*s10b + yFrac0*s11b + s11a = yFrac1*s10a + yFrac0*s11a + pr := uint32(s11r) + pg := uint32(s11g) + pb := uint32(s11b) + pa := uint32(s11a) + if dstMask != nil { + qr, qg, qb, qa := dst.At(dr.Min.X+int(dx), dr.Min.Y+int(dy)).RGBA() + _, _, _, ma := dstMask.At(dmp.X+dr.Min.X+int(dx), dmp.Y+dr.Min.Y+int(dy)).RGBA() + pr = pr * ma / 0xffff + pg = pg * ma / 0xffff + pb = pb * ma / 0xffff + pa = pa * ma / 0xffff + pa1 := 0xffff - ma + dstColorRGBA64.R = uint16(qr*pa1/0xffff + pr) + dstColorRGBA64.G = uint16(qg*pa1/0xffff + pg) + dstColorRGBA64.B = uint16(qb*pa1/0xffff + pb) + dstColorRGBA64.A = uint16(qa*pa1/0xffff + pa) + dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor) + } else { + dstColorRGBA64.R = uint16(pr) + dstColorRGBA64.G = uint16(pg) + dstColorRGBA64.B = uint16(pb) + dstColorRGBA64.A = uint16(pa) + dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor) + } + } + } +} + +func (z *kernelScaler) Scale(dst Image, dr image.Rectangle, src image.Image, sr image.Rectangle, op Op, opts *Options) { + if z.dw != int32(dr.Dx()) || z.dh != int32(dr.Dy()) || z.sw != int32(sr.Dx()) || z.sh != int32(sr.Dy()) { + z.kernel.Scale(dst, dr, src, sr, op, opts) + return + } + + var o Options + if opts != nil { + o = *opts + } + + // adr is the affected destination pixels. + adr := dst.Bounds().Intersect(dr) + adr, o.DstMask = clipAffectedDestRect(adr, o.DstMask, o.DstMaskP) + if adr.Empty() || sr.Empty() { + return + } + // Make adr relative to dr.Min. + adr = adr.Sub(dr.Min) + if op == Over && o.SrcMask == nil && opaque(src) { + op = Src + } + + if _, ok := src.(*image.Uniform); ok && o.DstMask == nil && o.SrcMask == nil && sr.In(src.Bounds()) { + Draw(dst, dr, src, src.Bounds().Min, op) + return + } + + // Create a temporary buffer: + // scaleX distributes the source image's columns over the temporary image. + // scaleY distributes the temporary image's rows over the destination image. + var tmp [][4]float64 + if z.pool.New != nil { + tmpp := z.pool.Get().(*[][4]float64) + defer z.pool.Put(tmpp) + tmp = *tmpp + } else { + tmp = z.makeTmpBuf() + } + + // sr is the source pixels. If it extends beyond the src bounds, + // we cannot use the type-specific fast paths, as they access + // the Pix fields directly without bounds checking. + // + // Similarly, the fast paths assume that the masks are nil. + if o.SrcMask != nil || !sr.In(src.Bounds()) { + z.scaleX_Image(tmp, src, sr, &o) + } else { + switch src := src.(type) { + case *image.Gray: + z.scaleX_Gray(tmp, src, sr, &o) + case *image.NRGBA: + z.scaleX_NRGBA(tmp, src, sr, &o) + case *image.RGBA: + z.scaleX_RGBA(tmp, src, sr, &o) + case *image.YCbCr: + switch src.SubsampleRatio { + default: + z.scaleX_Image(tmp, src, sr, &o) + case image.YCbCrSubsampleRatio444: + z.scaleX_YCbCr444(tmp, src, sr, &o) + case image.YCbCrSubsampleRatio422: + z.scaleX_YCbCr422(tmp, src, sr, &o) + case image.YCbCrSubsampleRatio420: + z.scaleX_YCbCr420(tmp, src, sr, &o) + case image.YCbCrSubsampleRatio440: + z.scaleX_YCbCr440(tmp, src, sr, &o) + } + default: + z.scaleX_Image(tmp, src, sr, &o) + } + } + + if o.DstMask != nil { + switch op { + case Over: + z.scaleY_Image_Over(dst, dr, adr, tmp, &o) + case Src: + z.scaleY_Image_Src(dst, dr, adr, tmp, &o) + } + } else { + switch op { + case Over: + switch dst := dst.(type) { + case *image.RGBA: + z.scaleY_RGBA_Over(dst, dr, adr, tmp, &o) + default: + z.scaleY_Image_Over(dst, dr, adr, tmp, &o) + } + case Src: + switch dst := dst.(type) { + case *image.RGBA: + z.scaleY_RGBA_Src(dst, dr, adr, tmp, &o) + default: + z.scaleY_Image_Src(dst, dr, adr, tmp, &o) + } + } + } +} + +func (q *Kernel) Transform(dst Image, s2d f64.Aff3, src image.Image, sr image.Rectangle, op Op, opts *Options) { + var o Options + if opts != nil { + o = *opts + } + + dr := transformRect(&s2d, &sr) + // adr is the affected destination pixels. + adr := dst.Bounds().Intersect(dr) + adr, o.DstMask = clipAffectedDestRect(adr, o.DstMask, o.DstMaskP) + if adr.Empty() || sr.Empty() { + return + } + if op == Over && o.SrcMask == nil && opaque(src) { + op = Src + } + d2s := invert(&s2d) + // bias is a translation of the mapping from dst coordinates to src + // coordinates such that the latter temporarily have non-negative X + // and Y coordinates. This allows us to write int(f) instead of + // int(math.Floor(f)), since "round to zero" and "round down" are + // equivalent when f >= 0, but the former is much cheaper. The X-- + // and Y-- are because the TransformLeaf methods have a "sx -= 0.5" + // adjustment. + bias := transformRect(&d2s, &adr).Min + bias.X-- + bias.Y-- + d2s[2] -= float64(bias.X) + d2s[5] -= float64(bias.Y) + // Make adr relative to dr.Min. + adr = adr.Sub(dr.Min) + + if u, ok := src.(*image.Uniform); ok && o.DstMask != nil && o.SrcMask != nil && sr.In(src.Bounds()) { + transform_Uniform(dst, dr, adr, &d2s, u, sr, bias, op) + return + } + + xscale := abs(d2s[0]) + if s := abs(d2s[1]); xscale < s { + xscale = s + } + yscale := abs(d2s[3]) + if s := abs(d2s[4]); yscale < s { + yscale = s + } + + // sr is the source pixels. If it extends beyond the src bounds, + // we cannot use the type-specific fast paths, as they access + // the Pix fields directly without bounds checking. + // + // Similarly, the fast paths assume that the masks are nil. + if o.DstMask != nil || o.SrcMask != nil || !sr.In(src.Bounds()) { + switch op { + case Over: + q.transform_Image_Image_Over(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale, &o) + case Src: + q.transform_Image_Image_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale, &o) + } + } else { + switch op { + case Over: + switch dst := dst.(type) { + case *image.RGBA: + switch src := src.(type) { + case *image.NRGBA: + q.transform_RGBA_NRGBA_Over(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale, &o) + case *image.RGBA: + q.transform_RGBA_RGBA_Over(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale, &o) + default: + q.transform_RGBA_Image_Over(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale, &o) + } + default: + switch src := src.(type) { + default: + q.transform_Image_Image_Over(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale, &o) + } + } + case Src: + switch dst := dst.(type) { + case *image.RGBA: + switch src := src.(type) { + case *image.Gray: + q.transform_RGBA_Gray_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale, &o) + case *image.NRGBA: + q.transform_RGBA_NRGBA_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale, &o) + case *image.RGBA: + q.transform_RGBA_RGBA_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale, &o) + case *image.YCbCr: + switch src.SubsampleRatio { + default: + q.transform_RGBA_Image_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale, &o) + case image.YCbCrSubsampleRatio444: + q.transform_RGBA_YCbCr444_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale, &o) + case image.YCbCrSubsampleRatio422: + q.transform_RGBA_YCbCr422_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale, &o) + case image.YCbCrSubsampleRatio420: + q.transform_RGBA_YCbCr420_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale, &o) + case image.YCbCrSubsampleRatio440: + q.transform_RGBA_YCbCr440_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale, &o) + } + default: + q.transform_RGBA_Image_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale, &o) + } + default: + switch src := src.(type) { + default: + q.transform_Image_Image_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale, &o) + } + } + } + } +} + +func (z *kernelScaler) scaleX_Gray(tmp [][4]float64, src *image.Gray, sr image.Rectangle, opts *Options) { + t := 0 + for y := int32(0); y < z.sh; y++ { + for _, s := range z.horizontal.sources { + var pr float64 + for _, c := range z.horizontal.contribs[s.i:s.j] { + pi := (sr.Min.Y+int(y)-src.Rect.Min.Y)*src.Stride + (sr.Min.X + int(c.coord) - src.Rect.Min.X) + pru := uint32(src.Pix[pi]) * 0x101 + pr += float64(pru) * c.weight + } + pr *= s.invTotalWeightFFFF + tmp[t] = [4]float64{ + pr, + pr, + pr, + 1, + } + t++ + } + } +} + +func (z *kernelScaler) scaleX_NRGBA(tmp [][4]float64, src *image.NRGBA, sr image.Rectangle, opts *Options) { + t := 0 + for y := int32(0); y < z.sh; y++ { + for _, s := range z.horizontal.sources { + var pr, pg, pb, pa float64 + for _, c := range z.horizontal.contribs[s.i:s.j] { + pi := (sr.Min.Y+int(y)-src.Rect.Min.Y)*src.Stride + (sr.Min.X+int(c.coord)-src.Rect.Min.X)*4 + pau := uint32(src.Pix[pi+3]) * 0x101 + pru := uint32(src.Pix[pi+0]) * pau / 0xff + pgu := uint32(src.Pix[pi+1]) * pau / 0xff + pbu := uint32(src.Pix[pi+2]) * pau / 0xff + pr += float64(pru) * c.weight + pg += float64(pgu) * c.weight + pb += float64(pbu) * c.weight + pa += float64(pau) * c.weight + } + tmp[t] = [4]float64{ + pr * s.invTotalWeightFFFF, + pg * s.invTotalWeightFFFF, + pb * s.invTotalWeightFFFF, + pa * s.invTotalWeightFFFF, + } + t++ + } + } +} + +func (z *kernelScaler) scaleX_RGBA(tmp [][4]float64, src *image.RGBA, sr image.Rectangle, opts *Options) { + t := 0 + for y := int32(0); y < z.sh; y++ { + for _, s := range z.horizontal.sources { + var pr, pg, pb, pa float64 + for _, c := range z.horizontal.contribs[s.i:s.j] { + pi := (sr.Min.Y+int(y)-src.Rect.Min.Y)*src.Stride + (sr.Min.X+int(c.coord)-src.Rect.Min.X)*4 + pru := uint32(src.Pix[pi+0]) * 0x101 + pgu := uint32(src.Pix[pi+1]) * 0x101 + pbu := uint32(src.Pix[pi+2]) * 0x101 + pau := uint32(src.Pix[pi+3]) * 0x101 + pr += float64(pru) * c.weight + pg += float64(pgu) * c.weight + pb += float64(pbu) * c.weight + pa += float64(pau) * c.weight + } + tmp[t] = [4]float64{ + pr * s.invTotalWeightFFFF, + pg * s.invTotalWeightFFFF, + pb * s.invTotalWeightFFFF, + pa * s.invTotalWeightFFFF, + } + t++ + } + } +} + +func (z *kernelScaler) scaleX_YCbCr444(tmp [][4]float64, src *image.YCbCr, sr image.Rectangle, opts *Options) { + t := 0 + for y := int32(0); y < z.sh; y++ { + for _, s := range z.horizontal.sources { + var pr, pg, pb float64 + for _, c := range z.horizontal.contribs[s.i:s.j] { + pi := (sr.Min.Y+int(y)-src.Rect.Min.Y)*src.YStride + (sr.Min.X + int(c.coord) - src.Rect.Min.X) + pj := (sr.Min.Y+int(y)-src.Rect.Min.Y)*src.CStride + (sr.Min.X + int(c.coord) - src.Rect.Min.X) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + pyy1 := int(src.Y[pi]) * 0x10100 + pcb1 := int(src.Cb[pj]) - 128 + pcr1 := int(src.Cr[pj]) - 128 + pru := (pyy1 + 91881*pcr1) >> 8 + pgu := (pyy1 - 22554*pcb1 - 46802*pcr1) >> 8 + pbu := (pyy1 + 116130*pcb1) >> 8 + if pru < 0 { + pru = 0 + } else if pru > 0xffff { + pru = 0xffff + } + if pgu < 0 { + pgu = 0 + } else if pgu > 0xffff { + pgu = 0xffff + } + if pbu < 0 { + pbu = 0 + } else if pbu > 0xffff { + pbu = 0xffff + } + + pr += float64(pru) * c.weight + pg += float64(pgu) * c.weight + pb += float64(pbu) * c.weight + } + tmp[t] = [4]float64{ + pr * s.invTotalWeightFFFF, + pg * s.invTotalWeightFFFF, + pb * s.invTotalWeightFFFF, + 1, + } + t++ + } + } +} + +func (z *kernelScaler) scaleX_YCbCr422(tmp [][4]float64, src *image.YCbCr, sr image.Rectangle, opts *Options) { + t := 0 + for y := int32(0); y < z.sh; y++ { + for _, s := range z.horizontal.sources { + var pr, pg, pb float64 + for _, c := range z.horizontal.contribs[s.i:s.j] { + pi := (sr.Min.Y+int(y)-src.Rect.Min.Y)*src.YStride + (sr.Min.X + int(c.coord) - src.Rect.Min.X) + pj := (sr.Min.Y+int(y)-src.Rect.Min.Y)*src.CStride + ((sr.Min.X+int(c.coord))/2 - src.Rect.Min.X/2) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + pyy1 := int(src.Y[pi]) * 0x10100 + pcb1 := int(src.Cb[pj]) - 128 + pcr1 := int(src.Cr[pj]) - 128 + pru := (pyy1 + 91881*pcr1) >> 8 + pgu := (pyy1 - 22554*pcb1 - 46802*pcr1) >> 8 + pbu := (pyy1 + 116130*pcb1) >> 8 + if pru < 0 { + pru = 0 + } else if pru > 0xffff { + pru = 0xffff + } + if pgu < 0 { + pgu = 0 + } else if pgu > 0xffff { + pgu = 0xffff + } + if pbu < 0 { + pbu = 0 + } else if pbu > 0xffff { + pbu = 0xffff + } + + pr += float64(pru) * c.weight + pg += float64(pgu) * c.weight + pb += float64(pbu) * c.weight + } + tmp[t] = [4]float64{ + pr * s.invTotalWeightFFFF, + pg * s.invTotalWeightFFFF, + pb * s.invTotalWeightFFFF, + 1, + } + t++ + } + } +} + +func (z *kernelScaler) scaleX_YCbCr420(tmp [][4]float64, src *image.YCbCr, sr image.Rectangle, opts *Options) { + t := 0 + for y := int32(0); y < z.sh; y++ { + for _, s := range z.horizontal.sources { + var pr, pg, pb float64 + for _, c := range z.horizontal.contribs[s.i:s.j] { + pi := (sr.Min.Y+int(y)-src.Rect.Min.Y)*src.YStride + (sr.Min.X + int(c.coord) - src.Rect.Min.X) + pj := ((sr.Min.Y+int(y))/2-src.Rect.Min.Y/2)*src.CStride + ((sr.Min.X+int(c.coord))/2 - src.Rect.Min.X/2) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + pyy1 := int(src.Y[pi]) * 0x10100 + pcb1 := int(src.Cb[pj]) - 128 + pcr1 := int(src.Cr[pj]) - 128 + pru := (pyy1 + 91881*pcr1) >> 8 + pgu := (pyy1 - 22554*pcb1 - 46802*pcr1) >> 8 + pbu := (pyy1 + 116130*pcb1) >> 8 + if pru < 0 { + pru = 0 + } else if pru > 0xffff { + pru = 0xffff + } + if pgu < 0 { + pgu = 0 + } else if pgu > 0xffff { + pgu = 0xffff + } + if pbu < 0 { + pbu = 0 + } else if pbu > 0xffff { + pbu = 0xffff + } + + pr += float64(pru) * c.weight + pg += float64(pgu) * c.weight + pb += float64(pbu) * c.weight + } + tmp[t] = [4]float64{ + pr * s.invTotalWeightFFFF, + pg * s.invTotalWeightFFFF, + pb * s.invTotalWeightFFFF, + 1, + } + t++ + } + } +} + +func (z *kernelScaler) scaleX_YCbCr440(tmp [][4]float64, src *image.YCbCr, sr image.Rectangle, opts *Options) { + t := 0 + for y := int32(0); y < z.sh; y++ { + for _, s := range z.horizontal.sources { + var pr, pg, pb float64 + for _, c := range z.horizontal.contribs[s.i:s.j] { + pi := (sr.Min.Y+int(y)-src.Rect.Min.Y)*src.YStride + (sr.Min.X + int(c.coord) - src.Rect.Min.X) + pj := ((sr.Min.Y+int(y))/2-src.Rect.Min.Y/2)*src.CStride + (sr.Min.X + int(c.coord) - src.Rect.Min.X) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + pyy1 := int(src.Y[pi]) * 0x10100 + pcb1 := int(src.Cb[pj]) - 128 + pcr1 := int(src.Cr[pj]) - 128 + pru := (pyy1 + 91881*pcr1) >> 8 + pgu := (pyy1 - 22554*pcb1 - 46802*pcr1) >> 8 + pbu := (pyy1 + 116130*pcb1) >> 8 + if pru < 0 { + pru = 0 + } else if pru > 0xffff { + pru = 0xffff + } + if pgu < 0 { + pgu = 0 + } else if pgu > 0xffff { + pgu = 0xffff + } + if pbu < 0 { + pbu = 0 + } else if pbu > 0xffff { + pbu = 0xffff + } + + pr += float64(pru) * c.weight + pg += float64(pgu) * c.weight + pb += float64(pbu) * c.weight + } + tmp[t] = [4]float64{ + pr * s.invTotalWeightFFFF, + pg * s.invTotalWeightFFFF, + pb * s.invTotalWeightFFFF, + 1, + } + t++ + } + } +} + +func (z *kernelScaler) scaleX_Image(tmp [][4]float64, src image.Image, sr image.Rectangle, opts *Options) { + t := 0 + srcMask, smp := opts.SrcMask, opts.SrcMaskP + for y := int32(0); y < z.sh; y++ { + for _, s := range z.horizontal.sources { + var pr, pg, pb, pa float64 + for _, c := range z.horizontal.contribs[s.i:s.j] { + pru, pgu, pbu, pau := src.At(sr.Min.X+int(c.coord), sr.Min.Y+int(y)).RGBA() + if srcMask != nil { + _, _, _, ma := srcMask.At(smp.X+sr.Min.X+int(c.coord), smp.Y+sr.Min.Y+int(y)).RGBA() + pru = pru * ma / 0xffff + pgu = pgu * ma / 0xffff + pbu = pbu * ma / 0xffff + pau = pau * ma / 0xffff + } + pr += float64(pru) * c.weight + pg += float64(pgu) * c.weight + pb += float64(pbu) * c.weight + pa += float64(pau) * c.weight + } + tmp[t] = [4]float64{ + pr * s.invTotalWeightFFFF, + pg * s.invTotalWeightFFFF, + pb * s.invTotalWeightFFFF, + pa * s.invTotalWeightFFFF, + } + t++ + } + } +} + +func (z *kernelScaler) scaleY_RGBA_Over(dst *image.RGBA, dr, adr image.Rectangle, tmp [][4]float64, opts *Options) { + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { + d := (dr.Min.Y+adr.Min.Y-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+int(dx)-dst.Rect.Min.X)*4 + for _, s := range z.vertical.sources[adr.Min.Y:adr.Max.Y] { + var pr, pg, pb, pa float64 + for _, c := range z.vertical.contribs[s.i:s.j] { + p := &tmp[c.coord*z.dw+dx] + pr += p[0] * c.weight + pg += p[1] * c.weight + pb += p[2] * c.weight + pa += p[3] * c.weight + } + + if pr > pa { + pr = pa + } + if pg > pa { + pg = pa + } + if pb > pa { + pb = pa + } + + pr0 := uint32(ftou(pr * s.invTotalWeight)) + pg0 := uint32(ftou(pg * s.invTotalWeight)) + pb0 := uint32(ftou(pb * s.invTotalWeight)) + pa0 := uint32(ftou(pa * s.invTotalWeight)) + pa1 := (0xffff - uint32(pa0)) * 0x101 + dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*pa1/0xffff + pr0) >> 8) + dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*pa1/0xffff + pg0) >> 8) + dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*pa1/0xffff + pb0) >> 8) + dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*pa1/0xffff + pa0) >> 8) + d += dst.Stride + } + } +} + +func (z *kernelScaler) scaleY_RGBA_Src(dst *image.RGBA, dr, adr image.Rectangle, tmp [][4]float64, opts *Options) { + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { + d := (dr.Min.Y+adr.Min.Y-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+int(dx)-dst.Rect.Min.X)*4 + for _, s := range z.vertical.sources[adr.Min.Y:adr.Max.Y] { + var pr, pg, pb, pa float64 + for _, c := range z.vertical.contribs[s.i:s.j] { + p := &tmp[c.coord*z.dw+dx] + pr += p[0] * c.weight + pg += p[1] * c.weight + pb += p[2] * c.weight + pa += p[3] * c.weight + } + + if pr > pa { + pr = pa + } + if pg > pa { + pg = pa + } + if pb > pa { + pb = pa + } + + dst.Pix[d+0] = uint8(ftou(pr*s.invTotalWeight) >> 8) + dst.Pix[d+1] = uint8(ftou(pg*s.invTotalWeight) >> 8) + dst.Pix[d+2] = uint8(ftou(pb*s.invTotalWeight) >> 8) + dst.Pix[d+3] = uint8(ftou(pa*s.invTotalWeight) >> 8) + d += dst.Stride + } + } +} + +func (z *kernelScaler) scaleY_Image_Over(dst Image, dr, adr image.Rectangle, tmp [][4]float64, opts *Options) { + dstMask, dmp := opts.DstMask, opts.DstMaskP + dstColorRGBA64 := &color.RGBA64{} + dstColor := color.Color(dstColorRGBA64) + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { + for dy, s := range z.vertical.sources[adr.Min.Y:adr.Max.Y] { + var pr, pg, pb, pa float64 + for _, c := range z.vertical.contribs[s.i:s.j] { + p := &tmp[c.coord*z.dw+dx] + pr += p[0] * c.weight + pg += p[1] * c.weight + pb += p[2] * c.weight + pa += p[3] * c.weight + } + + if pr > pa { + pr = pa + } + if pg > pa { + pg = pa + } + if pb > pa { + pb = pa + } + + qr, qg, qb, qa := dst.At(dr.Min.X+int(dx), dr.Min.Y+int(adr.Min.Y+dy)).RGBA() + pr0 := uint32(ftou(pr * s.invTotalWeight)) + pg0 := uint32(ftou(pg * s.invTotalWeight)) + pb0 := uint32(ftou(pb * s.invTotalWeight)) + pa0 := uint32(ftou(pa * s.invTotalWeight)) + if dstMask != nil { + _, _, _, ma := dstMask.At(dmp.X+dr.Min.X+int(dx), dmp.Y+dr.Min.Y+int(adr.Min.Y+dy)).RGBA() + pr0 = pr0 * ma / 0xffff + pg0 = pg0 * ma / 0xffff + pb0 = pb0 * ma / 0xffff + pa0 = pa0 * ma / 0xffff + } + pa1 := 0xffff - pa0 + dstColorRGBA64.R = uint16(qr*pa1/0xffff + pr0) + dstColorRGBA64.G = uint16(qg*pa1/0xffff + pg0) + dstColorRGBA64.B = uint16(qb*pa1/0xffff + pb0) + dstColorRGBA64.A = uint16(qa*pa1/0xffff + pa0) + dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(adr.Min.Y+dy), dstColor) + } + } +} + +func (z *kernelScaler) scaleY_Image_Src(dst Image, dr, adr image.Rectangle, tmp [][4]float64, opts *Options) { + dstMask, dmp := opts.DstMask, opts.DstMaskP + dstColorRGBA64 := &color.RGBA64{} + dstColor := color.Color(dstColorRGBA64) + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { + for dy, s := range z.vertical.sources[adr.Min.Y:adr.Max.Y] { + var pr, pg, pb, pa float64 + for _, c := range z.vertical.contribs[s.i:s.j] { + p := &tmp[c.coord*z.dw+dx] + pr += p[0] * c.weight + pg += p[1] * c.weight + pb += p[2] * c.weight + pa += p[3] * c.weight + } + + if pr > pa { + pr = pa + } + if pg > pa { + pg = pa + } + if pb > pa { + pb = pa + } + + if dstMask != nil { + qr, qg, qb, qa := dst.At(dr.Min.X+int(dx), dr.Min.Y+int(adr.Min.Y+dy)).RGBA() + _, _, _, ma := dstMask.At(dmp.X+dr.Min.X+int(dx), dmp.Y+dr.Min.Y+int(adr.Min.Y+dy)).RGBA() + pr := uint32(ftou(pr*s.invTotalWeight)) * ma / 0xffff + pg := uint32(ftou(pg*s.invTotalWeight)) * ma / 0xffff + pb := uint32(ftou(pb*s.invTotalWeight)) * ma / 0xffff + pa := uint32(ftou(pa*s.invTotalWeight)) * ma / 0xffff + pa1 := 0xffff - ma + dstColorRGBA64.R = uint16(qr*pa1/0xffff + pr) + dstColorRGBA64.G = uint16(qg*pa1/0xffff + pg) + dstColorRGBA64.B = uint16(qb*pa1/0xffff + pb) + dstColorRGBA64.A = uint16(qa*pa1/0xffff + pa) + dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(adr.Min.Y+dy), dstColor) + } else { + dstColorRGBA64.R = ftou(pr * s.invTotalWeight) + dstColorRGBA64.G = ftou(pg * s.invTotalWeight) + dstColorRGBA64.B = ftou(pb * s.invTotalWeight) + dstColorRGBA64.A = ftou(pa * s.invTotalWeight) + dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(adr.Min.Y+dy), dstColor) + } + } + } +} + +func (q *Kernel) transform_RGBA_Gray_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.Gray, sr image.Rectangle, bias image.Point, xscale, yscale float64, opts *Options) { + // When shrinking, broaden the effective kernel support so that we still + // visit every source pixel. + xHalfWidth, xKernelArgScale := q.Support, 1.0 + if xscale > 1 { + xHalfWidth *= xscale + xKernelArgScale = 1 / xscale + } + yHalfWidth, yKernelArgScale := q.Support, 1.0 + if yscale > 1 { + yHalfWidth *= yscale + yKernelArgScale = 1 / yscale + } + + xWeights := make([]float64, 1+2*int(math.Ceil(xHalfWidth))) + yWeights := make([]float64, 1+2*int(math.Ceil(yHalfWidth))) + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + // TODO: adjust the bias so that we can use int(f) instead + // of math.Floor(f) and math.Ceil(f). + sx += float64(bias.X) + sx -= 0.5 + ix := int(math.Floor(sx - xHalfWidth)) + if ix < sr.Min.X { + ix = sr.Min.X + } + jx := int(math.Ceil(sx + xHalfWidth)) + if jx > sr.Max.X { + jx = sr.Max.X + } + + totalXWeight := 0.0 + for kx := ix; kx < jx; kx++ { + xWeight := 0.0 + if t := abs((sx - float64(kx)) * xKernelArgScale); t < q.Support { + xWeight = q.At(t) + } + xWeights[kx-ix] = xWeight + totalXWeight += xWeight + } + for x := range xWeights[:jx-ix] { + xWeights[x] /= totalXWeight + } + + sy += float64(bias.Y) + sy -= 0.5 + iy := int(math.Floor(sy - yHalfWidth)) + if iy < sr.Min.Y { + iy = sr.Min.Y + } + jy := int(math.Ceil(sy + yHalfWidth)) + if jy > sr.Max.Y { + jy = sr.Max.Y + } + + totalYWeight := 0.0 + for ky := iy; ky < jy; ky++ { + yWeight := 0.0 + if t := abs((sy - float64(ky)) * yKernelArgScale); t < q.Support { + yWeight = q.At(t) + } + yWeights[ky-iy] = yWeight + totalYWeight += yWeight + } + for y := range yWeights[:jy-iy] { + yWeights[y] /= totalYWeight + } + + var pr float64 + for ky := iy; ky < jy; ky++ { + if yWeight := yWeights[ky-iy]; yWeight != 0 { + for kx := ix; kx < jx; kx++ { + if w := xWeights[kx-ix] * yWeight; w != 0 { + pi := (ky-src.Rect.Min.Y)*src.Stride + (kx - src.Rect.Min.X) + pru := uint32(src.Pix[pi]) * 0x101 + pr += float64(pru) * w + } + } + } + } + out := uint8(fffftou(pr) >> 8) + dst.Pix[d+0] = out + dst.Pix[d+1] = out + dst.Pix[d+2] = out + dst.Pix[d+3] = 0xff + } + } +} + +func (q *Kernel) transform_RGBA_NRGBA_Over(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.NRGBA, sr image.Rectangle, bias image.Point, xscale, yscale float64, opts *Options) { + // When shrinking, broaden the effective kernel support so that we still + // visit every source pixel. + xHalfWidth, xKernelArgScale := q.Support, 1.0 + if xscale > 1 { + xHalfWidth *= xscale + xKernelArgScale = 1 / xscale + } + yHalfWidth, yKernelArgScale := q.Support, 1.0 + if yscale > 1 { + yHalfWidth *= yscale + yKernelArgScale = 1 / yscale + } + + xWeights := make([]float64, 1+2*int(math.Ceil(xHalfWidth))) + yWeights := make([]float64, 1+2*int(math.Ceil(yHalfWidth))) + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + // TODO: adjust the bias so that we can use int(f) instead + // of math.Floor(f) and math.Ceil(f). + sx += float64(bias.X) + sx -= 0.5 + ix := int(math.Floor(sx - xHalfWidth)) + if ix < sr.Min.X { + ix = sr.Min.X + } + jx := int(math.Ceil(sx + xHalfWidth)) + if jx > sr.Max.X { + jx = sr.Max.X + } + + totalXWeight := 0.0 + for kx := ix; kx < jx; kx++ { + xWeight := 0.0 + if t := abs((sx - float64(kx)) * xKernelArgScale); t < q.Support { + xWeight = q.At(t) + } + xWeights[kx-ix] = xWeight + totalXWeight += xWeight + } + for x := range xWeights[:jx-ix] { + xWeights[x] /= totalXWeight + } + + sy += float64(bias.Y) + sy -= 0.5 + iy := int(math.Floor(sy - yHalfWidth)) + if iy < sr.Min.Y { + iy = sr.Min.Y + } + jy := int(math.Ceil(sy + yHalfWidth)) + if jy > sr.Max.Y { + jy = sr.Max.Y + } + + totalYWeight := 0.0 + for ky := iy; ky < jy; ky++ { + yWeight := 0.0 + if t := abs((sy - float64(ky)) * yKernelArgScale); t < q.Support { + yWeight = q.At(t) + } + yWeights[ky-iy] = yWeight + totalYWeight += yWeight + } + for y := range yWeights[:jy-iy] { + yWeights[y] /= totalYWeight + } + + var pr, pg, pb, pa float64 + for ky := iy; ky < jy; ky++ { + if yWeight := yWeights[ky-iy]; yWeight != 0 { + for kx := ix; kx < jx; kx++ { + if w := xWeights[kx-ix] * yWeight; w != 0 { + pi := (ky-src.Rect.Min.Y)*src.Stride + (kx-src.Rect.Min.X)*4 + pau := uint32(src.Pix[pi+3]) * 0x101 + pru := uint32(src.Pix[pi+0]) * pau / 0xff + pgu := uint32(src.Pix[pi+1]) * pau / 0xff + pbu := uint32(src.Pix[pi+2]) * pau / 0xff + pr += float64(pru) * w + pg += float64(pgu) * w + pb += float64(pbu) * w + pa += float64(pau) * w + } + } + } + } + + if pr > pa { + pr = pa + } + if pg > pa { + pg = pa + } + if pb > pa { + pb = pa + } + + pr0 := uint32(fffftou(pr)) + pg0 := uint32(fffftou(pg)) + pb0 := uint32(fffftou(pb)) + pa0 := uint32(fffftou(pa)) + pa1 := (0xffff - uint32(pa0)) * 0x101 + dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*pa1/0xffff + pr0) >> 8) + dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*pa1/0xffff + pg0) >> 8) + dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*pa1/0xffff + pb0) >> 8) + dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*pa1/0xffff + pa0) >> 8) + } + } +} + +func (q *Kernel) transform_RGBA_NRGBA_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.NRGBA, sr image.Rectangle, bias image.Point, xscale, yscale float64, opts *Options) { + // When shrinking, broaden the effective kernel support so that we still + // visit every source pixel. + xHalfWidth, xKernelArgScale := q.Support, 1.0 + if xscale > 1 { + xHalfWidth *= xscale + xKernelArgScale = 1 / xscale + } + yHalfWidth, yKernelArgScale := q.Support, 1.0 + if yscale > 1 { + yHalfWidth *= yscale + yKernelArgScale = 1 / yscale + } + + xWeights := make([]float64, 1+2*int(math.Ceil(xHalfWidth))) + yWeights := make([]float64, 1+2*int(math.Ceil(yHalfWidth))) + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + // TODO: adjust the bias so that we can use int(f) instead + // of math.Floor(f) and math.Ceil(f). + sx += float64(bias.X) + sx -= 0.5 + ix := int(math.Floor(sx - xHalfWidth)) + if ix < sr.Min.X { + ix = sr.Min.X + } + jx := int(math.Ceil(sx + xHalfWidth)) + if jx > sr.Max.X { + jx = sr.Max.X + } + + totalXWeight := 0.0 + for kx := ix; kx < jx; kx++ { + xWeight := 0.0 + if t := abs((sx - float64(kx)) * xKernelArgScale); t < q.Support { + xWeight = q.At(t) + } + xWeights[kx-ix] = xWeight + totalXWeight += xWeight + } + for x := range xWeights[:jx-ix] { + xWeights[x] /= totalXWeight + } + + sy += float64(bias.Y) + sy -= 0.5 + iy := int(math.Floor(sy - yHalfWidth)) + if iy < sr.Min.Y { + iy = sr.Min.Y + } + jy := int(math.Ceil(sy + yHalfWidth)) + if jy > sr.Max.Y { + jy = sr.Max.Y + } + + totalYWeight := 0.0 + for ky := iy; ky < jy; ky++ { + yWeight := 0.0 + if t := abs((sy - float64(ky)) * yKernelArgScale); t < q.Support { + yWeight = q.At(t) + } + yWeights[ky-iy] = yWeight + totalYWeight += yWeight + } + for y := range yWeights[:jy-iy] { + yWeights[y] /= totalYWeight + } + + var pr, pg, pb, pa float64 + for ky := iy; ky < jy; ky++ { + if yWeight := yWeights[ky-iy]; yWeight != 0 { + for kx := ix; kx < jx; kx++ { + if w := xWeights[kx-ix] * yWeight; w != 0 { + pi := (ky-src.Rect.Min.Y)*src.Stride + (kx-src.Rect.Min.X)*4 + pau := uint32(src.Pix[pi+3]) * 0x101 + pru := uint32(src.Pix[pi+0]) * pau / 0xff + pgu := uint32(src.Pix[pi+1]) * pau / 0xff + pbu := uint32(src.Pix[pi+2]) * pau / 0xff + pr += float64(pru) * w + pg += float64(pgu) * w + pb += float64(pbu) * w + pa += float64(pau) * w + } + } + } + } + + if pr > pa { + pr = pa + } + if pg > pa { + pg = pa + } + if pb > pa { + pb = pa + } + + dst.Pix[d+0] = uint8(fffftou(pr) >> 8) + dst.Pix[d+1] = uint8(fffftou(pg) >> 8) + dst.Pix[d+2] = uint8(fffftou(pb) >> 8) + dst.Pix[d+3] = uint8(fffftou(pa) >> 8) + } + } +} + +func (q *Kernel) transform_RGBA_RGBA_Over(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.RGBA, sr image.Rectangle, bias image.Point, xscale, yscale float64, opts *Options) { + // When shrinking, broaden the effective kernel support so that we still + // visit every source pixel. + xHalfWidth, xKernelArgScale := q.Support, 1.0 + if xscale > 1 { + xHalfWidth *= xscale + xKernelArgScale = 1 / xscale + } + yHalfWidth, yKernelArgScale := q.Support, 1.0 + if yscale > 1 { + yHalfWidth *= yscale + yKernelArgScale = 1 / yscale + } + + xWeights := make([]float64, 1+2*int(math.Ceil(xHalfWidth))) + yWeights := make([]float64, 1+2*int(math.Ceil(yHalfWidth))) + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + // TODO: adjust the bias so that we can use int(f) instead + // of math.Floor(f) and math.Ceil(f). + sx += float64(bias.X) + sx -= 0.5 + ix := int(math.Floor(sx - xHalfWidth)) + if ix < sr.Min.X { + ix = sr.Min.X + } + jx := int(math.Ceil(sx + xHalfWidth)) + if jx > sr.Max.X { + jx = sr.Max.X + } + + totalXWeight := 0.0 + for kx := ix; kx < jx; kx++ { + xWeight := 0.0 + if t := abs((sx - float64(kx)) * xKernelArgScale); t < q.Support { + xWeight = q.At(t) + } + xWeights[kx-ix] = xWeight + totalXWeight += xWeight + } + for x := range xWeights[:jx-ix] { + xWeights[x] /= totalXWeight + } + + sy += float64(bias.Y) + sy -= 0.5 + iy := int(math.Floor(sy - yHalfWidth)) + if iy < sr.Min.Y { + iy = sr.Min.Y + } + jy := int(math.Ceil(sy + yHalfWidth)) + if jy > sr.Max.Y { + jy = sr.Max.Y + } + + totalYWeight := 0.0 + for ky := iy; ky < jy; ky++ { + yWeight := 0.0 + if t := abs((sy - float64(ky)) * yKernelArgScale); t < q.Support { + yWeight = q.At(t) + } + yWeights[ky-iy] = yWeight + totalYWeight += yWeight + } + for y := range yWeights[:jy-iy] { + yWeights[y] /= totalYWeight + } + + var pr, pg, pb, pa float64 + for ky := iy; ky < jy; ky++ { + if yWeight := yWeights[ky-iy]; yWeight != 0 { + for kx := ix; kx < jx; kx++ { + if w := xWeights[kx-ix] * yWeight; w != 0 { + pi := (ky-src.Rect.Min.Y)*src.Stride + (kx-src.Rect.Min.X)*4 + pru := uint32(src.Pix[pi+0]) * 0x101 + pgu := uint32(src.Pix[pi+1]) * 0x101 + pbu := uint32(src.Pix[pi+2]) * 0x101 + pau := uint32(src.Pix[pi+3]) * 0x101 + pr += float64(pru) * w + pg += float64(pgu) * w + pb += float64(pbu) * w + pa += float64(pau) * w + } + } + } + } + + if pr > pa { + pr = pa + } + if pg > pa { + pg = pa + } + if pb > pa { + pb = pa + } + + pr0 := uint32(fffftou(pr)) + pg0 := uint32(fffftou(pg)) + pb0 := uint32(fffftou(pb)) + pa0 := uint32(fffftou(pa)) + pa1 := (0xffff - uint32(pa0)) * 0x101 + dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*pa1/0xffff + pr0) >> 8) + dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*pa1/0xffff + pg0) >> 8) + dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*pa1/0xffff + pb0) >> 8) + dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*pa1/0xffff + pa0) >> 8) + } + } +} + +func (q *Kernel) transform_RGBA_RGBA_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.RGBA, sr image.Rectangle, bias image.Point, xscale, yscale float64, opts *Options) { + // When shrinking, broaden the effective kernel support so that we still + // visit every source pixel. + xHalfWidth, xKernelArgScale := q.Support, 1.0 + if xscale > 1 { + xHalfWidth *= xscale + xKernelArgScale = 1 / xscale + } + yHalfWidth, yKernelArgScale := q.Support, 1.0 + if yscale > 1 { + yHalfWidth *= yscale + yKernelArgScale = 1 / yscale + } + + xWeights := make([]float64, 1+2*int(math.Ceil(xHalfWidth))) + yWeights := make([]float64, 1+2*int(math.Ceil(yHalfWidth))) + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + // TODO: adjust the bias so that we can use int(f) instead + // of math.Floor(f) and math.Ceil(f). + sx += float64(bias.X) + sx -= 0.5 + ix := int(math.Floor(sx - xHalfWidth)) + if ix < sr.Min.X { + ix = sr.Min.X + } + jx := int(math.Ceil(sx + xHalfWidth)) + if jx > sr.Max.X { + jx = sr.Max.X + } + + totalXWeight := 0.0 + for kx := ix; kx < jx; kx++ { + xWeight := 0.0 + if t := abs((sx - float64(kx)) * xKernelArgScale); t < q.Support { + xWeight = q.At(t) + } + xWeights[kx-ix] = xWeight + totalXWeight += xWeight + } + for x := range xWeights[:jx-ix] { + xWeights[x] /= totalXWeight + } + + sy += float64(bias.Y) + sy -= 0.5 + iy := int(math.Floor(sy - yHalfWidth)) + if iy < sr.Min.Y { + iy = sr.Min.Y + } + jy := int(math.Ceil(sy + yHalfWidth)) + if jy > sr.Max.Y { + jy = sr.Max.Y + } + + totalYWeight := 0.0 + for ky := iy; ky < jy; ky++ { + yWeight := 0.0 + if t := abs((sy - float64(ky)) * yKernelArgScale); t < q.Support { + yWeight = q.At(t) + } + yWeights[ky-iy] = yWeight + totalYWeight += yWeight + } + for y := range yWeights[:jy-iy] { + yWeights[y] /= totalYWeight + } + + var pr, pg, pb, pa float64 + for ky := iy; ky < jy; ky++ { + if yWeight := yWeights[ky-iy]; yWeight != 0 { + for kx := ix; kx < jx; kx++ { + if w := xWeights[kx-ix] * yWeight; w != 0 { + pi := (ky-src.Rect.Min.Y)*src.Stride + (kx-src.Rect.Min.X)*4 + pru := uint32(src.Pix[pi+0]) * 0x101 + pgu := uint32(src.Pix[pi+1]) * 0x101 + pbu := uint32(src.Pix[pi+2]) * 0x101 + pau := uint32(src.Pix[pi+3]) * 0x101 + pr += float64(pru) * w + pg += float64(pgu) * w + pb += float64(pbu) * w + pa += float64(pau) * w + } + } + } + } + + if pr > pa { + pr = pa + } + if pg > pa { + pg = pa + } + if pb > pa { + pb = pa + } + + dst.Pix[d+0] = uint8(fffftou(pr) >> 8) + dst.Pix[d+1] = uint8(fffftou(pg) >> 8) + dst.Pix[d+2] = uint8(fffftou(pb) >> 8) + dst.Pix[d+3] = uint8(fffftou(pa) >> 8) + } + } +} + +func (q *Kernel) transform_RGBA_YCbCr444_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point, xscale, yscale float64, opts *Options) { + // When shrinking, broaden the effective kernel support so that we still + // visit every source pixel. + xHalfWidth, xKernelArgScale := q.Support, 1.0 + if xscale > 1 { + xHalfWidth *= xscale + xKernelArgScale = 1 / xscale + } + yHalfWidth, yKernelArgScale := q.Support, 1.0 + if yscale > 1 { + yHalfWidth *= yscale + yKernelArgScale = 1 / yscale + } + + xWeights := make([]float64, 1+2*int(math.Ceil(xHalfWidth))) + yWeights := make([]float64, 1+2*int(math.Ceil(yHalfWidth))) + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + // TODO: adjust the bias so that we can use int(f) instead + // of math.Floor(f) and math.Ceil(f). + sx += float64(bias.X) + sx -= 0.5 + ix := int(math.Floor(sx - xHalfWidth)) + if ix < sr.Min.X { + ix = sr.Min.X + } + jx := int(math.Ceil(sx + xHalfWidth)) + if jx > sr.Max.X { + jx = sr.Max.X + } + + totalXWeight := 0.0 + for kx := ix; kx < jx; kx++ { + xWeight := 0.0 + if t := abs((sx - float64(kx)) * xKernelArgScale); t < q.Support { + xWeight = q.At(t) + } + xWeights[kx-ix] = xWeight + totalXWeight += xWeight + } + for x := range xWeights[:jx-ix] { + xWeights[x] /= totalXWeight + } + + sy += float64(bias.Y) + sy -= 0.5 + iy := int(math.Floor(sy - yHalfWidth)) + if iy < sr.Min.Y { + iy = sr.Min.Y + } + jy := int(math.Ceil(sy + yHalfWidth)) + if jy > sr.Max.Y { + jy = sr.Max.Y + } + + totalYWeight := 0.0 + for ky := iy; ky < jy; ky++ { + yWeight := 0.0 + if t := abs((sy - float64(ky)) * yKernelArgScale); t < q.Support { + yWeight = q.At(t) + } + yWeights[ky-iy] = yWeight + totalYWeight += yWeight + } + for y := range yWeights[:jy-iy] { + yWeights[y] /= totalYWeight + } + + var pr, pg, pb float64 + for ky := iy; ky < jy; ky++ { + if yWeight := yWeights[ky-iy]; yWeight != 0 { + for kx := ix; kx < jx; kx++ { + if w := xWeights[kx-ix] * yWeight; w != 0 { + pi := (ky-src.Rect.Min.Y)*src.YStride + (kx - src.Rect.Min.X) + pj := (ky-src.Rect.Min.Y)*src.CStride + (kx - src.Rect.Min.X) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + pyy1 := int(src.Y[pi]) * 0x10100 + pcb1 := int(src.Cb[pj]) - 128 + pcr1 := int(src.Cr[pj]) - 128 + pru := (pyy1 + 91881*pcr1) >> 8 + pgu := (pyy1 - 22554*pcb1 - 46802*pcr1) >> 8 + pbu := (pyy1 + 116130*pcb1) >> 8 + if pru < 0 { + pru = 0 + } else if pru > 0xffff { + pru = 0xffff + } + if pgu < 0 { + pgu = 0 + } else if pgu > 0xffff { + pgu = 0xffff + } + if pbu < 0 { + pbu = 0 + } else if pbu > 0xffff { + pbu = 0xffff + } + + pr += float64(pru) * w + pg += float64(pgu) * w + pb += float64(pbu) * w + } + } + } + } + dst.Pix[d+0] = uint8(fffftou(pr) >> 8) + dst.Pix[d+1] = uint8(fffftou(pg) >> 8) + dst.Pix[d+2] = uint8(fffftou(pb) >> 8) + dst.Pix[d+3] = 0xff + } + } +} + +func (q *Kernel) transform_RGBA_YCbCr422_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point, xscale, yscale float64, opts *Options) { + // When shrinking, broaden the effective kernel support so that we still + // visit every source pixel. + xHalfWidth, xKernelArgScale := q.Support, 1.0 + if xscale > 1 { + xHalfWidth *= xscale + xKernelArgScale = 1 / xscale + } + yHalfWidth, yKernelArgScale := q.Support, 1.0 + if yscale > 1 { + yHalfWidth *= yscale + yKernelArgScale = 1 / yscale + } + + xWeights := make([]float64, 1+2*int(math.Ceil(xHalfWidth))) + yWeights := make([]float64, 1+2*int(math.Ceil(yHalfWidth))) + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + // TODO: adjust the bias so that we can use int(f) instead + // of math.Floor(f) and math.Ceil(f). + sx += float64(bias.X) + sx -= 0.5 + ix := int(math.Floor(sx - xHalfWidth)) + if ix < sr.Min.X { + ix = sr.Min.X + } + jx := int(math.Ceil(sx + xHalfWidth)) + if jx > sr.Max.X { + jx = sr.Max.X + } + + totalXWeight := 0.0 + for kx := ix; kx < jx; kx++ { + xWeight := 0.0 + if t := abs((sx - float64(kx)) * xKernelArgScale); t < q.Support { + xWeight = q.At(t) + } + xWeights[kx-ix] = xWeight + totalXWeight += xWeight + } + for x := range xWeights[:jx-ix] { + xWeights[x] /= totalXWeight + } + + sy += float64(bias.Y) + sy -= 0.5 + iy := int(math.Floor(sy - yHalfWidth)) + if iy < sr.Min.Y { + iy = sr.Min.Y + } + jy := int(math.Ceil(sy + yHalfWidth)) + if jy > sr.Max.Y { + jy = sr.Max.Y + } + + totalYWeight := 0.0 + for ky := iy; ky < jy; ky++ { + yWeight := 0.0 + if t := abs((sy - float64(ky)) * yKernelArgScale); t < q.Support { + yWeight = q.At(t) + } + yWeights[ky-iy] = yWeight + totalYWeight += yWeight + } + for y := range yWeights[:jy-iy] { + yWeights[y] /= totalYWeight + } + + var pr, pg, pb float64 + for ky := iy; ky < jy; ky++ { + if yWeight := yWeights[ky-iy]; yWeight != 0 { + for kx := ix; kx < jx; kx++ { + if w := xWeights[kx-ix] * yWeight; w != 0 { + pi := (ky-src.Rect.Min.Y)*src.YStride + (kx - src.Rect.Min.X) + pj := (ky-src.Rect.Min.Y)*src.CStride + ((kx)/2 - src.Rect.Min.X/2) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + pyy1 := int(src.Y[pi]) * 0x10100 + pcb1 := int(src.Cb[pj]) - 128 + pcr1 := int(src.Cr[pj]) - 128 + pru := (pyy1 + 91881*pcr1) >> 8 + pgu := (pyy1 - 22554*pcb1 - 46802*pcr1) >> 8 + pbu := (pyy1 + 116130*pcb1) >> 8 + if pru < 0 { + pru = 0 + } else if pru > 0xffff { + pru = 0xffff + } + if pgu < 0 { + pgu = 0 + } else if pgu > 0xffff { + pgu = 0xffff + } + if pbu < 0 { + pbu = 0 + } else if pbu > 0xffff { + pbu = 0xffff + } + + pr += float64(pru) * w + pg += float64(pgu) * w + pb += float64(pbu) * w + } + } + } + } + dst.Pix[d+0] = uint8(fffftou(pr) >> 8) + dst.Pix[d+1] = uint8(fffftou(pg) >> 8) + dst.Pix[d+2] = uint8(fffftou(pb) >> 8) + dst.Pix[d+3] = 0xff + } + } +} + +func (q *Kernel) transform_RGBA_YCbCr420_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point, xscale, yscale float64, opts *Options) { + // When shrinking, broaden the effective kernel support so that we still + // visit every source pixel. + xHalfWidth, xKernelArgScale := q.Support, 1.0 + if xscale > 1 { + xHalfWidth *= xscale + xKernelArgScale = 1 / xscale + } + yHalfWidth, yKernelArgScale := q.Support, 1.0 + if yscale > 1 { + yHalfWidth *= yscale + yKernelArgScale = 1 / yscale + } + + xWeights := make([]float64, 1+2*int(math.Ceil(xHalfWidth))) + yWeights := make([]float64, 1+2*int(math.Ceil(yHalfWidth))) + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + // TODO: adjust the bias so that we can use int(f) instead + // of math.Floor(f) and math.Ceil(f). + sx += float64(bias.X) + sx -= 0.5 + ix := int(math.Floor(sx - xHalfWidth)) + if ix < sr.Min.X { + ix = sr.Min.X + } + jx := int(math.Ceil(sx + xHalfWidth)) + if jx > sr.Max.X { + jx = sr.Max.X + } + + totalXWeight := 0.0 + for kx := ix; kx < jx; kx++ { + xWeight := 0.0 + if t := abs((sx - float64(kx)) * xKernelArgScale); t < q.Support { + xWeight = q.At(t) + } + xWeights[kx-ix] = xWeight + totalXWeight += xWeight + } + for x := range xWeights[:jx-ix] { + xWeights[x] /= totalXWeight + } + + sy += float64(bias.Y) + sy -= 0.5 + iy := int(math.Floor(sy - yHalfWidth)) + if iy < sr.Min.Y { + iy = sr.Min.Y + } + jy := int(math.Ceil(sy + yHalfWidth)) + if jy > sr.Max.Y { + jy = sr.Max.Y + } + + totalYWeight := 0.0 + for ky := iy; ky < jy; ky++ { + yWeight := 0.0 + if t := abs((sy - float64(ky)) * yKernelArgScale); t < q.Support { + yWeight = q.At(t) + } + yWeights[ky-iy] = yWeight + totalYWeight += yWeight + } + for y := range yWeights[:jy-iy] { + yWeights[y] /= totalYWeight + } + + var pr, pg, pb float64 + for ky := iy; ky < jy; ky++ { + if yWeight := yWeights[ky-iy]; yWeight != 0 { + for kx := ix; kx < jx; kx++ { + if w := xWeights[kx-ix] * yWeight; w != 0 { + pi := (ky-src.Rect.Min.Y)*src.YStride + (kx - src.Rect.Min.X) + pj := ((ky)/2-src.Rect.Min.Y/2)*src.CStride + ((kx)/2 - src.Rect.Min.X/2) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + pyy1 := int(src.Y[pi]) * 0x10100 + pcb1 := int(src.Cb[pj]) - 128 + pcr1 := int(src.Cr[pj]) - 128 + pru := (pyy1 + 91881*pcr1) >> 8 + pgu := (pyy1 - 22554*pcb1 - 46802*pcr1) >> 8 + pbu := (pyy1 + 116130*pcb1) >> 8 + if pru < 0 { + pru = 0 + } else if pru > 0xffff { + pru = 0xffff + } + if pgu < 0 { + pgu = 0 + } else if pgu > 0xffff { + pgu = 0xffff + } + if pbu < 0 { + pbu = 0 + } else if pbu > 0xffff { + pbu = 0xffff + } + + pr += float64(pru) * w + pg += float64(pgu) * w + pb += float64(pbu) * w + } + } + } + } + dst.Pix[d+0] = uint8(fffftou(pr) >> 8) + dst.Pix[d+1] = uint8(fffftou(pg) >> 8) + dst.Pix[d+2] = uint8(fffftou(pb) >> 8) + dst.Pix[d+3] = 0xff + } + } +} + +func (q *Kernel) transform_RGBA_YCbCr440_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point, xscale, yscale float64, opts *Options) { + // When shrinking, broaden the effective kernel support so that we still + // visit every source pixel. + xHalfWidth, xKernelArgScale := q.Support, 1.0 + if xscale > 1 { + xHalfWidth *= xscale + xKernelArgScale = 1 / xscale + } + yHalfWidth, yKernelArgScale := q.Support, 1.0 + if yscale > 1 { + yHalfWidth *= yscale + yKernelArgScale = 1 / yscale + } + + xWeights := make([]float64, 1+2*int(math.Ceil(xHalfWidth))) + yWeights := make([]float64, 1+2*int(math.Ceil(yHalfWidth))) + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + // TODO: adjust the bias so that we can use int(f) instead + // of math.Floor(f) and math.Ceil(f). + sx += float64(bias.X) + sx -= 0.5 + ix := int(math.Floor(sx - xHalfWidth)) + if ix < sr.Min.X { + ix = sr.Min.X + } + jx := int(math.Ceil(sx + xHalfWidth)) + if jx > sr.Max.X { + jx = sr.Max.X + } + + totalXWeight := 0.0 + for kx := ix; kx < jx; kx++ { + xWeight := 0.0 + if t := abs((sx - float64(kx)) * xKernelArgScale); t < q.Support { + xWeight = q.At(t) + } + xWeights[kx-ix] = xWeight + totalXWeight += xWeight + } + for x := range xWeights[:jx-ix] { + xWeights[x] /= totalXWeight + } + + sy += float64(bias.Y) + sy -= 0.5 + iy := int(math.Floor(sy - yHalfWidth)) + if iy < sr.Min.Y { + iy = sr.Min.Y + } + jy := int(math.Ceil(sy + yHalfWidth)) + if jy > sr.Max.Y { + jy = sr.Max.Y + } + + totalYWeight := 0.0 + for ky := iy; ky < jy; ky++ { + yWeight := 0.0 + if t := abs((sy - float64(ky)) * yKernelArgScale); t < q.Support { + yWeight = q.At(t) + } + yWeights[ky-iy] = yWeight + totalYWeight += yWeight + } + for y := range yWeights[:jy-iy] { + yWeights[y] /= totalYWeight + } + + var pr, pg, pb float64 + for ky := iy; ky < jy; ky++ { + if yWeight := yWeights[ky-iy]; yWeight != 0 { + for kx := ix; kx < jx; kx++ { + if w := xWeights[kx-ix] * yWeight; w != 0 { + pi := (ky-src.Rect.Min.Y)*src.YStride + (kx - src.Rect.Min.X) + pj := ((ky)/2-src.Rect.Min.Y/2)*src.CStride + (kx - src.Rect.Min.X) + + // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. + pyy1 := int(src.Y[pi]) * 0x10100 + pcb1 := int(src.Cb[pj]) - 128 + pcr1 := int(src.Cr[pj]) - 128 + pru := (pyy1 + 91881*pcr1) >> 8 + pgu := (pyy1 - 22554*pcb1 - 46802*pcr1) >> 8 + pbu := (pyy1 + 116130*pcb1) >> 8 + if pru < 0 { + pru = 0 + } else if pru > 0xffff { + pru = 0xffff + } + if pgu < 0 { + pgu = 0 + } else if pgu > 0xffff { + pgu = 0xffff + } + if pbu < 0 { + pbu = 0 + } else if pbu > 0xffff { + pbu = 0xffff + } + + pr += float64(pru) * w + pg += float64(pgu) * w + pb += float64(pbu) * w + } + } + } + } + dst.Pix[d+0] = uint8(fffftou(pr) >> 8) + dst.Pix[d+1] = uint8(fffftou(pg) >> 8) + dst.Pix[d+2] = uint8(fffftou(pb) >> 8) + dst.Pix[d+3] = 0xff + } + } +} + +func (q *Kernel) transform_RGBA_Image_Over(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src image.Image, sr image.Rectangle, bias image.Point, xscale, yscale float64, opts *Options) { + // When shrinking, broaden the effective kernel support so that we still + // visit every source pixel. + xHalfWidth, xKernelArgScale := q.Support, 1.0 + if xscale > 1 { + xHalfWidth *= xscale + xKernelArgScale = 1 / xscale + } + yHalfWidth, yKernelArgScale := q.Support, 1.0 + if yscale > 1 { + yHalfWidth *= yscale + yKernelArgScale = 1 / yscale + } + + xWeights := make([]float64, 1+2*int(math.Ceil(xHalfWidth))) + yWeights := make([]float64, 1+2*int(math.Ceil(yHalfWidth))) + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + // TODO: adjust the bias so that we can use int(f) instead + // of math.Floor(f) and math.Ceil(f). + sx += float64(bias.X) + sx -= 0.5 + ix := int(math.Floor(sx - xHalfWidth)) + if ix < sr.Min.X { + ix = sr.Min.X + } + jx := int(math.Ceil(sx + xHalfWidth)) + if jx > sr.Max.X { + jx = sr.Max.X + } + + totalXWeight := 0.0 + for kx := ix; kx < jx; kx++ { + xWeight := 0.0 + if t := abs((sx - float64(kx)) * xKernelArgScale); t < q.Support { + xWeight = q.At(t) + } + xWeights[kx-ix] = xWeight + totalXWeight += xWeight + } + for x := range xWeights[:jx-ix] { + xWeights[x] /= totalXWeight + } + + sy += float64(bias.Y) + sy -= 0.5 + iy := int(math.Floor(sy - yHalfWidth)) + if iy < sr.Min.Y { + iy = sr.Min.Y + } + jy := int(math.Ceil(sy + yHalfWidth)) + if jy > sr.Max.Y { + jy = sr.Max.Y + } + + totalYWeight := 0.0 + for ky := iy; ky < jy; ky++ { + yWeight := 0.0 + if t := abs((sy - float64(ky)) * yKernelArgScale); t < q.Support { + yWeight = q.At(t) + } + yWeights[ky-iy] = yWeight + totalYWeight += yWeight + } + for y := range yWeights[:jy-iy] { + yWeights[y] /= totalYWeight + } + + var pr, pg, pb, pa float64 + for ky := iy; ky < jy; ky++ { + if yWeight := yWeights[ky-iy]; yWeight != 0 { + for kx := ix; kx < jx; kx++ { + if w := xWeights[kx-ix] * yWeight; w != 0 { + pru, pgu, pbu, pau := src.At(kx, ky).RGBA() + pr += float64(pru) * w + pg += float64(pgu) * w + pb += float64(pbu) * w + pa += float64(pau) * w + } + } + } + } + + if pr > pa { + pr = pa + } + if pg > pa { + pg = pa + } + if pb > pa { + pb = pa + } + + pr0 := uint32(fffftou(pr)) + pg0 := uint32(fffftou(pg)) + pb0 := uint32(fffftou(pb)) + pa0 := uint32(fffftou(pa)) + pa1 := (0xffff - uint32(pa0)) * 0x101 + dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*pa1/0xffff + pr0) >> 8) + dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*pa1/0xffff + pg0) >> 8) + dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*pa1/0xffff + pb0) >> 8) + dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*pa1/0xffff + pa0) >> 8) + } + } +} + +func (q *Kernel) transform_RGBA_Image_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src image.Image, sr image.Rectangle, bias image.Point, xscale, yscale float64, opts *Options) { + // When shrinking, broaden the effective kernel support so that we still + // visit every source pixel. + xHalfWidth, xKernelArgScale := q.Support, 1.0 + if xscale > 1 { + xHalfWidth *= xscale + xKernelArgScale = 1 / xscale + } + yHalfWidth, yKernelArgScale := q.Support, 1.0 + if yscale > 1 { + yHalfWidth *= yscale + yKernelArgScale = 1 / yscale + } + + xWeights := make([]float64, 1+2*int(math.Ceil(xHalfWidth))) + yWeights := make([]float64, 1+2*int(math.Ceil(yHalfWidth))) + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + // TODO: adjust the bias so that we can use int(f) instead + // of math.Floor(f) and math.Ceil(f). + sx += float64(bias.X) + sx -= 0.5 + ix := int(math.Floor(sx - xHalfWidth)) + if ix < sr.Min.X { + ix = sr.Min.X + } + jx := int(math.Ceil(sx + xHalfWidth)) + if jx > sr.Max.X { + jx = sr.Max.X + } + + totalXWeight := 0.0 + for kx := ix; kx < jx; kx++ { + xWeight := 0.0 + if t := abs((sx - float64(kx)) * xKernelArgScale); t < q.Support { + xWeight = q.At(t) + } + xWeights[kx-ix] = xWeight + totalXWeight += xWeight + } + for x := range xWeights[:jx-ix] { + xWeights[x] /= totalXWeight + } + + sy += float64(bias.Y) + sy -= 0.5 + iy := int(math.Floor(sy - yHalfWidth)) + if iy < sr.Min.Y { + iy = sr.Min.Y + } + jy := int(math.Ceil(sy + yHalfWidth)) + if jy > sr.Max.Y { + jy = sr.Max.Y + } + + totalYWeight := 0.0 + for ky := iy; ky < jy; ky++ { + yWeight := 0.0 + if t := abs((sy - float64(ky)) * yKernelArgScale); t < q.Support { + yWeight = q.At(t) + } + yWeights[ky-iy] = yWeight + totalYWeight += yWeight + } + for y := range yWeights[:jy-iy] { + yWeights[y] /= totalYWeight + } + + var pr, pg, pb, pa float64 + for ky := iy; ky < jy; ky++ { + if yWeight := yWeights[ky-iy]; yWeight != 0 { + for kx := ix; kx < jx; kx++ { + if w := xWeights[kx-ix] * yWeight; w != 0 { + pru, pgu, pbu, pau := src.At(kx, ky).RGBA() + pr += float64(pru) * w + pg += float64(pgu) * w + pb += float64(pbu) * w + pa += float64(pau) * w + } + } + } + } + + if pr > pa { + pr = pa + } + if pg > pa { + pg = pa + } + if pb > pa { + pb = pa + } + + dst.Pix[d+0] = uint8(fffftou(pr) >> 8) + dst.Pix[d+1] = uint8(fffftou(pg) >> 8) + dst.Pix[d+2] = uint8(fffftou(pb) >> 8) + dst.Pix[d+3] = uint8(fffftou(pa) >> 8) + } + } +} + +func (q *Kernel) transform_Image_Image_Over(dst Image, dr, adr image.Rectangle, d2s *f64.Aff3, src image.Image, sr image.Rectangle, bias image.Point, xscale, yscale float64, opts *Options) { + // When shrinking, broaden the effective kernel support so that we still + // visit every source pixel. + xHalfWidth, xKernelArgScale := q.Support, 1.0 + if xscale > 1 { + xHalfWidth *= xscale + xKernelArgScale = 1 / xscale + } + yHalfWidth, yKernelArgScale := q.Support, 1.0 + if yscale > 1 { + yHalfWidth *= yscale + yKernelArgScale = 1 / yscale + } + + xWeights := make([]float64, 1+2*int(math.Ceil(xHalfWidth))) + yWeights := make([]float64, 1+2*int(math.Ceil(yHalfWidth))) + + srcMask, smp := opts.SrcMask, opts.SrcMaskP + dstMask, dmp := opts.DstMask, opts.DstMaskP + dstColorRGBA64 := &color.RGBA64{} + dstColor := color.Color(dstColorRGBA64) + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + // TODO: adjust the bias so that we can use int(f) instead + // of math.Floor(f) and math.Ceil(f). + sx += float64(bias.X) + sx -= 0.5 + ix := int(math.Floor(sx - xHalfWidth)) + if ix < sr.Min.X { + ix = sr.Min.X + } + jx := int(math.Ceil(sx + xHalfWidth)) + if jx > sr.Max.X { + jx = sr.Max.X + } + + totalXWeight := 0.0 + for kx := ix; kx < jx; kx++ { + xWeight := 0.0 + if t := abs((sx - float64(kx)) * xKernelArgScale); t < q.Support { + xWeight = q.At(t) + } + xWeights[kx-ix] = xWeight + totalXWeight += xWeight + } + for x := range xWeights[:jx-ix] { + xWeights[x] /= totalXWeight + } + + sy += float64(bias.Y) + sy -= 0.5 + iy := int(math.Floor(sy - yHalfWidth)) + if iy < sr.Min.Y { + iy = sr.Min.Y + } + jy := int(math.Ceil(sy + yHalfWidth)) + if jy > sr.Max.Y { + jy = sr.Max.Y + } + + totalYWeight := 0.0 + for ky := iy; ky < jy; ky++ { + yWeight := 0.0 + if t := abs((sy - float64(ky)) * yKernelArgScale); t < q.Support { + yWeight = q.At(t) + } + yWeights[ky-iy] = yWeight + totalYWeight += yWeight + } + for y := range yWeights[:jy-iy] { + yWeights[y] /= totalYWeight + } + + var pr, pg, pb, pa float64 + for ky := iy; ky < jy; ky++ { + if yWeight := yWeights[ky-iy]; yWeight != 0 { + for kx := ix; kx < jx; kx++ { + if w := xWeights[kx-ix] * yWeight; w != 0 { + pru, pgu, pbu, pau := src.At(kx, ky).RGBA() + if srcMask != nil { + _, _, _, ma := srcMask.At(smp.X+kx, smp.Y+ky).RGBA() + pru = pru * ma / 0xffff + pgu = pgu * ma / 0xffff + pbu = pbu * ma / 0xffff + pau = pau * ma / 0xffff + } + pr += float64(pru) * w + pg += float64(pgu) * w + pb += float64(pbu) * w + pa += float64(pau) * w + } + } + } + } + + if pr > pa { + pr = pa + } + if pg > pa { + pg = pa + } + if pb > pa { + pb = pa + } + + qr, qg, qb, qa := dst.At(dr.Min.X+int(dx), dr.Min.Y+int(dy)).RGBA() + pr0 := uint32(fffftou(pr)) + pg0 := uint32(fffftou(pg)) + pb0 := uint32(fffftou(pb)) + pa0 := uint32(fffftou(pa)) + if dstMask != nil { + _, _, _, ma := dstMask.At(dmp.X+dr.Min.X+int(dx), dmp.Y+dr.Min.Y+int(dy)).RGBA() + pr0 = pr0 * ma / 0xffff + pg0 = pg0 * ma / 0xffff + pb0 = pb0 * ma / 0xffff + pa0 = pa0 * ma / 0xffff + } + pa1 := 0xffff - pa0 + dstColorRGBA64.R = uint16(qr*pa1/0xffff + pr0) + dstColorRGBA64.G = uint16(qg*pa1/0xffff + pg0) + dstColorRGBA64.B = uint16(qb*pa1/0xffff + pb0) + dstColorRGBA64.A = uint16(qa*pa1/0xffff + pa0) + dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor) + } + } +} + +func (q *Kernel) transform_Image_Image_Src(dst Image, dr, adr image.Rectangle, d2s *f64.Aff3, src image.Image, sr image.Rectangle, bias image.Point, xscale, yscale float64, opts *Options) { + // When shrinking, broaden the effective kernel support so that we still + // visit every source pixel. + xHalfWidth, xKernelArgScale := q.Support, 1.0 + if xscale > 1 { + xHalfWidth *= xscale + xKernelArgScale = 1 / xscale + } + yHalfWidth, yKernelArgScale := q.Support, 1.0 + if yscale > 1 { + yHalfWidth *= yscale + yKernelArgScale = 1 / yscale + } + + xWeights := make([]float64, 1+2*int(math.Ceil(xHalfWidth))) + yWeights := make([]float64, 1+2*int(math.Ceil(yHalfWidth))) + + srcMask, smp := opts.SrcMask, opts.SrcMaskP + dstMask, dmp := opts.DstMask, opts.DstMaskP + dstColorRGBA64 := &color.RGBA64{} + dstColor := color.Color(dstColorRGBA64) + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] + sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] + if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { + continue + } + + // TODO: adjust the bias so that we can use int(f) instead + // of math.Floor(f) and math.Ceil(f). + sx += float64(bias.X) + sx -= 0.5 + ix := int(math.Floor(sx - xHalfWidth)) + if ix < sr.Min.X { + ix = sr.Min.X + } + jx := int(math.Ceil(sx + xHalfWidth)) + if jx > sr.Max.X { + jx = sr.Max.X + } + + totalXWeight := 0.0 + for kx := ix; kx < jx; kx++ { + xWeight := 0.0 + if t := abs((sx - float64(kx)) * xKernelArgScale); t < q.Support { + xWeight = q.At(t) + } + xWeights[kx-ix] = xWeight + totalXWeight += xWeight + } + for x := range xWeights[:jx-ix] { + xWeights[x] /= totalXWeight + } + + sy += float64(bias.Y) + sy -= 0.5 + iy := int(math.Floor(sy - yHalfWidth)) + if iy < sr.Min.Y { + iy = sr.Min.Y + } + jy := int(math.Ceil(sy + yHalfWidth)) + if jy > sr.Max.Y { + jy = sr.Max.Y + } + + totalYWeight := 0.0 + for ky := iy; ky < jy; ky++ { + yWeight := 0.0 + if t := abs((sy - float64(ky)) * yKernelArgScale); t < q.Support { + yWeight = q.At(t) + } + yWeights[ky-iy] = yWeight + totalYWeight += yWeight + } + for y := range yWeights[:jy-iy] { + yWeights[y] /= totalYWeight + } + + var pr, pg, pb, pa float64 + for ky := iy; ky < jy; ky++ { + if yWeight := yWeights[ky-iy]; yWeight != 0 { + for kx := ix; kx < jx; kx++ { + if w := xWeights[kx-ix] * yWeight; w != 0 { + pru, pgu, pbu, pau := src.At(kx, ky).RGBA() + if srcMask != nil { + _, _, _, ma := srcMask.At(smp.X+kx, smp.Y+ky).RGBA() + pru = pru * ma / 0xffff + pgu = pgu * ma / 0xffff + pbu = pbu * ma / 0xffff + pau = pau * ma / 0xffff + } + pr += float64(pru) * w + pg += float64(pgu) * w + pb += float64(pbu) * w + pa += float64(pau) * w + } + } + } + } + + if pr > pa { + pr = pa + } + if pg > pa { + pg = pa + } + if pb > pa { + pb = pa + } + + if dstMask != nil { + qr, qg, qb, qa := dst.At(dr.Min.X+int(dx), dr.Min.Y+int(dy)).RGBA() + _, _, _, ma := dstMask.At(dmp.X+dr.Min.X+int(dx), dmp.Y+dr.Min.Y+int(dy)).RGBA() + pr := uint32(fffftou(pr)) * ma / 0xffff + pg := uint32(fffftou(pg)) * ma / 0xffff + pb := uint32(fffftou(pb)) * ma / 0xffff + pa := uint32(fffftou(pa)) * ma / 0xffff + pa1 := 0xffff - ma + dstColorRGBA64.R = uint16(qr*pa1/0xffff + pr) + dstColorRGBA64.G = uint16(qg*pa1/0xffff + pg) + dstColorRGBA64.B = uint16(qb*pa1/0xffff + pb) + dstColorRGBA64.A = uint16(qa*pa1/0xffff + pa) + dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor) + } else { + dstColorRGBA64.R = fffftou(pr) + dstColorRGBA64.G = fffftou(pg) + dstColorRGBA64.B = fffftou(pb) + dstColorRGBA64.A = fffftou(pa) + dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor) + } + } + } +} diff --git a/src/golang.org/x/image/draw/scale.go b/src/golang.org/x/image/draw/scale.go new file mode 100644 index 0000000000..98ab404eb1 --- /dev/null +++ b/src/golang.org/x/image/draw/scale.go @@ -0,0 +1,527 @@ +// Copyright 2015 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. + +//go:generate go run gen.go + +package draw + +import ( + "image" + "image/color" + "math" + "sync" + + "golang.org/x/image/math/f64" +) + +// Copy copies the part of the source image defined by src and sr and writes +// the result of a Porter-Duff composition to the part of the destination image +// defined by dst and the translation of sr so that sr.Min translates to dp. +func Copy(dst Image, dp image.Point, src image.Image, sr image.Rectangle, op Op, opts *Options) { + var o Options + if opts != nil { + o = *opts + } + dr := sr.Add(dp.Sub(sr.Min)) + if o.DstMask == nil { + DrawMask(dst, dr, src, sr.Min, o.SrcMask, o.SrcMaskP.Add(sr.Min), op) + } else { + NearestNeighbor.Scale(dst, dr, src, sr, op, opts) + } +} + +// Scaler scales the part of the source image defined by src and sr and writes +// the result of a Porter-Duff composition to the part of the destination image +// defined by dst and dr. +// +// A Scaler is safe to use concurrently. +type Scaler interface { + Scale(dst Image, dr image.Rectangle, src image.Image, sr image.Rectangle, op Op, opts *Options) +} + +// Transformer transforms the part of the source image defined by src and sr +// and writes the result of a Porter-Duff composition to the part of the +// destination image defined by dst and the affine transform m applied to sr. +// +// For example, if m is the matrix +// +// m00 m01 m02 +// m10 m11 m12 +// +// then the src-space point (sx, sy) maps to the dst-space point +// (m00*sx + m01*sy + m02, m10*sx + m11*sy + m12). +// +// A Transformer is safe to use concurrently. +type Transformer interface { + Transform(dst Image, m f64.Aff3, src image.Image, sr image.Rectangle, op Op, opts *Options) +} + +// Options are optional parameters to Copy, Scale and Transform. +// +// A nil *Options means to use the default (zero) values of each field. +type Options struct { + // Masks limit what parts of the dst image are drawn to and what parts of + // the src image are drawn from. + // + // A dst or src mask image having a zero alpha (transparent) pixel value in + // the respective coordinate space means that that dst pixel is entirely + // unaffected or that src pixel is considered transparent black. A full + // alpha (opaque) value means that the dst pixel is maximally affected or + // the src pixel contributes maximally. The default values, nil, are + // equivalent to fully opaque, infinitely large mask images. + // + // The DstMask is otherwise known as a clip mask, and its pixels map 1:1 to + // the dst image's pixels. DstMaskP in DstMask space corresponds to + // image.Point{X:0, Y:0} in dst space. For example, when limiting + // repainting to a 'dirty rectangle', use that image.Rectangle and a zero + // image.Point as the DstMask and DstMaskP. + // + // The SrcMask's pixels map 1:1 to the src image's pixels. SrcMaskP in + // SrcMask space corresponds to image.Point{X:0, Y:0} in src space. For + // example, when drawing font glyphs in a uniform color, use an + // *image.Uniform as the src, and use the glyph atlas image and the + // per-glyph offset as SrcMask and SrcMaskP: + // Copy(dst, dp, image.NewUniform(color), image.Rect(0, 0, glyphWidth, glyphHeight), &Options{ + // SrcMask: glyphAtlas, + // SrcMaskP: glyphOffset, + // }) + DstMask image.Image + DstMaskP image.Point + SrcMask image.Image + SrcMaskP image.Point + + // TODO: a smooth vs sharp edges option, for arbitrary rotations? +} + +// Interpolator is an interpolation algorithm, when dst and src pixels don't +// have a 1:1 correspondence. +// +// Of the interpolators provided by this package: +// - NearestNeighbor is fast but usually looks worst. +// - CatmullRom is slow but usually looks best. +// - ApproxBiLinear has reasonable speed and quality. +// +// The time taken depends on the size of dr. For kernel interpolators, the +// speed also depends on the size of sr, and so are often slower than +// non-kernel interpolators, especially when scaling down. +type Interpolator interface { + Scaler + Transformer +} + +// Kernel is an interpolator that blends source pixels weighted by a symmetric +// kernel function. +type Kernel struct { + // Support is the kernel support and must be >= 0. At(t) is assumed to be + // zero when t >= Support. + Support float64 + // At is the kernel function. It will only be called with t in the + // range [0, Support). + At func(t float64) float64 +} + +// Scale implements the Scaler interface. +func (q *Kernel) Scale(dst Image, dr image.Rectangle, src image.Image, sr image.Rectangle, op Op, opts *Options) { + q.newScaler(dr.Dx(), dr.Dy(), sr.Dx(), sr.Dy(), false).Scale(dst, dr, src, sr, op, opts) +} + +// NewScaler returns a Scaler that is optimized for scaling multiple times with +// the same fixed destination and source width and height. +func (q *Kernel) NewScaler(dw, dh, sw, sh int) Scaler { + return q.newScaler(dw, dh, sw, sh, true) +} + +func (q *Kernel) newScaler(dw, dh, sw, sh int, usePool bool) Scaler { + z := &kernelScaler{ + kernel: q, + dw: int32(dw), + dh: int32(dh), + sw: int32(sw), + sh: int32(sh), + horizontal: newDistrib(q, int32(dw), int32(sw)), + vertical: newDistrib(q, int32(dh), int32(sh)), + } + if usePool { + z.pool.New = func() interface{} { + tmp := z.makeTmpBuf() + return &tmp + } + } + return z +} + +var ( + // NearestNeighbor is the nearest neighbor interpolator. It is very fast, + // but usually gives very low quality results. When scaling up, the result + // will look 'blocky'. + NearestNeighbor = Interpolator(nnInterpolator{}) + + // ApproxBiLinear is a mixture of the nearest neighbor and bi-linear + // interpolators. It is fast, but usually gives medium quality results. + // + // It implements bi-linear interpolation when upscaling and a bi-linear + // blend of the 4 nearest neighbor pixels when downscaling. This yields + // nicer quality than nearest neighbor interpolation when upscaling, but + // the time taken is independent of the number of source pixels, unlike the + // bi-linear interpolator. When downscaling a large image, the performance + // difference can be significant. + ApproxBiLinear = Interpolator(ablInterpolator{}) + + // BiLinear is the tent kernel. It is slow, but usually gives high quality + // results. + BiLinear = &Kernel{1, func(t float64) float64 { + return 1 - t + }} + + // CatmullRom is the Catmull-Rom kernel. It is very slow, but usually gives + // very high quality results. + // + // It is an instance of the more general cubic BC-spline kernel with parameters + // B=0 and C=0.5. See Mitchell and Netravali, "Reconstruction Filters in + // Computer Graphics", Computer Graphics, Vol. 22, No. 4, pp. 221-228. + CatmullRom = &Kernel{2, func(t float64) float64 { + if t < 1 { + return (1.5*t-2.5)*t*t + 1 + } + return ((-0.5*t+2.5)*t-4)*t + 2 + }} + + // TODO: a Kaiser-Bessel kernel? +) + +type nnInterpolator struct{} + +type ablInterpolator struct{} + +type kernelScaler struct { + kernel *Kernel + dw, dh, sw, sh int32 + horizontal, vertical distrib + pool sync.Pool +} + +func (z *kernelScaler) makeTmpBuf() [][4]float64 { + return make([][4]float64, z.dw*z.sh) +} + +// source is a range of contribs, their inverse total weight, and that ITW +// divided by 0xffff. +type source struct { + i, j int32 + invTotalWeight float64 + invTotalWeightFFFF float64 +} + +// contrib is the weight of a column or row. +type contrib struct { + coord int32 + weight float64 +} + +// distrib measures how source pixels are distributed over destination pixels. +type distrib struct { + // sources are what contribs each column or row in the source image owns, + // and the total weight of those contribs. + sources []source + // contribs are the contributions indexed by sources[s].i and sources[s].j. + contribs []contrib +} + +// newDistrib returns a distrib that distributes sw source columns (or rows) +// over dw destination columns (or rows). +func newDistrib(q *Kernel, dw, sw int32) distrib { + scale := float64(sw) / float64(dw) + halfWidth, kernelArgScale := q.Support, 1.0 + // When shrinking, broaden the effective kernel support so that we still + // visit every source pixel. + if scale > 1 { + halfWidth *= scale + kernelArgScale = 1 / scale + } + + // Make the sources slice, one source for each column or row, and temporarily + // appropriate its elements' fields so that invTotalWeight is the scaled + // coordinate of the source column or row, and i and j are the lower and + // upper bounds of the range of destination columns or rows affected by the + // source column or row. + n, sources := int32(0), make([]source, dw) + for x := range sources { + center := (float64(x)+0.5)*scale - 0.5 + i := int32(math.Floor(center - halfWidth)) + if i < 0 { + i = 0 + } + j := int32(math.Ceil(center + halfWidth)) + if j > sw { + j = sw + if j < i { + j = i + } + } + sources[x] = source{i: i, j: j, invTotalWeight: center} + n += j - i + } + + contribs := make([]contrib, 0, n) + for k, b := range sources { + totalWeight := 0.0 + l := int32(len(contribs)) + for coord := b.i; coord < b.j; coord++ { + t := abs((b.invTotalWeight - float64(coord)) * kernelArgScale) + if t >= q.Support { + continue + } + weight := q.At(t) + if weight == 0 { + continue + } + totalWeight += weight + contribs = append(contribs, contrib{coord, weight}) + } + totalWeight = 1 / totalWeight + sources[k] = source{ + i: l, + j: int32(len(contribs)), + invTotalWeight: totalWeight, + invTotalWeightFFFF: totalWeight / 0xffff, + } + } + + return distrib{sources, contribs} +} + +// abs is like math.Abs, but it doesn't care about negative zero, infinities or +// NaNs. +func abs(f float64) float64 { + if f < 0 { + f = -f + } + return f +} + +// ftou converts the range [0.0, 1.0] to [0, 0xffff]. +func ftou(f float64) uint16 { + i := int32(0xffff*f + 0.5) + if i > 0xffff { + return 0xffff + } + if i > 0 { + return uint16(i) + } + return 0 +} + +// fffftou converts the range [0.0, 65535.0] to [0, 0xffff]. +func fffftou(f float64) uint16 { + i := int32(f + 0.5) + if i > 0xffff { + return 0xffff + } + if i > 0 { + return uint16(i) + } + return 0 +} + +// invert returns the inverse of m. +// +// TODO: move this into the f64 package, once we work out the convention for +// matrix methods in that package: do they modify the receiver, take a dst +// pointer argument, or return a new value? +func invert(m *f64.Aff3) f64.Aff3 { + m00 := +m[3*1+1] + m01 := -m[3*0+1] + m02 := +m[3*1+2]*m[3*0+1] - m[3*1+1]*m[3*0+2] + m10 := -m[3*1+0] + m11 := +m[3*0+0] + m12 := +m[3*1+0]*m[3*0+2] - m[3*1+2]*m[3*0+0] + + det := m00*m11 - m10*m01 + + return f64.Aff3{ + m00 / det, + m01 / det, + m02 / det, + m10 / det, + m11 / det, + m12 / det, + } +} + +func matMul(p, q *f64.Aff3) f64.Aff3 { + return f64.Aff3{ + p[3*0+0]*q[3*0+0] + p[3*0+1]*q[3*1+0], + p[3*0+0]*q[3*0+1] + p[3*0+1]*q[3*1+1], + p[3*0+0]*q[3*0+2] + p[3*0+1]*q[3*1+2] + p[3*0+2], + p[3*1+0]*q[3*0+0] + p[3*1+1]*q[3*1+0], + p[3*1+0]*q[3*0+1] + p[3*1+1]*q[3*1+1], + p[3*1+0]*q[3*0+2] + p[3*1+1]*q[3*1+2] + p[3*1+2], + } +} + +// transformRect returns a rectangle dr that contains sr transformed by s2d. +func transformRect(s2d *f64.Aff3, sr *image.Rectangle) (dr image.Rectangle) { + ps := [...]image.Point{ + {sr.Min.X, sr.Min.Y}, + {sr.Max.X, sr.Min.Y}, + {sr.Min.X, sr.Max.Y}, + {sr.Max.X, sr.Max.Y}, + } + for i, p := range ps { + sxf := float64(p.X) + syf := float64(p.Y) + dx := int(math.Floor(s2d[0]*sxf + s2d[1]*syf + s2d[2])) + dy := int(math.Floor(s2d[3]*sxf + s2d[4]*syf + s2d[5])) + + // The +1 adjustments below are because an image.Rectangle is inclusive + // on the low end but exclusive on the high end. + + if i == 0 { + dr = image.Rectangle{ + Min: image.Point{dx + 0, dy + 0}, + Max: image.Point{dx + 1, dy + 1}, + } + continue + } + + if dr.Min.X > dx { + dr.Min.X = dx + } + dx++ + if dr.Max.X < dx { + dr.Max.X = dx + } + + if dr.Min.Y > dy { + dr.Min.Y = dy + } + dy++ + if dr.Max.Y < dy { + dr.Max.Y = dy + } + } + return dr +} + +func clipAffectedDestRect(adr image.Rectangle, dstMask image.Image, dstMaskP image.Point) (image.Rectangle, image.Image) { + if dstMask == nil { + return adr, nil + } + // TODO: enable this fast path once Go 1.5 is released, where an + // image.Rectangle implements image.Image. + // if r, ok := dstMask.(image.Rectangle); ok { + // return adr.Intersect(r.Sub(dstMaskP)), nil + // } + // TODO: clip to dstMask.Bounds() if the color model implies that out-of-bounds means 0 alpha? + return adr, dstMask +} + +func transform_Uniform(dst Image, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.Uniform, sr image.Rectangle, bias image.Point, op Op) { + switch op { + case Over: + switch dst := dst.(type) { + case *image.RGBA: + pr, pg, pb, pa := src.C.RGBA() + pa1 := (0xffff - pa) * 0x101 + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := dst.PixOffset(dr.Min.X+adr.Min.X, dr.Min.Y+int(dy)) + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X + sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y + if !(image.Point{sx0, sy0}).In(sr) { + continue + } + dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*pa1/0xffff + pr) >> 8) + dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*pa1/0xffff + pg) >> 8) + dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*pa1/0xffff + pb) >> 8) + dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*pa1/0xffff + pa) >> 8) + } + } + + default: + pr, pg, pb, pa := src.C.RGBA() + pa1 := 0xffff - pa + dstColorRGBA64 := &color.RGBA64{} + dstColor := color.Color(dstColorRGBA64) + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X + sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y + if !(image.Point{sx0, sy0}).In(sr) { + continue + } + qr, qg, qb, qa := dst.At(dr.Min.X+int(dx), dr.Min.Y+int(dy)).RGBA() + dstColorRGBA64.R = uint16(qr*pa1/0xffff + pr) + dstColorRGBA64.G = uint16(qg*pa1/0xffff + pg) + dstColorRGBA64.B = uint16(qb*pa1/0xffff + pb) + dstColorRGBA64.A = uint16(qa*pa1/0xffff + pa) + dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor) + } + } + } + + case Src: + switch dst := dst.(type) { + case *image.RGBA: + pr, pg, pb, pa := src.C.RGBA() + pr8 := uint8(pr >> 8) + pg8 := uint8(pg >> 8) + pb8 := uint8(pb >> 8) + pa8 := uint8(pa >> 8) + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + d := dst.PixOffset(dr.Min.X+adr.Min.X, dr.Min.Y+int(dy)) + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X + sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y + if !(image.Point{sx0, sy0}).In(sr) { + continue + } + dst.Pix[d+0] = pr8 + dst.Pix[d+1] = pg8 + dst.Pix[d+2] = pb8 + dst.Pix[d+3] = pa8 + } + } + + default: + pr, pg, pb, pa := src.C.RGBA() + dstColorRGBA64 := &color.RGBA64{ + uint16(pr), + uint16(pg), + uint16(pb), + uint16(pa), + } + dstColor := color.Color(dstColorRGBA64) + + for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { + dyf := float64(dr.Min.Y+int(dy)) + 0.5 + for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { + dxf := float64(dr.Min.X+int(dx)) + 0.5 + sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X + sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y + if !(image.Point{sx0, sy0}).In(sr) { + continue + } + dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor) + } + } + } + } +} + +func opaque(m image.Image) bool { + o, ok := m.(interface { + Opaque() bool + }) + return ok && o.Opaque() +} diff --git a/src/golang.org/x/image/draw/scale_test.go b/src/golang.org/x/image/draw/scale_test.go new file mode 100644 index 0000000000..5e184c24e4 --- /dev/null +++ b/src/golang.org/x/image/draw/scale_test.go @@ -0,0 +1,731 @@ +// Copyright 2015 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. + +package draw + +import ( + "bytes" + "flag" + "fmt" + "image" + "image/color" + "image/png" + "math/rand" + "os" + "reflect" + "testing" + + "golang.org/x/image/math/f64" + + _ "image/jpeg" +) + +var genGoldenFiles = flag.Bool("gen_golden_files", false, "whether to generate the TestXxx golden files.") + +var transformMatrix = func(scale, tx, ty float64) f64.Aff3 { + const cos30, sin30 = 0.866025404, 0.5 + return f64.Aff3{ + +scale * cos30, -scale * sin30, tx, + +scale * sin30, +scale * cos30, ty, + } +} + +func encode(filename string, m image.Image) error { + f, err := os.Create(filename) + if err != nil { + return fmt.Errorf("Create: %v", err) + } + defer f.Close() + if err := png.Encode(f, m); err != nil { + return fmt.Errorf("Encode: %v", err) + } + return nil +} + +// testInterp tests that interpolating the source image gives the exact +// destination image. This is to ensure that any refactoring or optimization of +// the interpolation code doesn't change the behavior. Changing the actual +// algorithm or kernel used by any particular quality setting will obviously +// change the resultant pixels. In such a case, use the gen_golden_files flag +// to regenerate the golden files. +func testInterp(t *testing.T, w int, h int, direction, prefix, suffix string) { + f, err := os.Open("../testdata/" + prefix + suffix) + if err != nil { + t.Fatalf("Open: %v", err) + } + defer f.Close() + src, _, err := image.Decode(f) + if err != nil { + t.Fatalf("Decode: %v", err) + } + + op, scale := Src, 3.75 + if prefix == "tux" { + op, scale = Over, 0.125 + } + green := image.NewUniform(color.RGBA{0x00, 0x22, 0x11, 0xff}) + + testCases := map[string]Interpolator{ + "nn": NearestNeighbor, + "ab": ApproxBiLinear, + "bl": BiLinear, + "cr": CatmullRom, + } + for name, q := range testCases { + goldenFilename := fmt.Sprintf("../testdata/%s-%s-%s.png", prefix, direction, name) + + got := image.NewRGBA(image.Rect(0, 0, w, h)) + Copy(got, image.Point{}, green, got.Bounds(), Src, nil) + if direction == "rotate" { + q.Transform(got, transformMatrix(scale, 40, 10), src, src.Bounds(), op, nil) + } else { + q.Scale(got, got.Bounds(), src, src.Bounds(), op, nil) + } + + if *genGoldenFiles { + if err := encode(goldenFilename, got); err != nil { + t.Error(err) + } + continue + } + + g, err := os.Open(goldenFilename) + if err != nil { + t.Errorf("Open: %v", err) + continue + } + defer g.Close() + wantRaw, err := png.Decode(g) + if err != nil { + t.Errorf("Decode: %v", err) + continue + } + // convert wantRaw to RGBA. + want, ok := wantRaw.(*image.RGBA) + if !ok { + b := wantRaw.Bounds() + want = image.NewRGBA(b) + Draw(want, b, wantRaw, b.Min, Src) + } + + if !reflect.DeepEqual(got, want) { + t.Errorf("%s: actual image differs from golden image", goldenFilename) + continue + } + } +} + +func TestScaleDown(t *testing.T) { testInterp(t, 100, 100, "down", "go-turns-two", "-280x360.jpeg") } +func TestScaleUp(t *testing.T) { testInterp(t, 75, 100, "up", "go-turns-two", "-14x18.png") } +func TestTformSrc(t *testing.T) { testInterp(t, 100, 100, "rotate", "go-turns-two", "-14x18.png") } +func TestTformOver(t *testing.T) { testInterp(t, 100, 100, "rotate", "tux", ".png") } + +// TestSimpleTransforms tests Scale and Transform calls that simplify to Copy +// or Scale calls. +func TestSimpleTransforms(t *testing.T) { + f, err := os.Open("../testdata/testpattern.png") // A 100x100 image. + if err != nil { + t.Fatalf("Open: %v", err) + } + defer f.Close() + src, _, err := image.Decode(f) + if err != nil { + t.Fatalf("Decode: %v", err) + } + + dst0 := image.NewRGBA(image.Rect(0, 0, 120, 150)) + dst1 := image.NewRGBA(image.Rect(0, 0, 120, 150)) + for _, op := range []string{"scale/copy", "tform/copy", "tform/scale"} { + for _, epsilon := range []float64{0, 1e-50, 1e-1} { + Copy(dst0, image.Point{}, image.Transparent, dst0.Bounds(), Src, nil) + Copy(dst1, image.Point{}, image.Transparent, dst1.Bounds(), Src, nil) + + switch op { + case "scale/copy": + dr := image.Rect(10, 30, 10+100, 30+100) + if epsilon > 1e-10 { + dr.Max.X++ + } + Copy(dst0, image.Point{10, 30}, src, src.Bounds(), Src, nil) + ApproxBiLinear.Scale(dst1, dr, src, src.Bounds(), Src, nil) + case "tform/copy": + Copy(dst0, image.Point{10, 30}, src, src.Bounds(), Src, nil) + ApproxBiLinear.Transform(dst1, f64.Aff3{ + 1, 0 + epsilon, 10, + 0, 1, 30, + }, src, src.Bounds(), Src, nil) + case "tform/scale": + ApproxBiLinear.Scale(dst0, image.Rect(10, 50, 10+50, 50+50), src, src.Bounds(), Src, nil) + ApproxBiLinear.Transform(dst1, f64.Aff3{ + 0.5, 0.0 + epsilon, 10, + 0.0, 0.5, 50, + }, src, src.Bounds(), Src, nil) + } + + differ := !bytes.Equal(dst0.Pix, dst1.Pix) + if epsilon > 1e-10 { + if !differ { + t.Errorf("%s yielded same pixels, want different pixels: epsilon=%v", op, epsilon) + } + } else { + if differ { + t.Errorf("%s yielded different pixels, want same pixels: epsilon=%v", op, epsilon) + } + } + } + } +} + +func BenchmarkSimpleScaleCopy(b *testing.B) { + dst := image.NewRGBA(image.Rect(0, 0, 640, 480)) + src := image.NewRGBA(image.Rect(0, 0, 400, 300)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + ApproxBiLinear.Scale(dst, image.Rect(10, 20, 10+400, 20+300), src, src.Bounds(), Src, nil) + } +} + +func BenchmarkSimpleTransformCopy(b *testing.B) { + dst := image.NewRGBA(image.Rect(0, 0, 640, 480)) + src := image.NewRGBA(image.Rect(0, 0, 400, 300)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + ApproxBiLinear.Transform(dst, f64.Aff3{ + 1, 0, 10, + 0, 1, 20, + }, src, src.Bounds(), Src, nil) + } +} + +func BenchmarkSimpleTransformScale(b *testing.B) { + dst := image.NewRGBA(image.Rect(0, 0, 640, 480)) + src := image.NewRGBA(image.Rect(0, 0, 400, 300)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + ApproxBiLinear.Transform(dst, f64.Aff3{ + 0.5, 0.0, 10, + 0.0, 0.5, 20, + }, src, src.Bounds(), Src, nil) + } +} + +func TestOps(t *testing.T) { + blue := image.NewUniform(color.RGBA{0x00, 0x00, 0xff, 0xff}) + testCases := map[Op]color.RGBA{ + Over: color.RGBA{0x7f, 0x00, 0x80, 0xff}, + Src: color.RGBA{0x7f, 0x00, 0x00, 0x7f}, + } + for op, want := range testCases { + dst := image.NewRGBA(image.Rect(0, 0, 2, 2)) + Copy(dst, image.Point{}, blue, dst.Bounds(), Src, nil) + + src := image.NewRGBA(image.Rect(0, 0, 1, 1)) + src.SetRGBA(0, 0, color.RGBA{0x7f, 0x00, 0x00, 0x7f}) + + NearestNeighbor.Scale(dst, dst.Bounds(), src, src.Bounds(), op, nil) + + if got := dst.RGBAAt(0, 0); got != want { + t.Errorf("op=%v: got %v, want %v", op, got, want) + } + } +} + +// TestNegativeWeights tests that scaling by a kernel that produces negative +// weights, such as the Catmull-Rom kernel, doesn't produce an invalid color +// according to Go's alpha-premultiplied model. +func TestNegativeWeights(t *testing.T) { + check := func(m *image.RGBA) error { + b := m.Bounds() + for y := b.Min.Y; y < b.Max.Y; y++ { + for x := b.Min.X; x < b.Max.X; x++ { + if c := m.RGBAAt(x, y); c.R > c.A || c.G > c.A || c.B > c.A { + return fmt.Errorf("invalid color.RGBA at (%d, %d): %v", x, y, c) + } + } + } + return nil + } + + src := image.NewRGBA(image.Rect(0, 0, 16, 16)) + for y := 0; y < 16; y++ { + for x := 0; x < 16; x++ { + a := y * 0x11 + src.Set(x, y, color.RGBA{ + R: uint8(x * 0x11 * a / 0xff), + A: uint8(a), + }) + } + } + if err := check(src); err != nil { + t.Fatalf("src image: %v", err) + } + + dst := image.NewRGBA(image.Rect(0, 0, 32, 32)) + CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), Over, nil) + if err := check(dst); err != nil { + t.Fatalf("dst image: %v", err) + } +} + +func fillPix(r *rand.Rand, pixs ...[]byte) { + for _, pix := range pixs { + for i := range pix { + pix[i] = uint8(r.Intn(256)) + } + } +} + +func TestInterpClipCommute(t *testing.T) { + src := image.NewNRGBA(image.Rect(0, 0, 20, 20)) + fillPix(rand.New(rand.NewSource(0)), src.Pix) + + outer := image.Rect(1, 1, 8, 5) + inner := image.Rect(2, 3, 6, 5) + qs := []Interpolator{ + NearestNeighbor, + ApproxBiLinear, + CatmullRom, + } + for _, transform := range []bool{false, true} { + for _, q := range qs { + dst0 := image.NewRGBA(image.Rect(1, 1, 10, 10)) + dst1 := image.NewRGBA(image.Rect(1, 1, 10, 10)) + for i := range dst0.Pix { + dst0.Pix[i] = uint8(i / 4) + dst1.Pix[i] = uint8(i / 4) + } + + var interp func(dst *image.RGBA) + if transform { + interp = func(dst *image.RGBA) { + q.Transform(dst, transformMatrix(3.75, 2, 1), src, src.Bounds(), Over, nil) + } + } else { + interp = func(dst *image.RGBA) { + q.Scale(dst, outer, src, src.Bounds(), Over, nil) + } + } + + // Interpolate then clip. + interp(dst0) + dst0 = dst0.SubImage(inner).(*image.RGBA) + + // Clip then interpolate. + dst1 = dst1.SubImage(inner).(*image.RGBA) + interp(dst1) + + loop: + for y := inner.Min.Y; y < inner.Max.Y; y++ { + for x := inner.Min.X; x < inner.Max.X; x++ { + if c0, c1 := dst0.RGBAAt(x, y), dst1.RGBAAt(x, y); c0 != c1 { + t.Errorf("q=%T: at (%d, %d): c0=%v, c1=%v", q, x, y, c0, c1) + break loop + } + } + } + } + } +} + +// translatedImage is an image m translated by t. +type translatedImage struct { + m image.Image + t image.Point +} + +func (t *translatedImage) At(x, y int) color.Color { return t.m.At(x-t.t.X, y-t.t.Y) } +func (t *translatedImage) Bounds() image.Rectangle { return t.m.Bounds().Add(t.t) } +func (t *translatedImage) ColorModel() color.Model { return t.m.ColorModel() } + +// TestSrcTranslationInvariance tests that Scale and Transform are invariant +// under src translations. Specifically, when some source pixels are not in the +// bottom-right quadrant of src coordinate space, we consistently round down, +// not round towards zero. +func TestSrcTranslationInvariance(t *testing.T) { + f, err := os.Open("../testdata/testpattern.png") + if err != nil { + t.Fatalf("Open: %v", err) + } + defer f.Close() + src, _, err := image.Decode(f) + if err != nil { + t.Fatalf("Decode: %v", err) + } + sr := image.Rect(2, 3, 16, 12) + if !sr.In(src.Bounds()) { + t.Fatalf("src bounds too small: got %v", src.Bounds()) + } + qs := []Interpolator{ + NearestNeighbor, + ApproxBiLinear, + CatmullRom, + } + deltas := []image.Point{ + {+0, +0}, + {+0, +5}, + {+0, -5}, + {+5, +0}, + {-5, +0}, + {+8, +8}, + {+8, -8}, + {-8, +8}, + {-8, -8}, + } + m00 := transformMatrix(3.75, 0, 0) + + for _, transform := range []bool{false, true} { + for _, q := range qs { + want := image.NewRGBA(image.Rect(0, 0, 20, 20)) + if transform { + q.Transform(want, m00, src, sr, Over, nil) + } else { + q.Scale(want, want.Bounds(), src, sr, Over, nil) + } + for _, delta := range deltas { + tsrc := &translatedImage{src, delta} + got := image.NewRGBA(image.Rect(0, 0, 20, 20)) + if transform { + m := matMul(&m00, &f64.Aff3{ + 1, 0, -float64(delta.X), + 0, 1, -float64(delta.Y), + }) + q.Transform(got, m, tsrc, sr.Add(delta), Over, nil) + } else { + q.Scale(got, got.Bounds(), tsrc, sr.Add(delta), Over, nil) + } + if !bytes.Equal(got.Pix, want.Pix) { + t.Errorf("pix differ for delta=%v, transform=%t, q=%T", delta, transform, q) + } + } + } + } +} + +func TestSrcMask(t *testing.T) { + srcMask := image.NewRGBA(image.Rect(0, 0, 23, 1)) + srcMask.SetRGBA(19, 0, color.RGBA{0x00, 0x00, 0x00, 0x7f}) + srcMask.SetRGBA(20, 0, color.RGBA{0x00, 0x00, 0x00, 0xff}) + srcMask.SetRGBA(21, 0, color.RGBA{0x00, 0x00, 0x00, 0x3f}) + srcMask.SetRGBA(22, 0, color.RGBA{0x00, 0x00, 0x00, 0x00}) + red := image.NewUniform(color.RGBA{0xff, 0x00, 0x00, 0xff}) + blue := image.NewUniform(color.RGBA{0x00, 0x00, 0xff, 0xff}) + dst := image.NewRGBA(image.Rect(0, 0, 6, 1)) + Copy(dst, image.Point{}, blue, dst.Bounds(), Src, nil) + NearestNeighbor.Scale(dst, dst.Bounds(), red, image.Rect(0, 0, 3, 1), Over, &Options{ + SrcMask: srcMask, + SrcMaskP: image.Point{20, 0}, + }) + got := [6]color.RGBA{ + dst.RGBAAt(0, 0), + dst.RGBAAt(1, 0), + dst.RGBAAt(2, 0), + dst.RGBAAt(3, 0), + dst.RGBAAt(4, 0), + dst.RGBAAt(5, 0), + } + want := [6]color.RGBA{ + {0xff, 0x00, 0x00, 0xff}, + {0xff, 0x00, 0x00, 0xff}, + {0x3f, 0x00, 0xc0, 0xff}, + {0x3f, 0x00, 0xc0, 0xff}, + {0x00, 0x00, 0xff, 0xff}, + {0x00, 0x00, 0xff, 0xff}, + } + if got != want { + t.Errorf("\ngot %v\nwant %v", got, want) + } +} + +func TestDstMask(t *testing.T) { + dstMask := image.NewRGBA(image.Rect(0, 0, 23, 1)) + dstMask.SetRGBA(19, 0, color.RGBA{0x00, 0x00, 0x00, 0x7f}) + dstMask.SetRGBA(20, 0, color.RGBA{0x00, 0x00, 0x00, 0xff}) + dstMask.SetRGBA(21, 0, color.RGBA{0x00, 0x00, 0x00, 0x3f}) + dstMask.SetRGBA(22, 0, color.RGBA{0x00, 0x00, 0x00, 0x00}) + red := image.NewRGBA(image.Rect(0, 0, 1, 1)) + red.SetRGBA(0, 0, color.RGBA{0xff, 0x00, 0x00, 0xff}) + blue := image.NewUniform(color.RGBA{0x00, 0x00, 0xff, 0xff}) + qs := []Interpolator{ + NearestNeighbor, + ApproxBiLinear, + CatmullRom, + } + for _, q := range qs { + dst := image.NewRGBA(image.Rect(0, 0, 3, 1)) + Copy(dst, image.Point{}, blue, dst.Bounds(), Src, nil) + q.Scale(dst, dst.Bounds(), red, red.Bounds(), Over, &Options{ + DstMask: dstMask, + DstMaskP: image.Point{20, 0}, + }) + got := [3]color.RGBA{ + dst.RGBAAt(0, 0), + dst.RGBAAt(1, 0), + dst.RGBAAt(2, 0), + } + want := [3]color.RGBA{ + {0xff, 0x00, 0x00, 0xff}, + {0x3f, 0x00, 0xc0, 0xff}, + {0x00, 0x00, 0xff, 0xff}, + } + if got != want { + t.Errorf("q=%T:\ngot %v\nwant %v", q, got, want) + } + } +} + +func TestRectDstMask(t *testing.T) { + f, err := os.Open("../testdata/testpattern.png") + if err != nil { + t.Fatalf("Open: %v", err) + } + defer f.Close() + src, _, err := image.Decode(f) + if err != nil { + t.Fatalf("Decode: %v", err) + } + m00 := transformMatrix(1, 0, 0) + + bounds := image.Rect(0, 0, 50, 50) + dstOutside := image.NewRGBA(bounds) + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + for x := bounds.Min.X; x < bounds.Max.X; x++ { + dstOutside.SetRGBA(x, y, color.RGBA{uint8(5 * x), uint8(5 * y), 0x00, 0xff}) + } + } + + mk := func(q Transformer, dstMask image.Image, dstMaskP image.Point) *image.RGBA { + m := image.NewRGBA(bounds) + Copy(m, bounds.Min, dstOutside, bounds, Src, nil) + q.Transform(m, m00, src, src.Bounds(), Over, &Options{ + DstMask: dstMask, + DstMaskP: dstMaskP, + }) + return m + } + + qs := []Interpolator{ + NearestNeighbor, + ApproxBiLinear, + CatmullRom, + } + dstMaskPs := []image.Point{ + {0, 0}, + {5, 7}, + {-3, 0}, + } + rect := image.Rect(10, 10, 30, 40) + for _, q := range qs { + for _, dstMaskP := range dstMaskPs { + dstInside := mk(q, nil, image.Point{}) + for _, wrap := range []bool{false, true} { + // TODO: replace "rectImage(rect)" with "rect" once Go 1.5 is + // released, where an image.Rectangle implements image.Image. + dstMask := image.Image(rectImage(rect)) + if wrap { + dstMask = srcWrapper{dstMask} + } + dst := mk(q, dstMask, dstMaskP) + + nError := 0 + loop: + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + for x := bounds.Min.X; x < bounds.Max.X; x++ { + which := dstOutside + if (image.Point{x, y}).Add(dstMaskP).In(rect) { + which = dstInside + } + if got, want := dst.RGBAAt(x, y), which.RGBAAt(x, y); got != want { + if nError == 10 { + t.Errorf("q=%T dmp=%v wrap=%v: ...and more errors", q, dstMaskP, wrap) + break loop + } + nError++ + t.Errorf("q=%T dmp=%v wrap=%v: x=%3d y=%3d: got %v, want %v", + q, dstMaskP, wrap, x, y, got, want) + } + } + } + } + } + } +} + +// TODO: delete this wrapper type once Go 1.5 is released, where an +// image.Rectangle implements image.Image. +type rectImage image.Rectangle + +func (r rectImage) ColorModel() color.Model { return color.Alpha16Model } +func (r rectImage) Bounds() image.Rectangle { return image.Rectangle(r) } +func (r rectImage) At(x, y int) color.Color { + if (image.Point{x, y}).In(image.Rectangle(r)) { + return color.Opaque + } + return color.Transparent +} + +// The fooWrapper types wrap the dst or src image to avoid triggering the +// type-specific fast path implementations. +type ( + dstWrapper struct{ Image } + srcWrapper struct{ image.Image } +) + +func srcGray(boundsHint image.Rectangle) (image.Image, error) { + m := image.NewGray(boundsHint) + fillPix(rand.New(rand.NewSource(0)), m.Pix) + return m, nil +} + +func srcNRGBA(boundsHint image.Rectangle) (image.Image, error) { + m := image.NewNRGBA(boundsHint) + fillPix(rand.New(rand.NewSource(1)), m.Pix) + return m, nil +} + +func srcRGBA(boundsHint image.Rectangle) (image.Image, error) { + m := image.NewRGBA(boundsHint) + fillPix(rand.New(rand.NewSource(2)), m.Pix) + // RGBA is alpha-premultiplied, so the R, G and B values should + // be <= the A values. + for i := 0; i < len(m.Pix); i += 4 { + m.Pix[i+0] = uint8(uint32(m.Pix[i+0]) * uint32(m.Pix[i+3]) / 0xff) + m.Pix[i+1] = uint8(uint32(m.Pix[i+1]) * uint32(m.Pix[i+3]) / 0xff) + m.Pix[i+2] = uint8(uint32(m.Pix[i+2]) * uint32(m.Pix[i+3]) / 0xff) + } + return m, nil +} + +func srcUnif(boundsHint image.Rectangle) (image.Image, error) { + return image.NewUniform(color.RGBA64{0x1234, 0x5555, 0x9181, 0xbeef}), nil +} + +func srcYCbCr(boundsHint image.Rectangle) (image.Image, error) { + m := image.NewYCbCr(boundsHint, image.YCbCrSubsampleRatio420) + fillPix(rand.New(rand.NewSource(3)), m.Y, m.Cb, m.Cr) + return m, nil +} + +func srcLarge(boundsHint image.Rectangle) (image.Image, error) { + // 3072 x 2304 is over 7 million pixels at 4:3, comparable to a + // 2015 smart-phone camera's output. + return srcYCbCr(image.Rect(0, 0, 3072, 2304)) +} + +func srcTux(boundsHint image.Rectangle) (image.Image, error) { + // tux.png is a 386 x 395 image. + f, err := os.Open("../testdata/tux.png") + if err != nil { + return nil, fmt.Errorf("Open: %v", err) + } + defer f.Close() + src, err := png.Decode(f) + if err != nil { + return nil, fmt.Errorf("Decode: %v", err) + } + return src, nil +} + +func benchScale(b *testing.B, w int, h int, op Op, srcf func(image.Rectangle) (image.Image, error), q Interpolator) { + dst := image.NewRGBA(image.Rect(0, 0, w, h)) + src, err := srcf(image.Rect(0, 0, 1024, 768)) + if err != nil { + b.Fatal(err) + } + dr, sr := dst.Bounds(), src.Bounds() + scaler := Scaler(q) + if n, ok := q.(interface { + NewScaler(int, int, int, int) Scaler + }); ok { + scaler = n.NewScaler(dr.Dx(), dr.Dy(), sr.Dx(), sr.Dy()) + } + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + scaler.Scale(dst, dr, src, sr, op, nil) + } +} + +func benchTform(b *testing.B, w int, h int, op Op, srcf func(image.Rectangle) (image.Image, error), q Interpolator) { + dst := image.NewRGBA(image.Rect(0, 0, w, h)) + src, err := srcf(image.Rect(0, 0, 1024, 768)) + if err != nil { + b.Fatal(err) + } + sr := src.Bounds() + m := transformMatrix(3.75, 40, 10) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + q.Transform(dst, m, src, sr, op, nil) + } +} + +func BenchmarkScaleNNLargeDown(b *testing.B) { benchScale(b, 200, 150, Src, srcLarge, NearestNeighbor) } +func BenchmarkScaleABLargeDown(b *testing.B) { benchScale(b, 200, 150, Src, srcLarge, ApproxBiLinear) } +func BenchmarkScaleBLLargeDown(b *testing.B) { benchScale(b, 200, 150, Src, srcLarge, BiLinear) } +func BenchmarkScaleCRLargeDown(b *testing.B) { benchScale(b, 200, 150, Src, srcLarge, CatmullRom) } + +func BenchmarkScaleNNDown(b *testing.B) { benchScale(b, 120, 80, Src, srcTux, NearestNeighbor) } +func BenchmarkScaleABDown(b *testing.B) { benchScale(b, 120, 80, Src, srcTux, ApproxBiLinear) } +func BenchmarkScaleBLDown(b *testing.B) { benchScale(b, 120, 80, Src, srcTux, BiLinear) } +func BenchmarkScaleCRDown(b *testing.B) { benchScale(b, 120, 80, Src, srcTux, CatmullRom) } + +func BenchmarkScaleNNUp(b *testing.B) { benchScale(b, 800, 600, Src, srcTux, NearestNeighbor) } +func BenchmarkScaleABUp(b *testing.B) { benchScale(b, 800, 600, Src, srcTux, ApproxBiLinear) } +func BenchmarkScaleBLUp(b *testing.B) { benchScale(b, 800, 600, Src, srcTux, BiLinear) } +func BenchmarkScaleCRUp(b *testing.B) { benchScale(b, 800, 600, Src, srcTux, CatmullRom) } + +func BenchmarkScaleNNSrcRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcRGBA, NearestNeighbor) } +func BenchmarkScaleNNSrcUnif(b *testing.B) { benchScale(b, 200, 150, Src, srcUnif, NearestNeighbor) } + +func BenchmarkScaleNNOverRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcRGBA, NearestNeighbor) } +func BenchmarkScaleNNOverUnif(b *testing.B) { benchScale(b, 200, 150, Over, srcUnif, NearestNeighbor) } + +func BenchmarkTformNNSrcRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcRGBA, NearestNeighbor) } +func BenchmarkTformNNSrcUnif(b *testing.B) { benchTform(b, 200, 150, Src, srcUnif, NearestNeighbor) } + +func BenchmarkTformNNOverRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcRGBA, NearestNeighbor) } +func BenchmarkTformNNOverUnif(b *testing.B) { benchTform(b, 200, 150, Over, srcUnif, NearestNeighbor) } + +func BenchmarkScaleABSrcGray(b *testing.B) { benchScale(b, 200, 150, Src, srcGray, ApproxBiLinear) } +func BenchmarkScaleABSrcNRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcNRGBA, ApproxBiLinear) } +func BenchmarkScaleABSrcRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcRGBA, ApproxBiLinear) } +func BenchmarkScaleABSrcYCbCr(b *testing.B) { benchScale(b, 200, 150, Src, srcYCbCr, ApproxBiLinear) } + +func BenchmarkScaleABOverGray(b *testing.B) { benchScale(b, 200, 150, Over, srcGray, ApproxBiLinear) } +func BenchmarkScaleABOverNRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcNRGBA, ApproxBiLinear) } +func BenchmarkScaleABOverRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcRGBA, ApproxBiLinear) } +func BenchmarkScaleABOverYCbCr(b *testing.B) { benchScale(b, 200, 150, Over, srcYCbCr, ApproxBiLinear) } + +func BenchmarkTformABSrcGray(b *testing.B) { benchTform(b, 200, 150, Src, srcGray, ApproxBiLinear) } +func BenchmarkTformABSrcNRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcNRGBA, ApproxBiLinear) } +func BenchmarkTformABSrcRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcRGBA, ApproxBiLinear) } +func BenchmarkTformABSrcYCbCr(b *testing.B) { benchTform(b, 200, 150, Src, srcYCbCr, ApproxBiLinear) } + +func BenchmarkTformABOverGray(b *testing.B) { benchTform(b, 200, 150, Over, srcGray, ApproxBiLinear) } +func BenchmarkTformABOverNRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcNRGBA, ApproxBiLinear) } +func BenchmarkTformABOverRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcRGBA, ApproxBiLinear) } +func BenchmarkTformABOverYCbCr(b *testing.B) { benchTform(b, 200, 150, Over, srcYCbCr, ApproxBiLinear) } + +func BenchmarkScaleCRSrcGray(b *testing.B) { benchScale(b, 200, 150, Src, srcGray, CatmullRom) } +func BenchmarkScaleCRSrcNRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcNRGBA, CatmullRom) } +func BenchmarkScaleCRSrcRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcRGBA, CatmullRom) } +func BenchmarkScaleCRSrcYCbCr(b *testing.B) { benchScale(b, 200, 150, Src, srcYCbCr, CatmullRom) } + +func BenchmarkScaleCROverGray(b *testing.B) { benchScale(b, 200, 150, Over, srcGray, CatmullRom) } +func BenchmarkScaleCROverNRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcNRGBA, CatmullRom) } +func BenchmarkScaleCROverRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcRGBA, CatmullRom) } +func BenchmarkScaleCROverYCbCr(b *testing.B) { benchScale(b, 200, 150, Over, srcYCbCr, CatmullRom) } + +func BenchmarkTformCRSrcGray(b *testing.B) { benchTform(b, 200, 150, Src, srcGray, CatmullRom) } +func BenchmarkTformCRSrcNRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcNRGBA, CatmullRom) } +func BenchmarkTformCRSrcRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcRGBA, CatmullRom) } +func BenchmarkTformCRSrcYCbCr(b *testing.B) { benchTform(b, 200, 150, Src, srcYCbCr, CatmullRom) } + +func BenchmarkTformCROverGray(b *testing.B) { benchTform(b, 200, 150, Over, srcGray, CatmullRom) } +func BenchmarkTformCROverNRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcNRGBA, CatmullRom) } +func BenchmarkTformCROverRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcRGBA, CatmullRom) } +func BenchmarkTformCROverYCbCr(b *testing.B) { benchTform(b, 200, 150, Over, srcYCbCr, CatmullRom) } diff --git a/src/golang.org/x/image/draw/stdlib_test.go b/src/golang.org/x/image/draw/stdlib_test.go new file mode 100644 index 0000000000..c45f78c2e4 --- /dev/null +++ b/src/golang.org/x/image/draw/stdlib_test.go @@ -0,0 +1,96 @@ +// Copyright 2015 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 go1.5 + +package draw + +// This file contains tests that depend on the exact behavior of the +// image/color package in the standard library. The color conversion formula +// from YCbCr to RGBA changed between Go 1.4 and Go 1.5, so this file's tests +// are only enabled for Go 1.5 and above. + +import ( + "bytes" + "image" + "image/color" + "testing" +) + +// TestFastPaths tests that the fast path implementations produce identical +// results to the generic implementation. +func TestFastPaths(t *testing.T) { + drs := []image.Rectangle{ + image.Rect(0, 0, 10, 10), // The dst bounds. + image.Rect(3, 4, 8, 6), // A strict subset of the dst bounds. + image.Rect(-3, -5, 2, 4), // Partial out-of-bounds #0. + image.Rect(4, -2, 6, 12), // Partial out-of-bounds #1. + image.Rect(12, 14, 23, 45), // Complete out-of-bounds. + image.Rect(5, 5, 5, 5), // Empty. + } + srs := []image.Rectangle{ + image.Rect(0, 0, 12, 9), // The src bounds. + image.Rect(2, 2, 10, 8), // A strict subset of the src bounds. + image.Rect(10, 5, 20, 20), // Partial out-of-bounds #0. + image.Rect(-40, 0, 40, 8), // Partial out-of-bounds #1. + image.Rect(-8, -8, -4, -4), // Complete out-of-bounds. + image.Rect(5, 5, 5, 5), // Empty. + } + srcfs := []func(image.Rectangle) (image.Image, error){ + srcGray, + srcNRGBA, + srcRGBA, + srcUnif, + srcYCbCr, + } + var srcs []image.Image + for _, srcf := range srcfs { + src, err := srcf(srs[0]) + if err != nil { + t.Fatal(err) + } + srcs = append(srcs, src) + } + qs := []Interpolator{ + NearestNeighbor, + ApproxBiLinear, + CatmullRom, + } + ops := []Op{ + Over, + Src, + } + blue := image.NewUniform(color.RGBA{0x11, 0x22, 0x44, 0x7f}) + + for _, dr := range drs { + for _, src := range srcs { + for _, sr := range srs { + for _, transform := range []bool{false, true} { + for _, q := range qs { + for _, op := range ops { + dst0 := image.NewRGBA(drs[0]) + dst1 := image.NewRGBA(drs[0]) + Draw(dst0, dst0.Bounds(), blue, image.Point{}, Src) + Draw(dstWrapper{dst1}, dst1.Bounds(), srcWrapper{blue}, image.Point{}, Src) + + if transform { + m := transformMatrix(3.75, 2, 1) + q.Transform(dst0, m, src, sr, op, nil) + q.Transform(dstWrapper{dst1}, m, srcWrapper{src}, sr, op, nil) + } else { + q.Scale(dst0, dr, src, sr, op, nil) + q.Scale(dstWrapper{dst1}, dr, srcWrapper{src}, sr, op, nil) + } + + if !bytes.Equal(dst0.Pix, dst1.Pix) { + t.Errorf("pix differ for dr=%v, src=%T, sr=%v, transform=%t, q=%T", + dr, src, sr, transform, q) + } + } + } + } + } + } + } +} diff --git a/src/golang.org/x/image/math/f32/f32.go b/src/golang.org/x/image/math/f32/f32.go new file mode 100644 index 0000000000..4ca1eb47d2 --- /dev/null +++ b/src/golang.org/x/image/math/f32/f32.go @@ -0,0 +1,37 @@ +// Copyright 2015 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. + +// Package f32 implements float32 vector and matrix types. +package f32 // import "golang.org/x/image/math/f32" + +// Vec2 is a 2-element vector. +type Vec2 [2]float32 + +// Vec3 is a 3-element vector. +type Vec3 [3]float32 + +// Vec4 is a 4-element vector. +type Vec4 [4]float32 + +// Mat3 is a 3x3 matrix in row major order. +// +// m[3*r + c] is the element in the r'th row and c'th column. +type Mat3 [9]float32 + +// Mat4 is a 4x4 matrix in row major order. +// +// m[4*r + c] is the element in the r'th row and c'th column. +type Mat4 [16]float32 + +// Aff3 is a 3x3 affine transformation matrix in row major order, where the +// bottom row is implicitly [0 0 1]. +// +// m[3*r + c] is the element in the r'th row and c'th column. +type Aff3 [6]float32 + +// Aff4 is a 4x4 affine transformation matrix in row major order, where the +// bottom row is implicitly [0 0 0 1]. +// +// m[4*r + c] is the element in the r'th row and c'th column. +type Aff4 [12]float32 diff --git a/src/golang.org/x/image/math/f64/f64.go b/src/golang.org/x/image/math/f64/f64.go new file mode 100644 index 0000000000..a1f7fc0ef7 --- /dev/null +++ b/src/golang.org/x/image/math/f64/f64.go @@ -0,0 +1,37 @@ +// Copyright 2015 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. + +// Package f64 implements float64 vector and matrix types. +package f64 // import "golang.org/x/image/math/f64" + +// Vec2 is a 2-element vector. +type Vec2 [2]float64 + +// Vec3 is a 3-element vector. +type Vec3 [3]float64 + +// Vec4 is a 4-element vector. +type Vec4 [4]float64 + +// Mat3 is a 3x3 matrix in row major order. +// +// m[3*r + c] is the element in the r'th row and c'th column. +type Mat3 [9]float64 + +// Mat4 is a 4x4 matrix in row major order. +// +// m[4*r + c] is the element in the r'th row and c'th column. +type Mat4 [16]float64 + +// Aff3 is a 3x3 affine transformation matrix in row major order, where the +// bottom row is implicitly [0 0 1]. +// +// m[3*r + c] is the element in the r'th row and c'th column. +type Aff3 [6]float64 + +// Aff4 is a 4x4 affine transformation matrix in row major order, where the +// bottom row is implicitly [0 0 0 1]. +// +// m[4*r + c] is the element in the r'th row and c'th column. +type Aff4 [12]float64 diff --git a/src/golang.org/x/image/math/fixed/fixed.go b/src/golang.org/x/image/math/fixed/fixed.go new file mode 100644 index 0000000000..f943419260 --- /dev/null +++ b/src/golang.org/x/image/math/fixed/fixed.go @@ -0,0 +1,69 @@ +// Copyright 2015 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. + +// Package fixed implements fixed-point integer types. +package fixed // import "golang.org/x/image/math/fixed" + +import ( + "fmt" +) + +// TODO: implement fmt.Formatter for %f and %g. + +// Int26_6 is a signed 26.6 fixed-point number. +// +// The integer part ranges from -33554432 to 33554431, inclusive. The +// fractional part has 6 bits of precision. +// +// For example, the number one-and-a-quarter is Int26_6(1<<6 + 1<<4). +type Int26_6 int32 + +// String returns a human-readable representation of a 26.6 fixed-point number. +// +// For example, the number one-and-a-quarter becomes "1:16". +func (x Int26_6) String() string { + const shift, mask = 6, 1<<6 - 1 + if x >= 0 { + return fmt.Sprintf("%d:%02d", int32(x>>shift), int32(x&mask)) + } + x = -x + if x >= 0 { + return fmt.Sprintf("-%d:%02d", int32(x>>shift), int32(x&mask)) + } + return "-33554432:00" // The minimum value is -(1<<25). +} + +// Int52_12 is a signed 52.12 fixed-point number. +// +// The integer part ranges from -2251799813685248 to 2251799813685247, +// inclusive. The fractional part has 12 bits of precision. +// +// For example, the number one-and-a-quarter is Int52_12(1<<12 + 1<<10). +type Int52_12 int64 + +// String returns a human-readable representation of a 52.12 fixed-point +// number. +// +// For example, the number one-and-a-quarter becomes "1:1024". +func (x Int52_12) String() string { + const shift, mask = 12, 1<<12 - 1 + if x >= 0 { + return fmt.Sprintf("%d:%04d", int64(x>>shift), int64(x&mask)) + } + x = -x + if x >= 0 { + return fmt.Sprintf("-%d:%04d", int64(x>>shift), int64(x&mask)) + } + return "-2251799813685248:0000" // The minimum value is -(1<<51). +} + +// Point26_6 is a 26.6 fixed-point coordinate pair. +type Point26_6 struct { + X, Y Int26_6 +} + +// Point52_12 is a 52.12 fixed-point coordinate pair. +type Point52_12 struct { + X, Y Int52_12 +} diff --git a/src/golang.org/x/image/math/fixed/fixed_test.go b/src/golang.org/x/image/math/fixed/fixed_test.go new file mode 100644 index 0000000000..e252de7cb8 --- /dev/null +++ b/src/golang.org/x/image/math/fixed/fixed_test.go @@ -0,0 +1,25 @@ +// Copyright 2015 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. + +package fixed + +import ( + "testing" +) + +func TestInt26_6(t *testing.T) { + got := Int26_6(1<<6 + 1<<4).String() + want := "1:16" + if got != want { + t.Fatalf("got %q, want %q", got, want) + } +} + +func TestInt52_12(t *testing.T) { + got := Int52_12(1<<12 + 1<<10).String() + want := "1:1024" + if got != want { + t.Fatalf("got %q, want %q", got, want) + } +} diff --git a/src/golang.org/x/image/riff/example_test.go b/src/golang.org/x/image/riff/example_test.go new file mode 100644 index 0000000000..93c72b0954 --- /dev/null +++ b/src/golang.org/x/image/riff/example_test.go @@ -0,0 +1,113 @@ +// Copyright 2014 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. + +package riff_test + +import ( + "fmt" + "io" + "io/ioutil" + "log" + "strings" + + "golang.org/x/image/riff" +) + +func ExampleReader() { + formType, r, err := riff.NewReader(strings.NewReader(data)) + if err != nil { + log.Fatal(err) + } + fmt.Printf("RIFF(%s)\n", formType) + if err := dump(r, ".\t"); err != nil { + log.Fatal(err) + } + // Output: + // RIFF(ROOT) + // . ZERO "" + // . ONE "a" + // . LIST(META) + // . . LIST(GOOD) + // . . . ONE "a" + // . . . FIVE "klmno" + // . . ZERO "" + // . . LIST(BAD ) + // . . . THRE "def" + // . TWO "bc" + // . LIST(UGLY) + // . . FOUR "ghij" + // . . SIX "pqrstu" +} + +func dump(r *riff.Reader, indent string) error { + for { + chunkID, chunkLen, chunkData, err := r.Next() + if err == io.EOF { + return nil + } + if err != nil { + return err + } + if chunkID == riff.LIST { + listType, list, err := riff.NewListReader(chunkLen, chunkData) + if err != nil { + return err + } + fmt.Printf("%sLIST(%s)\n", indent, listType) + if err := dump(list, indent+".\t"); err != nil { + return err + } + continue + } + b, err := ioutil.ReadAll(chunkData) + if err != nil { + return err + } + fmt.Printf("%s%s %q\n", indent, chunkID, b) + } +} + +func encodeU32(u uint32) string { + return string([]byte{ + byte(u >> 0), + byte(u >> 8), + byte(u >> 16), + byte(u >> 24), + }) +} + +func encode(chunkID, contents string) string { + n := len(contents) + if n&1 == 1 { + contents += "\x00" + } + return chunkID + encodeU32(uint32(n)) + contents +} + +func encodeMulti(typ0, typ1 string, chunks ...string) string { + n := 4 + for _, c := range chunks { + n += len(c) + } + s := typ0 + encodeU32(uint32(n)) + typ1 + for _, c := range chunks { + s += c + } + return s +} + +var ( + d0 = encode("ZERO", "") + d1 = encode("ONE ", "a") + d2 = encode("TWO ", "bc") + d3 = encode("THRE", "def") + d4 = encode("FOUR", "ghij") + d5 = encode("FIVE", "klmno") + d6 = encode("SIX ", "pqrstu") + l0 = encodeMulti("LIST", "GOOD", d1, d5) + l1 = encodeMulti("LIST", "BAD ", d3) + l2 = encodeMulti("LIST", "UGLY", d4, d6) + l01 = encodeMulti("LIST", "META", l0, d0, l1) + data = encodeMulti("RIFF", "ROOT", d0, d1, l01, d2, l2) +) diff --git a/src/golang.org/x/image/riff/riff.go b/src/golang.org/x/image/riff/riff.go new file mode 100644 index 0000000000..9b9f71d815 --- /dev/null +++ b/src/golang.org/x/image/riff/riff.go @@ -0,0 +1,179 @@ +// Copyright 2014 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. + +// Package riff implements the Resource Interchange File Format, used by media +// formats such as AVI, WAVE and WEBP. +// +// A RIFF stream contains a sequence of chunks. Each chunk consists of an 8-byte +// header (containing a 4-byte chunk type and a 4-byte chunk length), the chunk +// data (presented as an io.Reader), and some padding bytes. +// +// A detailed description of the format is at +// http://www.tactilemedia.com/info/MCI_Control_Info.html +package riff // import "golang.org/x/image/riff" + +import ( + "errors" + "io" + "io/ioutil" + "math" +) + +var ( + errMissingPaddingByte = errors.New("riff: missing padding byte") + errMissingRIFFChunkHeader = errors.New("riff: missing RIFF chunk header") + errShortChunkData = errors.New("riff: short chunk data") + errShortChunkHeader = errors.New("riff: short chunk header") + errStaleReader = errors.New("riff: stale reader") +) + +// u32 decodes the first four bytes of b as a little-endian integer. +func u32(b []byte) uint32 { + return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 +} + +const chunkHeaderSize = 8 + +// FourCC is a four character code. +type FourCC [4]byte + +// LIST is the "LIST" FourCC. +var LIST = FourCC{'L', 'I', 'S', 'T'} + +// NewReader returns the RIFF stream's form type, such as "AVI " or "WAVE", and +// its chunks as a *Reader. +func NewReader(r io.Reader) (formType FourCC, data *Reader, err error) { + var buf [chunkHeaderSize]byte + if _, err := io.ReadFull(r, buf[:]); err != nil { + if err == io.EOF || err == io.ErrUnexpectedEOF { + err = errMissingRIFFChunkHeader + } + return FourCC{}, nil, err + } + if buf[0] != 'R' || buf[1] != 'I' || buf[2] != 'F' || buf[3] != 'F' { + return FourCC{}, nil, errMissingRIFFChunkHeader + } + return NewListReader(u32(buf[4:]), r) +} + +// NewListReader returns a LIST chunk's list type, such as "movi" or "wavl", +// and its chunks as a *Reader. +func NewListReader(chunkLen uint32, chunkData io.Reader) (listType FourCC, data *Reader, err error) { + if chunkLen < 4 { + return FourCC{}, nil, errShortChunkData + } + z := &Reader{r: chunkData} + if _, err := io.ReadFull(chunkData, z.buf[:4]); err != nil { + if err == io.EOF || err == io.ErrUnexpectedEOF { + err = errShortChunkData + } + return FourCC{}, nil, err + } + z.totalLen = chunkLen - 4 + return FourCC{z.buf[0], z.buf[1], z.buf[2], z.buf[3]}, z, nil +} + +// Reader reads chunks from an underlying io.Reader. +type Reader struct { + r io.Reader + err error + + totalLen uint32 + chunkLen uint32 + + chunkReader *chunkReader + buf [chunkHeaderSize]byte + padded bool +} + +// Next returns the next chunk's ID, length and data. It returns io.EOF if there +// are no more chunks. The io.Reader returned becomes stale after the next Next +// call, and should no longer be used. +// +// It is valid to call Next even if all of the previous chunk's data has not +// been read. +func (z *Reader) Next() (chunkID FourCC, chunkLen uint32, chunkData io.Reader, err error) { + if z.err != nil { + return FourCC{}, 0, nil, z.err + } + + // Drain the rest of the previous chunk. + if z.chunkLen != 0 { + _, z.err = io.Copy(ioutil.Discard, z.chunkReader) + if z.err != nil { + return FourCC{}, 0, nil, z.err + } + } + z.chunkReader = nil + if z.padded { + _, z.err = io.ReadFull(z.r, z.buf[:1]) + if z.err != nil { + if z.err == io.EOF { + z.err = errMissingPaddingByte + } + return FourCC{}, 0, nil, z.err + } + z.totalLen-- + } + + // We are done if we have no more data. + if z.totalLen == 0 { + z.err = io.EOF + return FourCC{}, 0, nil, z.err + } + + // Read the next chunk header. + if z.totalLen < chunkHeaderSize { + z.err = errShortChunkHeader + return FourCC{}, 0, nil, z.err + } + z.totalLen -= chunkHeaderSize + if _, err = io.ReadFull(z.r, z.buf[:chunkHeaderSize]); err != nil { + if z.err == io.EOF || z.err == io.ErrUnexpectedEOF { + z.err = errShortChunkHeader + } + return FourCC{}, 0, nil, z.err + } + chunkID = FourCC{z.buf[0], z.buf[1], z.buf[2], z.buf[3]} + z.chunkLen = u32(z.buf[4:]) + z.padded = z.chunkLen&1 == 1 + z.chunkReader = &chunkReader{z} + return chunkID, z.chunkLen, z.chunkReader, nil +} + +type chunkReader struct { + z *Reader +} + +func (c *chunkReader) Read(p []byte) (int, error) { + if c != c.z.chunkReader { + return 0, errStaleReader + } + z := c.z + if z.err != nil { + if z.err == io.EOF { + return 0, errStaleReader + } + return 0, z.err + } + + n := int(z.chunkLen) + if n == 0 { + return 0, io.EOF + } + if n < 0 { + // Converting uint32 to int overflowed. + n = math.MaxInt32 + } + if n > len(p) { + n = len(p) + } + n, err := z.r.Read(p[:n]) + z.totalLen -= uint32(n) + z.chunkLen -= uint32(n) + if err != io.EOF { + z.err = err + } + return n, err +} diff --git a/src/golang.org/x/image/testdata/blue-purple-pink-large.lossless.webp b/src/golang.org/x/image/testdata/blue-purple-pink-large.lossless.webp new file mode 100644 index 0000000000..d00c81fb0c Binary files /dev/null and b/src/golang.org/x/image/testdata/blue-purple-pink-large.lossless.webp differ diff --git a/src/golang.org/x/image/testdata/blue-purple-pink-large.no-filter.lossy.webp b/src/golang.org/x/image/testdata/blue-purple-pink-large.no-filter.lossy.webp new file mode 100644 index 0000000000..9067f4df3c Binary files /dev/null and b/src/golang.org/x/image/testdata/blue-purple-pink-large.no-filter.lossy.webp differ diff --git a/src/golang.org/x/image/testdata/blue-purple-pink-large.no-filter.lossy.webp.ycbcr.png b/src/golang.org/x/image/testdata/blue-purple-pink-large.no-filter.lossy.webp.ycbcr.png new file mode 100644 index 0000000000..2e32c281be Binary files /dev/null and b/src/golang.org/x/image/testdata/blue-purple-pink-large.no-filter.lossy.webp.ycbcr.png differ diff --git a/src/golang.org/x/image/testdata/blue-purple-pink-large.normal-filter.lossy.webp b/src/golang.org/x/image/testdata/blue-purple-pink-large.normal-filter.lossy.webp new file mode 100644 index 0000000000..a4ccc1a2d0 Binary files /dev/null and b/src/golang.org/x/image/testdata/blue-purple-pink-large.normal-filter.lossy.webp differ diff --git a/src/golang.org/x/image/testdata/blue-purple-pink-large.normal-filter.lossy.webp.ycbcr.png b/src/golang.org/x/image/testdata/blue-purple-pink-large.normal-filter.lossy.webp.ycbcr.png new file mode 100644 index 0000000000..5f7ec42dee Binary files /dev/null and b/src/golang.org/x/image/testdata/blue-purple-pink-large.normal-filter.lossy.webp.ycbcr.png differ diff --git a/src/golang.org/x/image/testdata/blue-purple-pink-large.png b/src/golang.org/x/image/testdata/blue-purple-pink-large.png new file mode 100644 index 0000000000..97555050a3 Binary files /dev/null and b/src/golang.org/x/image/testdata/blue-purple-pink-large.png differ diff --git a/src/golang.org/x/image/testdata/blue-purple-pink-large.simple-filter.lossy.webp b/src/golang.org/x/image/testdata/blue-purple-pink-large.simple-filter.lossy.webp new file mode 100644 index 0000000000..09fdb94b86 Binary files /dev/null and b/src/golang.org/x/image/testdata/blue-purple-pink-large.simple-filter.lossy.webp differ diff --git a/src/golang.org/x/image/testdata/blue-purple-pink-large.simple-filter.lossy.webp.ycbcr.png b/src/golang.org/x/image/testdata/blue-purple-pink-large.simple-filter.lossy.webp.ycbcr.png new file mode 100644 index 0000000000..946b3afac1 Binary files /dev/null and b/src/golang.org/x/image/testdata/blue-purple-pink-large.simple-filter.lossy.webp.ycbcr.png differ diff --git a/src/golang.org/x/image/testdata/blue-purple-pink.lossless.webp b/src/golang.org/x/image/testdata/blue-purple-pink.lossless.webp new file mode 100644 index 0000000000..b16a50ddf8 Binary files /dev/null and b/src/golang.org/x/image/testdata/blue-purple-pink.lossless.webp differ diff --git a/src/golang.org/x/image/testdata/blue-purple-pink.lossy.webp b/src/golang.org/x/image/testdata/blue-purple-pink.lossy.webp new file mode 100644 index 0000000000..d5143c0afd Binary files /dev/null and b/src/golang.org/x/image/testdata/blue-purple-pink.lossy.webp differ diff --git a/src/golang.org/x/image/testdata/blue-purple-pink.lossy.webp.ycbcr.png b/src/golang.org/x/image/testdata/blue-purple-pink.lossy.webp.ycbcr.png new file mode 100644 index 0000000000..eb51560cda Binary files /dev/null and b/src/golang.org/x/image/testdata/blue-purple-pink.lossy.webp.ycbcr.png differ diff --git a/src/golang.org/x/image/testdata/blue-purple-pink.lzwcompressed.tiff b/src/golang.org/x/image/testdata/blue-purple-pink.lzwcompressed.tiff new file mode 100644 index 0000000000..5978f7a7ce Binary files /dev/null and b/src/golang.org/x/image/testdata/blue-purple-pink.lzwcompressed.tiff differ diff --git a/src/golang.org/x/image/testdata/blue-purple-pink.png b/src/golang.org/x/image/testdata/blue-purple-pink.png new file mode 100644 index 0000000000..d4fbf6b371 Binary files /dev/null and b/src/golang.org/x/image/testdata/blue-purple-pink.png differ diff --git a/src/golang.org/x/image/testdata/bw-deflate.tiff b/src/golang.org/x/image/testdata/bw-deflate.tiff new file mode 100644 index 0000000000..137a0c3ef1 Binary files /dev/null and b/src/golang.org/x/image/testdata/bw-deflate.tiff differ diff --git a/src/golang.org/x/image/testdata/bw-packbits.tiff b/src/golang.org/x/image/testdata/bw-packbits.tiff new file mode 100644 index 0000000000..d59fa4aeed Binary files /dev/null and b/src/golang.org/x/image/testdata/bw-packbits.tiff differ diff --git a/src/golang.org/x/image/testdata/bw-uncompressed.tiff b/src/golang.org/x/image/testdata/bw-uncompressed.tiff new file mode 100644 index 0000000000..8390f11357 Binary files /dev/null and b/src/golang.org/x/image/testdata/bw-uncompressed.tiff differ diff --git a/src/golang.org/x/image/testdata/go-turns-two-14x18.png b/src/golang.org/x/image/testdata/go-turns-two-14x18.png new file mode 100644 index 0000000000..b6494b6be5 Binary files /dev/null and b/src/golang.org/x/image/testdata/go-turns-two-14x18.png differ diff --git a/src/golang.org/x/image/testdata/go-turns-two-280x360.jpeg b/src/golang.org/x/image/testdata/go-turns-two-280x360.jpeg new file mode 100644 index 0000000000..b56e492a6c Binary files /dev/null and b/src/golang.org/x/image/testdata/go-turns-two-280x360.jpeg differ diff --git a/src/golang.org/x/image/testdata/go-turns-two-down-ab.png b/src/golang.org/x/image/testdata/go-turns-two-down-ab.png new file mode 100644 index 0000000000..317c3afa15 Binary files /dev/null and b/src/golang.org/x/image/testdata/go-turns-two-down-ab.png differ diff --git a/src/golang.org/x/image/testdata/go-turns-two-down-bl.png b/src/golang.org/x/image/testdata/go-turns-two-down-bl.png new file mode 100644 index 0000000000..597d3628dd Binary files /dev/null and b/src/golang.org/x/image/testdata/go-turns-two-down-bl.png differ diff --git a/src/golang.org/x/image/testdata/go-turns-two-down-cr.png b/src/golang.org/x/image/testdata/go-turns-two-down-cr.png new file mode 100644 index 0000000000..ad1c20a2f0 Binary files /dev/null and b/src/golang.org/x/image/testdata/go-turns-two-down-cr.png differ diff --git a/src/golang.org/x/image/testdata/go-turns-two-down-nn.png b/src/golang.org/x/image/testdata/go-turns-two-down-nn.png new file mode 100644 index 0000000000..166841a761 Binary files /dev/null and b/src/golang.org/x/image/testdata/go-turns-two-down-nn.png differ diff --git a/src/golang.org/x/image/testdata/go-turns-two-rotate-ab.png b/src/golang.org/x/image/testdata/go-turns-two-rotate-ab.png new file mode 100644 index 0000000000..04fceaa77c Binary files /dev/null and b/src/golang.org/x/image/testdata/go-turns-two-rotate-ab.png differ diff --git a/src/golang.org/x/image/testdata/go-turns-two-rotate-bl.png b/src/golang.org/x/image/testdata/go-turns-two-rotate-bl.png new file mode 100644 index 0000000000..c8b717e222 Binary files /dev/null and b/src/golang.org/x/image/testdata/go-turns-two-rotate-bl.png differ diff --git a/src/golang.org/x/image/testdata/go-turns-two-rotate-cr.png b/src/golang.org/x/image/testdata/go-turns-two-rotate-cr.png new file mode 100644 index 0000000000..7e5cd9f624 Binary files /dev/null and b/src/golang.org/x/image/testdata/go-turns-two-rotate-cr.png differ diff --git a/src/golang.org/x/image/testdata/go-turns-two-rotate-nn.png b/src/golang.org/x/image/testdata/go-turns-two-rotate-nn.png new file mode 100644 index 0000000000..702c86352a Binary files /dev/null and b/src/golang.org/x/image/testdata/go-turns-two-rotate-nn.png differ diff --git a/src/golang.org/x/image/testdata/go-turns-two-up-ab.png b/src/golang.org/x/image/testdata/go-turns-two-up-ab.png new file mode 100644 index 0000000000..072446dc9d Binary files /dev/null and b/src/golang.org/x/image/testdata/go-turns-two-up-ab.png differ diff --git a/src/golang.org/x/image/testdata/go-turns-two-up-bl.png b/src/golang.org/x/image/testdata/go-turns-two-up-bl.png new file mode 100644 index 0000000000..c1bf630f43 Binary files /dev/null and b/src/golang.org/x/image/testdata/go-turns-two-up-bl.png differ diff --git a/src/golang.org/x/image/testdata/go-turns-two-up-cr.png b/src/golang.org/x/image/testdata/go-turns-two-up-cr.png new file mode 100644 index 0000000000..0ac83002f1 Binary files /dev/null and b/src/golang.org/x/image/testdata/go-turns-two-up-cr.png differ diff --git a/src/golang.org/x/image/testdata/go-turns-two-up-nn.png b/src/golang.org/x/image/testdata/go-turns-two-up-nn.png new file mode 100644 index 0000000000..eb63cb91e8 Binary files /dev/null and b/src/golang.org/x/image/testdata/go-turns-two-up-nn.png differ diff --git a/src/golang.org/x/image/testdata/gopher-doc.1bpp.lossless.webp b/src/golang.org/x/image/testdata/gopher-doc.1bpp.lossless.webp new file mode 100644 index 0000000000..fcca028736 Binary files /dev/null and b/src/golang.org/x/image/testdata/gopher-doc.1bpp.lossless.webp differ diff --git a/src/golang.org/x/image/testdata/gopher-doc.1bpp.png b/src/golang.org/x/image/testdata/gopher-doc.1bpp.png new file mode 100644 index 0000000000..9c5bb64f26 Binary files /dev/null and b/src/golang.org/x/image/testdata/gopher-doc.1bpp.png differ diff --git a/src/golang.org/x/image/testdata/gopher-doc.2bpp.lossless.webp b/src/golang.org/x/image/testdata/gopher-doc.2bpp.lossless.webp new file mode 100644 index 0000000000..d683d47fc2 Binary files /dev/null and b/src/golang.org/x/image/testdata/gopher-doc.2bpp.lossless.webp differ diff --git a/src/golang.org/x/image/testdata/gopher-doc.2bpp.png b/src/golang.org/x/image/testdata/gopher-doc.2bpp.png new file mode 100644 index 0000000000..af96769c5d Binary files /dev/null and b/src/golang.org/x/image/testdata/gopher-doc.2bpp.png differ diff --git a/src/golang.org/x/image/testdata/gopher-doc.4bpp.lossless.webp b/src/golang.org/x/image/testdata/gopher-doc.4bpp.lossless.webp new file mode 100644 index 0000000000..11d8ef19b7 Binary files /dev/null and b/src/golang.org/x/image/testdata/gopher-doc.4bpp.lossless.webp differ diff --git a/src/golang.org/x/image/testdata/gopher-doc.4bpp.png b/src/golang.org/x/image/testdata/gopher-doc.4bpp.png new file mode 100644 index 0000000000..fc1813778b Binary files /dev/null and b/src/golang.org/x/image/testdata/gopher-doc.4bpp.png differ diff --git a/src/golang.org/x/image/testdata/gopher-doc.8bpp.lossless.webp b/src/golang.org/x/image/testdata/gopher-doc.8bpp.lossless.webp new file mode 100644 index 0000000000..b6468e9b5a Binary files /dev/null and b/src/golang.org/x/image/testdata/gopher-doc.8bpp.lossless.webp differ diff --git a/src/golang.org/x/image/testdata/gopher-doc.8bpp.png b/src/golang.org/x/image/testdata/gopher-doc.8bpp.png new file mode 100644 index 0000000000..b877c54117 Binary files /dev/null and b/src/golang.org/x/image/testdata/gopher-doc.8bpp.png differ diff --git a/src/golang.org/x/image/testdata/no_compress.tiff b/src/golang.org/x/image/testdata/no_compress.tiff new file mode 100644 index 0000000000..3f72b29ae1 Binary files /dev/null and b/src/golang.org/x/image/testdata/no_compress.tiff differ diff --git a/src/golang.org/x/image/testdata/no_rps.tiff b/src/golang.org/x/image/testdata/no_rps.tiff new file mode 100644 index 0000000000..3280cf8e34 Binary files /dev/null and b/src/golang.org/x/image/testdata/no_rps.tiff differ diff --git a/src/golang.org/x/image/testdata/testpattern.png b/src/golang.org/x/image/testdata/testpattern.png new file mode 100644 index 0000000000..ec87bb56a2 Binary files /dev/null and b/src/golang.org/x/image/testdata/testpattern.png differ diff --git a/src/golang.org/x/image/testdata/tux-rotate-ab.png b/src/golang.org/x/image/testdata/tux-rotate-ab.png new file mode 100644 index 0000000000..181966caec Binary files /dev/null and b/src/golang.org/x/image/testdata/tux-rotate-ab.png differ diff --git a/src/golang.org/x/image/testdata/tux-rotate-bl.png b/src/golang.org/x/image/testdata/tux-rotate-bl.png new file mode 100644 index 0000000000..af3f4b0d56 Binary files /dev/null and b/src/golang.org/x/image/testdata/tux-rotate-bl.png differ diff --git a/src/golang.org/x/image/testdata/tux-rotate-cr.png b/src/golang.org/x/image/testdata/tux-rotate-cr.png new file mode 100644 index 0000000000..e5cff31f1b Binary files /dev/null and b/src/golang.org/x/image/testdata/tux-rotate-cr.png differ diff --git a/src/golang.org/x/image/testdata/tux-rotate-nn.png b/src/golang.org/x/image/testdata/tux-rotate-nn.png new file mode 100644 index 0000000000..c775c61dc8 Binary files /dev/null and b/src/golang.org/x/image/testdata/tux-rotate-nn.png differ diff --git a/src/golang.org/x/image/testdata/tux.lossless.webp b/src/golang.org/x/image/testdata/tux.lossless.webp new file mode 100644 index 0000000000..3b32c02a7f Binary files /dev/null and b/src/golang.org/x/image/testdata/tux.lossless.webp differ diff --git a/src/golang.org/x/image/testdata/tux.png b/src/golang.org/x/image/testdata/tux.png new file mode 100644 index 0000000000..2567fe7ca4 Binary files /dev/null and b/src/golang.org/x/image/testdata/tux.png differ diff --git a/src/golang.org/x/image/testdata/video-001-16bit.tiff b/src/golang.org/x/image/testdata/video-001-16bit.tiff new file mode 100644 index 0000000000..3b05ef0187 Binary files /dev/null and b/src/golang.org/x/image/testdata/video-001-16bit.tiff differ diff --git a/src/golang.org/x/image/testdata/video-001-gray-16bit.tiff b/src/golang.org/x/image/testdata/video-001-gray-16bit.tiff new file mode 100644 index 0000000000..356882a56a Binary files /dev/null and b/src/golang.org/x/image/testdata/video-001-gray-16bit.tiff differ diff --git a/src/golang.org/x/image/testdata/video-001-gray.tiff b/src/golang.org/x/image/testdata/video-001-gray.tiff new file mode 100644 index 0000000000..38fc9d2d11 Binary files /dev/null and b/src/golang.org/x/image/testdata/video-001-gray.tiff differ diff --git a/src/golang.org/x/image/testdata/video-001-paletted.tiff b/src/golang.org/x/image/testdata/video-001-paletted.tiff new file mode 100644 index 0000000000..5db84bc933 Binary files /dev/null and b/src/golang.org/x/image/testdata/video-001-paletted.tiff differ diff --git a/src/golang.org/x/image/testdata/video-001-strip-64.tiff b/src/golang.org/x/image/testdata/video-001-strip-64.tiff new file mode 100644 index 0000000000..9cf6c32669 Binary files /dev/null and b/src/golang.org/x/image/testdata/video-001-strip-64.tiff differ diff --git a/src/golang.org/x/image/testdata/video-001-tile-64x64.tiff b/src/golang.org/x/image/testdata/video-001-tile-64x64.tiff new file mode 100644 index 0000000000..fa56713069 Binary files /dev/null and b/src/golang.org/x/image/testdata/video-001-tile-64x64.tiff differ diff --git a/src/golang.org/x/image/testdata/video-001-uncompressed.tiff b/src/golang.org/x/image/testdata/video-001-uncompressed.tiff new file mode 100644 index 0000000000..fad147107b Binary files /dev/null and b/src/golang.org/x/image/testdata/video-001-uncompressed.tiff differ diff --git a/src/golang.org/x/image/testdata/video-001.bmp b/src/golang.org/x/image/testdata/video-001.bmp new file mode 100644 index 0000000000..ca3dd42a7c Binary files /dev/null and b/src/golang.org/x/image/testdata/video-001.bmp differ diff --git a/src/golang.org/x/image/testdata/video-001.lossy.webp b/src/golang.org/x/image/testdata/video-001.lossy.webp new file mode 100644 index 0000000000..302198eca4 Binary files /dev/null and b/src/golang.org/x/image/testdata/video-001.lossy.webp differ diff --git a/src/golang.org/x/image/testdata/video-001.lossy.webp.ycbcr.png b/src/golang.org/x/image/testdata/video-001.lossy.webp.ycbcr.png new file mode 100644 index 0000000000..dc5f8cfc36 Binary files /dev/null and b/src/golang.org/x/image/testdata/video-001.lossy.webp.ycbcr.png differ diff --git a/src/golang.org/x/image/testdata/video-001.png b/src/golang.org/x/image/testdata/video-001.png new file mode 100644 index 0000000000..d3468bbe8f Binary files /dev/null and b/src/golang.org/x/image/testdata/video-001.png differ diff --git a/src/golang.org/x/image/testdata/video-001.tiff b/src/golang.org/x/image/testdata/video-001.tiff new file mode 100644 index 0000000000..0dd6cd9313 Binary files /dev/null and b/src/golang.org/x/image/testdata/video-001.tiff differ diff --git a/src/golang.org/x/image/testdata/yellow_rose-small.bmp b/src/golang.org/x/image/testdata/yellow_rose-small.bmp new file mode 100644 index 0000000000..866fc7a863 Binary files /dev/null and b/src/golang.org/x/image/testdata/yellow_rose-small.bmp differ diff --git a/src/golang.org/x/image/testdata/yellow_rose-small.png b/src/golang.org/x/image/testdata/yellow_rose-small.png new file mode 100644 index 0000000000..772c239fe7 Binary files /dev/null and b/src/golang.org/x/image/testdata/yellow_rose-small.png differ diff --git a/src/golang.org/x/image/testdata/yellow_rose.lossless.webp b/src/golang.org/x/image/testdata/yellow_rose.lossless.webp new file mode 100644 index 0000000000..0c028f45fa Binary files /dev/null and b/src/golang.org/x/image/testdata/yellow_rose.lossless.webp differ diff --git a/src/golang.org/x/image/testdata/yellow_rose.lossy-with-alpha.webp b/src/golang.org/x/image/testdata/yellow_rose.lossy-with-alpha.webp new file mode 100644 index 0000000000..64d3b5d304 Binary files /dev/null and b/src/golang.org/x/image/testdata/yellow_rose.lossy-with-alpha.webp differ diff --git a/src/golang.org/x/image/testdata/yellow_rose.lossy-with-alpha.webp.nycbcra.png b/src/golang.org/x/image/testdata/yellow_rose.lossy-with-alpha.webp.nycbcra.png new file mode 100644 index 0000000000..44453158cb Binary files /dev/null and b/src/golang.org/x/image/testdata/yellow_rose.lossy-with-alpha.webp.nycbcra.png differ diff --git a/src/golang.org/x/image/testdata/yellow_rose.lossy.webp b/src/golang.org/x/image/testdata/yellow_rose.lossy.webp new file mode 100644 index 0000000000..57a845e2b9 Binary files /dev/null and b/src/golang.org/x/image/testdata/yellow_rose.lossy.webp differ diff --git a/src/golang.org/x/image/testdata/yellow_rose.lossy.webp.ycbcr.png b/src/golang.org/x/image/testdata/yellow_rose.lossy.webp.ycbcr.png new file mode 100644 index 0000000000..5e3bcd8972 Binary files /dev/null and b/src/golang.org/x/image/testdata/yellow_rose.lossy.webp.ycbcr.png differ diff --git a/src/golang.org/x/image/testdata/yellow_rose.png b/src/golang.org/x/image/testdata/yellow_rose.png new file mode 100644 index 0000000000..bbaefa88b4 Binary files /dev/null and b/src/golang.org/x/image/testdata/yellow_rose.png differ diff --git a/src/golang.org/x/image/tiff/buffer.go b/src/golang.org/x/image/tiff/buffer.go new file mode 100644 index 0000000000..d1801be48e --- /dev/null +++ b/src/golang.org/x/image/tiff/buffer.go @@ -0,0 +1,69 @@ +// 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. + +package tiff + +import "io" + +// buffer buffers an io.Reader to satisfy io.ReaderAt. +type buffer struct { + r io.Reader + buf []byte +} + +// fill reads data from b.r until the buffer contains at least end bytes. +func (b *buffer) fill(end int) error { + m := len(b.buf) + if end > m { + if end > cap(b.buf) { + newcap := 1024 + for newcap < end { + newcap *= 2 + } + newbuf := make([]byte, end, newcap) + copy(newbuf, b.buf) + b.buf = newbuf + } else { + b.buf = b.buf[:end] + } + if n, err := io.ReadFull(b.r, b.buf[m:end]); err != nil { + end = m + n + b.buf = b.buf[:end] + return err + } + } + return nil +} + +func (b *buffer) ReadAt(p []byte, off int64) (int, error) { + o := int(off) + end := o + len(p) + if int64(end) != off+int64(len(p)) { + return 0, io.ErrUnexpectedEOF + } + + err := b.fill(end) + return copy(p, b.buf[o:end]), err +} + +// Slice returns a slice of the underlying buffer. The slice contains +// n bytes starting at offset off. +func (b *buffer) Slice(off, n int) ([]byte, error) { + end := off + n + if err := b.fill(end); err != nil { + return nil, err + } + return b.buf[off:end], nil +} + +// newReaderAt converts an io.Reader into an io.ReaderAt. +func newReaderAt(r io.Reader) io.ReaderAt { + if ra, ok := r.(io.ReaderAt); ok { + return ra + } + return &buffer{ + r: r, + buf: make([]byte, 0, 1024), + } +} diff --git a/src/golang.org/x/image/tiff/buffer_test.go b/src/golang.org/x/image/tiff/buffer_test.go new file mode 100644 index 0000000000..e13afb3619 --- /dev/null +++ b/src/golang.org/x/image/tiff/buffer_test.go @@ -0,0 +1,36 @@ +// 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. + +package tiff + +import ( + "io" + "strings" + "testing" +) + +var readAtTests = []struct { + n int + off int64 + s string + err error +}{ + {2, 0, "ab", nil}, + {6, 0, "abcdef", nil}, + {3, 3, "def", nil}, + {3, 5, "f", io.EOF}, + {3, 6, "", io.EOF}, +} + +func TestReadAt(t *testing.T) { + r := newReaderAt(strings.NewReader("abcdef")) + b := make([]byte, 10) + for _, test := range readAtTests { + n, err := r.ReadAt(b[:test.n], test.off) + s := string(b[:n]) + if s != test.s || err != test.err { + t.Errorf("buffer.ReadAt(<%v bytes>, %v): got %v, %q; want %v, %q", test.n, test.off, err, s, test.err, test.s) + } + } +} diff --git a/src/golang.org/x/image/tiff/compress.go b/src/golang.org/x/image/tiff/compress.go new file mode 100644 index 0000000000..3f176f00a9 --- /dev/null +++ b/src/golang.org/x/image/tiff/compress.go @@ -0,0 +1,58 @@ +// 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. + +package tiff + +import ( + "bufio" + "io" +) + +type byteReader interface { + io.Reader + io.ByteReader +} + +// unpackBits decodes the PackBits-compressed data in src and returns the +// uncompressed data. +// +// The PackBits compression format is described in section 9 (p. 42) +// of the TIFF spec. +func unpackBits(r io.Reader) ([]byte, error) { + buf := make([]byte, 128) + dst := make([]byte, 0, 1024) + br, ok := r.(byteReader) + if !ok { + br = bufio.NewReader(r) + } + + for { + b, err := br.ReadByte() + if err != nil { + if err == io.EOF { + return dst, nil + } + return nil, err + } + code := int(int8(b)) + switch { + case code >= 0: + n, err := io.ReadFull(br, buf[:code+1]) + if err != nil { + return nil, err + } + dst = append(dst, buf[:n]...) + case code == -128: + // No-op. + default: + if b, err = br.ReadByte(); err != nil { + return nil, err + } + for j := 0; j < 1-code; j++ { + buf[j] = b + } + dst = append(dst, buf[:1-code]...) + } + } +} diff --git a/src/golang.org/x/image/tiff/consts.go b/src/golang.org/x/image/tiff/consts.go new file mode 100644 index 0000000000..3c51a70bef --- /dev/null +++ b/src/golang.org/x/image/tiff/consts.go @@ -0,0 +1,133 @@ +// 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. + +package tiff + +// A tiff image file contains one or more images. The metadata +// of each image is contained in an Image File Directory (IFD), +// which contains entries of 12 bytes each and is described +// on page 14-16 of the specification. An IFD entry consists of +// +// - a tag, which describes the signification of the entry, +// - the data type and length of the entry, +// - the data itself or a pointer to it if it is more than 4 bytes. +// +// The presence of a length means that each IFD is effectively an array. + +const ( + leHeader = "II\x2A\x00" // Header for little-endian files. + beHeader = "MM\x00\x2A" // Header for big-endian files. + + ifdLen = 12 // Length of an IFD entry in bytes. +) + +// Data types (p. 14-16 of the spec). +const ( + dtByte = 1 + dtASCII = 2 + dtShort = 3 + dtLong = 4 + dtRational = 5 +) + +// The length of one instance of each data type in bytes. +var lengths = [...]uint32{0, 1, 1, 2, 4, 8} + +// Tags (see p. 28-41 of the spec). +const ( + tImageWidth = 256 + tImageLength = 257 + tBitsPerSample = 258 + tCompression = 259 + tPhotometricInterpretation = 262 + + tStripOffsets = 273 + tSamplesPerPixel = 277 + tRowsPerStrip = 278 + tStripByteCounts = 279 + + tTileWidth = 322 + tTileLength = 323 + tTileOffsets = 324 + tTileByteCounts = 325 + + tXResolution = 282 + tYResolution = 283 + tResolutionUnit = 296 + + tPredictor = 317 + tColorMap = 320 + tExtraSamples = 338 + tSampleFormat = 339 +) + +// Compression types (defined in various places in the spec and supplements). +const ( + cNone = 1 + cCCITT = 2 + cG3 = 3 // Group 3 Fax. + cG4 = 4 // Group 4 Fax. + cLZW = 5 + cJPEGOld = 6 // Superseded by cJPEG. + cJPEG = 7 + cDeflate = 8 // zlib compression. + cPackBits = 32773 + cDeflateOld = 32946 // Superseded by cDeflate. +) + +// Photometric interpretation values (see p. 37 of the spec). +const ( + pWhiteIsZero = 0 + pBlackIsZero = 1 + pRGB = 2 + pPaletted = 3 + pTransMask = 4 // transparency mask + pCMYK = 5 + pYCbCr = 6 + pCIELab = 8 +) + +// Values for the tPredictor tag (page 64-65 of the spec). +const ( + prNone = 1 + prHorizontal = 2 +) + +// Values for the tResolutionUnit tag (page 18). +const ( + resNone = 1 + resPerInch = 2 // Dots per inch. + resPerCM = 3 // Dots per centimeter. +) + +// imageMode represents the mode of the image. +type imageMode int + +const ( + mBilevel imageMode = iota + mPaletted + mGray + mGrayInvert + mRGB + mRGBA + mNRGBA +) + +// CompressionType describes the type of compression used in Options. +type CompressionType int + +const ( + Uncompressed CompressionType = iota + Deflate +) + +// specValue returns the compression type constant from the TIFF spec that +// is equivalent to c. +func (c CompressionType) specValue() uint32 { + switch c { + case Deflate: + return cDeflate + } + return cNone +} diff --git a/src/golang.org/x/image/tiff/lzw/reader.go b/src/golang.org/x/image/tiff/lzw/reader.go new file mode 100644 index 0000000000..ad35819f5d --- /dev/null +++ b/src/golang.org/x/image/tiff/lzw/reader.go @@ -0,0 +1,277 @@ +// 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. + +// Package lzw implements the Lempel-Ziv-Welch compressed data format, +// described in T. A. Welch, ``A Technique for High-Performance Data +// Compression'', Computer, 17(6) (June 1984), pp 8-19. +// +// In particular, it implements LZW as used by the TIFF file format, including +// an "off by one" algorithmic difference when compared to standard LZW. +package lzw // import "golang.org/x/image/tiff/lzw" + +/* +This file was branched from src/pkg/compress/lzw/reader.go in the +standard library. Differences from the original are marked with "NOTE". + +The tif_lzw.c file in the libtiff C library has this comment: + +---- +The 5.0 spec describes a different algorithm than Aldus +implements. Specifically, Aldus does code length transitions +one code earlier than should be done (for real LZW). +Earlier versions of this library implemented the correct +LZW algorithm, but emitted codes in a bit order opposite +to the TIFF spec. Thus, to maintain compatibility w/ Aldus +we interpret MSB-LSB ordered codes to be images written w/ +old versions of this library, but otherwise adhere to the +Aldus "off by one" algorithm. +---- + +The Go code doesn't read (invalid) TIFF files written by old versions of +libtiff, but the LZW algorithm in this package still differs from the one in +Go's standard package library to accomodate this "off by one" in valid TIFFs. +*/ + +import ( + "bufio" + "errors" + "fmt" + "io" +) + +// Order specifies the bit ordering in an LZW data stream. +type Order int + +const ( + // LSB means Least Significant Bits first, as used in the GIF file format. + LSB Order = iota + // MSB means Most Significant Bits first, as used in the TIFF and PDF + // file formats. + MSB +) + +const ( + maxWidth = 12 + decoderInvalidCode = 0xffff + flushBuffer = 1 << maxWidth +) + +// decoder is the state from which the readXxx method converts a byte +// stream into a code stream. +type decoder struct { + r io.ByteReader + bits uint32 + nBits uint + width uint + read func(*decoder) (uint16, error) // readLSB or readMSB + litWidth int // width in bits of literal codes + err error + + // The first 1<= 1<>= d.width + d.nBits -= d.width + return code, nil +} + +// readMSB returns the next code for "Most Significant Bits first" data. +func (d *decoder) readMSB() (uint16, error) { + for d.nBits < d.width { + x, err := d.r.ReadByte() + if err != nil { + return 0, err + } + d.bits |= uint32(x) << (24 - d.nBits) + d.nBits += 8 + } + code := uint16(d.bits >> (32 - d.width)) + d.bits <<= d.width + d.nBits -= d.width + return code, nil +} + +func (d *decoder) Read(b []byte) (int, error) { + for { + if len(d.toRead) > 0 { + n := copy(b, d.toRead) + d.toRead = d.toRead[n:] + return n, nil + } + if d.err != nil { + return 0, d.err + } + d.decode() + } +} + +// decode decompresses bytes from r and leaves them in d.toRead. +// read specifies how to decode bytes into codes. +// litWidth is the width in bits of literal codes. +func (d *decoder) decode() { + // Loop over the code stream, converting codes into decompressed bytes. + for { + code, err := d.read(d) + if err != nil { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + d.err = err + d.flush() + return + } + switch { + case code < d.clear: + // We have a literal code. + d.output[d.o] = uint8(code) + d.o++ + if d.last != decoderInvalidCode { + // Save what the hi code expands to. + d.suffix[d.hi] = uint8(code) + d.prefix[d.hi] = d.last + } + case code == d.clear: + d.width = 1 + uint(d.litWidth) + d.hi = d.eof + d.overflow = 1 << d.width + d.last = decoderInvalidCode + continue + case code == d.eof: + d.flush() + d.err = io.EOF + return + case code <= d.hi: + c, i := code, len(d.output)-1 + if code == d.hi { + // code == hi is a special case which expands to the last expansion + // followed by the head of the last expansion. To find the head, we walk + // the prefix chain until we find a literal code. + c = d.last + for c >= d.clear { + c = d.prefix[c] + } + d.output[i] = uint8(c) + i-- + c = d.last + } + // Copy the suffix chain into output and then write that to w. + for c >= d.clear { + d.output[i] = d.suffix[c] + i-- + c = d.prefix[c] + } + d.output[i] = uint8(c) + d.o += copy(d.output[d.o:], d.output[i:]) + if d.last != decoderInvalidCode { + // Save what the hi code expands to. + d.suffix[d.hi] = uint8(c) + d.prefix[d.hi] = d.last + } + default: + d.err = errors.New("lzw: invalid code") + d.flush() + return + } + d.last, d.hi = code, d.hi+1 + if d.hi+1 >= d.overflow { // NOTE: the "+1" is where TIFF's LZW differs from the standard algorithm. + if d.width == maxWidth { + d.last = decoderInvalidCode + } else { + d.width++ + d.overflow <<= 1 + } + } + if d.o >= flushBuffer { + d.flush() + return + } + } +} + +func (d *decoder) flush() { + d.toRead = d.output[:d.o] + d.o = 0 +} + +var errClosed = errors.New("lzw: reader/writer is closed") + +func (d *decoder) Close() error { + d.err = errClosed // in case any Reads come along + return nil +} + +// NewReader creates a new io.ReadCloser. +// Reads from the returned io.ReadCloser read and decompress data from r. +// If r does not also implement io.ByteReader, +// the decompressor may read more data than necessary from r. +// It is the caller's responsibility to call Close on the ReadCloser when +// finished reading. +// The number of bits to use for literal codes, litWidth, must be in the +// range [2,8] and is typically 8. It must equal the litWidth +// used during compression. +func NewReader(r io.Reader, order Order, litWidth int) io.ReadCloser { + d := new(decoder) + switch order { + case LSB: + d.read = (*decoder).readLSB + case MSB: + d.read = (*decoder).readMSB + default: + d.err = errors.New("lzw: unknown order") + return d + } + if litWidth < 2 || 8 < litWidth { + d.err = fmt.Errorf("lzw: litWidth %d out of range", litWidth) + return d + } + if br, ok := r.(io.ByteReader); ok { + d.r = br + } else { + d.r = bufio.NewReader(r) + } + d.litWidth = litWidth + d.width = 1 + uint(litWidth) + d.clear = uint16(1) << uint(litWidth) + d.eof, d.hi = d.clear+1, d.clear+1 + d.overflow = uint16(1) << d.width + d.last = decoderInvalidCode + + return d +} diff --git a/src/golang.org/x/image/tiff/reader.go b/src/golang.org/x/image/tiff/reader.go new file mode 100644 index 0000000000..df39e8284b --- /dev/null +++ b/src/golang.org/x/image/tiff/reader.go @@ -0,0 +1,681 @@ +// 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. + +// Package tiff implements a TIFF image decoder and encoder. +// +// The TIFF specification is at http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf +package tiff // import "golang.org/x/image/tiff" + +import ( + "compress/zlib" + "encoding/binary" + "fmt" + "image" + "image/color" + "io" + "io/ioutil" + "math" + + "golang.org/x/image/tiff/lzw" +) + +// A FormatError reports that the input is not a valid TIFF image. +type FormatError string + +func (e FormatError) Error() string { + return "tiff: invalid format: " + string(e) +} + +// An UnsupportedError reports that the input uses a valid but +// unimplemented feature. +type UnsupportedError string + +func (e UnsupportedError) Error() string { + return "tiff: unsupported feature: " + string(e) +} + +// An InternalError reports that an internal error was encountered. +type InternalError string + +func (e InternalError) Error() string { + return "tiff: internal error: " + string(e) +} + +var errNoPixels = FormatError("not enough pixel data") + +type decoder struct { + r io.ReaderAt + byteOrder binary.ByteOrder + config image.Config + mode imageMode + bpp uint + features map[int][]uint + palette []color.Color + + buf []byte + off int // Current offset in buf. + v uint32 // Buffer value for reading with arbitrary bit depths. + nbits uint // Remaining number of bits in v. +} + +// firstVal returns the first uint of the features entry with the given tag, +// or 0 if the tag does not exist. +func (d *decoder) firstVal(tag int) uint { + f := d.features[tag] + if len(f) == 0 { + return 0 + } + return f[0] +} + +// ifdUint decodes the IFD entry in p, which must be of the Byte, Short +// or Long type, and returns the decoded uint values. +func (d *decoder) ifdUint(p []byte) (u []uint, err error) { + var raw []byte + if len(p) < ifdLen { + return nil, FormatError("bad IFD entry") + } + + datatype := d.byteOrder.Uint16(p[2:4]) + if dt := int(datatype); dt <= 0 || dt >= len(lengths) { + return nil, UnsupportedError("IFD entry datatype") + } + + count := d.byteOrder.Uint32(p[4:8]) + if count > math.MaxInt32/lengths[datatype] { + return nil, FormatError("IFD data too large") + } + if datalen := lengths[datatype] * count; datalen > 4 { + // The IFD contains a pointer to the real value. + raw = make([]byte, datalen) + _, err = d.r.ReadAt(raw, int64(d.byteOrder.Uint32(p[8:12]))) + } else { + raw = p[8 : 8+datalen] + } + if err != nil { + return nil, err + } + + u = make([]uint, count) + switch datatype { + case dtByte: + for i := uint32(0); i < count; i++ { + u[i] = uint(raw[i]) + } + case dtShort: + for i := uint32(0); i < count; i++ { + u[i] = uint(d.byteOrder.Uint16(raw[2*i : 2*(i+1)])) + } + case dtLong: + for i := uint32(0); i < count; i++ { + u[i] = uint(d.byteOrder.Uint32(raw[4*i : 4*(i+1)])) + } + default: + return nil, UnsupportedError("data type") + } + return u, nil +} + +// parseIFD decides whether the the IFD entry in p is "interesting" and +// stows away the data in the decoder. +func (d *decoder) parseIFD(p []byte) error { + tag := d.byteOrder.Uint16(p[0:2]) + switch tag { + case tBitsPerSample, + tExtraSamples, + tPhotometricInterpretation, + tCompression, + tPredictor, + tStripOffsets, + tStripByteCounts, + tRowsPerStrip, + tTileWidth, + tTileLength, + tTileOffsets, + tTileByteCounts, + tImageLength, + tImageWidth: + val, err := d.ifdUint(p) + if err != nil { + return err + } + d.features[int(tag)] = val + case tColorMap: + val, err := d.ifdUint(p) + if err != nil { + return err + } + numcolors := len(val) / 3 + if len(val)%3 != 0 || numcolors <= 0 || numcolors > 256 { + return FormatError("bad ColorMap length") + } + d.palette = make([]color.Color, numcolors) + for i := 0; i < numcolors; i++ { + d.palette[i] = color.RGBA64{ + uint16(val[i]), + uint16(val[i+numcolors]), + uint16(val[i+2*numcolors]), + 0xffff, + } + } + case tSampleFormat: + // Page 27 of the spec: If the SampleFormat is present and + // the value is not 1 [= unsigned integer data], a Baseline + // TIFF reader that cannot handle the SampleFormat value + // must terminate the import process gracefully. + val, err := d.ifdUint(p) + if err != nil { + return err + } + for _, v := range val { + if v != 1 { + return UnsupportedError("sample format") + } + } + } + return nil +} + +// readBits reads n bits from the internal buffer starting at the current offset. +func (d *decoder) readBits(n uint) (v uint32, ok bool) { + for d.nbits < n { + d.v <<= 8 + if d.off >= len(d.buf) { + return 0, false + } + d.v |= uint32(d.buf[d.off]) + d.off++ + d.nbits += 8 + } + d.nbits -= n + rv := d.v >> d.nbits + d.v &^= rv << d.nbits + return rv, true +} + +// flushBits discards the unread bits in the buffer used by readBits. +// It is used at the end of a line. +func (d *decoder) flushBits() { + d.v = 0 + d.nbits = 0 +} + +// minInt returns the smaller of x or y. +func minInt(a, b int) int { + if a <= b { + return a + } + return b +} + +// decode decodes the raw data of an image. +// It reads from d.buf and writes the strip or tile into dst. +func (d *decoder) decode(dst image.Image, xmin, ymin, xmax, ymax int) error { + d.off = 0 + + // Apply horizontal predictor if necessary. + // In this case, p contains the color difference to the preceding pixel. + // See page 64-65 of the spec. + if d.firstVal(tPredictor) == prHorizontal { + switch d.bpp { + case 16: + var off int + n := 2 * len(d.features[tBitsPerSample]) // bytes per sample times samples per pixel + for y := ymin; y < ymax; y++ { + off += n + for x := 0; x < (xmax-xmin-1)*n; x += 2 { + if off+2 > len(d.buf) { + return errNoPixels + } + v0 := d.byteOrder.Uint16(d.buf[off-n : off-n+2]) + v1 := d.byteOrder.Uint16(d.buf[off : off+2]) + d.byteOrder.PutUint16(d.buf[off:off+2], v1+v0) + off += 2 + } + } + case 8: + var off int + n := 1 * len(d.features[tBitsPerSample]) // bytes per sample times samples per pixel + for y := ymin; y < ymax; y++ { + off += n + for x := 0; x < (xmax-xmin-1)*n; x++ { + if off >= len(d.buf) { + return errNoPixels + } + d.buf[off] += d.buf[off-n] + off++ + } + } + case 1: + return UnsupportedError("horizontal predictor with 1 BitsPerSample") + } + } + + rMaxX := minInt(xmax, dst.Bounds().Max.X) + rMaxY := minInt(ymax, dst.Bounds().Max.Y) + switch d.mode { + case mGray, mGrayInvert: + if d.bpp == 16 { + img := dst.(*image.Gray16) + for y := ymin; y < rMaxY; y++ { + for x := xmin; x < rMaxX; x++ { + if d.off+2 > len(d.buf) { + return errNoPixels + } + v := d.byteOrder.Uint16(d.buf[d.off : d.off+2]) + d.off += 2 + if d.mode == mGrayInvert { + v = 0xffff - v + } + img.SetGray16(x, y, color.Gray16{v}) + } + } + } else { + img := dst.(*image.Gray) + max := uint32((1 << d.bpp) - 1) + for y := ymin; y < rMaxY; y++ { + for x := xmin; x < rMaxX; x++ { + v, ok := d.readBits(d.bpp) + if !ok { + return errNoPixels + } + v = v * 0xff / max + if d.mode == mGrayInvert { + v = 0xff - v + } + img.SetGray(x, y, color.Gray{uint8(v)}) + } + d.flushBits() + } + } + case mPaletted: + img := dst.(*image.Paletted) + for y := ymin; y < rMaxY; y++ { + for x := xmin; x < rMaxX; x++ { + v, ok := d.readBits(d.bpp) + if !ok { + return errNoPixels + } + img.SetColorIndex(x, y, uint8(v)) + } + d.flushBits() + } + case mRGB: + if d.bpp == 16 { + img := dst.(*image.RGBA64) + for y := ymin; y < rMaxY; y++ { + for x := xmin; x < rMaxX; x++ { + if d.off+6 > len(d.buf) { + return errNoPixels + } + r := d.byteOrder.Uint16(d.buf[d.off+0 : d.off+2]) + g := d.byteOrder.Uint16(d.buf[d.off+2 : d.off+4]) + b := d.byteOrder.Uint16(d.buf[d.off+4 : d.off+6]) + d.off += 6 + img.SetRGBA64(x, y, color.RGBA64{r, g, b, 0xffff}) + } + } + } else { + img := dst.(*image.RGBA) + for y := ymin; y < rMaxY; y++ { + min := img.PixOffset(xmin, y) + max := img.PixOffset(rMaxX, y) + off := (y - ymin) * (xmax - xmin) * 3 + for i := min; i < max; i += 4 { + if off+3 > len(d.buf) { + return errNoPixels + } + img.Pix[i+0] = d.buf[off+0] + img.Pix[i+1] = d.buf[off+1] + img.Pix[i+2] = d.buf[off+2] + img.Pix[i+3] = 0xff + off += 3 + } + } + } + case mNRGBA: + if d.bpp == 16 { + img := dst.(*image.NRGBA64) + for y := ymin; y < rMaxY; y++ { + for x := xmin; x < rMaxX; x++ { + if d.off+8 > len(d.buf) { + return errNoPixels + } + r := d.byteOrder.Uint16(d.buf[d.off+0 : d.off+2]) + g := d.byteOrder.Uint16(d.buf[d.off+2 : d.off+4]) + b := d.byteOrder.Uint16(d.buf[d.off+4 : d.off+6]) + a := d.byteOrder.Uint16(d.buf[d.off+6 : d.off+8]) + d.off += 8 + img.SetNRGBA64(x, y, color.NRGBA64{r, g, b, a}) + } + } + } else { + img := dst.(*image.NRGBA) + for y := ymin; y < rMaxY; y++ { + min := img.PixOffset(xmin, y) + max := img.PixOffset(rMaxX, y) + i0, i1 := (y-ymin)*(xmax-xmin)*4, (y-ymin+1)*(xmax-xmin)*4 + if i1 > len(d.buf) { + return errNoPixels + } + copy(img.Pix[min:max], d.buf[i0:i1]) + } + } + case mRGBA: + if d.bpp == 16 { + img := dst.(*image.RGBA64) + for y := ymin; y < rMaxY; y++ { + for x := xmin; x < rMaxX; x++ { + if d.off+8 > len(d.buf) { + return errNoPixels + } + r := d.byteOrder.Uint16(d.buf[d.off+0 : d.off+2]) + g := d.byteOrder.Uint16(d.buf[d.off+2 : d.off+4]) + b := d.byteOrder.Uint16(d.buf[d.off+4 : d.off+6]) + a := d.byteOrder.Uint16(d.buf[d.off+6 : d.off+8]) + d.off += 8 + img.SetRGBA64(x, y, color.RGBA64{r, g, b, a}) + } + } + } else { + img := dst.(*image.RGBA) + for y := ymin; y < rMaxY; y++ { + min := img.PixOffset(xmin, y) + max := img.PixOffset(rMaxX, y) + i0, i1 := (y-ymin)*(xmax-xmin)*4, (y-ymin+1)*(xmax-xmin)*4 + if i1 > len(d.buf) { + return errNoPixels + } + copy(img.Pix[min:max], d.buf[i0:i1]) + } + } + } + + return nil +} + +func newDecoder(r io.Reader) (*decoder, error) { + d := &decoder{ + r: newReaderAt(r), + features: make(map[int][]uint), + } + + p := make([]byte, 8) + if _, err := d.r.ReadAt(p, 0); err != nil { + return nil, err + } + switch string(p[0:4]) { + case leHeader: + d.byteOrder = binary.LittleEndian + case beHeader: + d.byteOrder = binary.BigEndian + default: + return nil, FormatError("malformed header") + } + + ifdOffset := int64(d.byteOrder.Uint32(p[4:8])) + + // The first two bytes contain the number of entries (12 bytes each). + if _, err := d.r.ReadAt(p[0:2], ifdOffset); err != nil { + return nil, err + } + numItems := int(d.byteOrder.Uint16(p[0:2])) + + // All IFD entries are read in one chunk. + p = make([]byte, ifdLen*numItems) + if _, err := d.r.ReadAt(p, ifdOffset+2); err != nil { + return nil, err + } + + for i := 0; i < len(p); i += ifdLen { + if err := d.parseIFD(p[i : i+ifdLen]); err != nil { + return nil, err + } + } + + d.config.Width = int(d.firstVal(tImageWidth)) + d.config.Height = int(d.firstVal(tImageLength)) + + if _, ok := d.features[tBitsPerSample]; !ok { + return nil, FormatError("BitsPerSample tag missing") + } + d.bpp = d.firstVal(tBitsPerSample) + switch d.bpp { + case 0: + return nil, FormatError("BitsPerSample must not be 0") + case 1, 8, 16: + // Nothing to do, these are accepted by this implementation. + default: + return nil, UnsupportedError(fmt.Sprintf("BitsPerSample of %v", d.bpp)) + } + + // Determine the image mode. + switch d.firstVal(tPhotometricInterpretation) { + case pRGB: + if d.bpp == 16 { + for _, b := range d.features[tBitsPerSample] { + if b != 16 { + return nil, FormatError("wrong number of samples for 16bit RGB") + } + } + } else { + for _, b := range d.features[tBitsPerSample] { + if b != 8 { + return nil, FormatError("wrong number of samples for 8bit RGB") + } + } + } + // RGB images normally have 3 samples per pixel. + // If there are more, ExtraSamples (p. 31-32 of the spec) + // gives their meaning (usually an alpha channel). + // + // This implementation does not support extra samples + // of an unspecified type. + switch len(d.features[tBitsPerSample]) { + case 3: + d.mode = mRGB + if d.bpp == 16 { + d.config.ColorModel = color.RGBA64Model + } else { + d.config.ColorModel = color.RGBAModel + } + case 4: + switch d.firstVal(tExtraSamples) { + case 1: + d.mode = mRGBA + if d.bpp == 16 { + d.config.ColorModel = color.RGBA64Model + } else { + d.config.ColorModel = color.RGBAModel + } + case 2: + d.mode = mNRGBA + if d.bpp == 16 { + d.config.ColorModel = color.NRGBA64Model + } else { + d.config.ColorModel = color.NRGBAModel + } + default: + return nil, FormatError("wrong number of samples for RGB") + } + default: + return nil, FormatError("wrong number of samples for RGB") + } + case pPaletted: + d.mode = mPaletted + d.config.ColorModel = color.Palette(d.palette) + case pWhiteIsZero: + d.mode = mGrayInvert + if d.bpp == 16 { + d.config.ColorModel = color.Gray16Model + } else { + d.config.ColorModel = color.GrayModel + } + case pBlackIsZero: + d.mode = mGray + if d.bpp == 16 { + d.config.ColorModel = color.Gray16Model + } else { + d.config.ColorModel = color.GrayModel + } + default: + return nil, UnsupportedError("color model") + } + + return d, nil +} + +// DecodeConfig returns the color model and dimensions of a TIFF image without +// decoding the entire image. +func DecodeConfig(r io.Reader) (image.Config, error) { + d, err := newDecoder(r) + if err != nil { + return image.Config{}, err + } + return d.config, nil +} + +// Decode reads a TIFF image from r and returns it as an image.Image. +// The type of Image returned depends on the contents of the TIFF. +func Decode(r io.Reader) (img image.Image, err error) { + d, err := newDecoder(r) + if err != nil { + return + } + + blockPadding := false + blockWidth := d.config.Width + blockHeight := d.config.Height + blocksAcross := 1 + blocksDown := 1 + + if d.config.Width == 0 { + blocksAcross = 0 + } + if d.config.Height == 0 { + blocksDown = 0 + } + + var blockOffsets, blockCounts []uint + + if int(d.firstVal(tTileWidth)) != 0 { + blockPadding = true + + blockWidth = int(d.firstVal(tTileWidth)) + blockHeight = int(d.firstVal(tTileLength)) + + if blockWidth != 0 { + blocksAcross = (d.config.Width + blockWidth - 1) / blockWidth + } + if blockHeight != 0 { + blocksDown = (d.config.Height + blockHeight - 1) / blockHeight + } + + blockCounts = d.features[tTileByteCounts] + blockOffsets = d.features[tTileOffsets] + + } else { + if int(d.firstVal(tRowsPerStrip)) != 0 { + blockHeight = int(d.firstVal(tRowsPerStrip)) + } + + if blockHeight != 0 { + blocksDown = (d.config.Height + blockHeight - 1) / blockHeight + } + + blockOffsets = d.features[tStripOffsets] + blockCounts = d.features[tStripByteCounts] + } + + // Check if we have the right number of strips/tiles, offsets and counts. + if n := blocksAcross * blocksDown; len(blockOffsets) < n || len(blockCounts) < n { + return nil, FormatError("inconsistent header") + } + + imgRect := image.Rect(0, 0, d.config.Width, d.config.Height) + switch d.mode { + case mGray, mGrayInvert: + if d.bpp == 16 { + img = image.NewGray16(imgRect) + } else { + img = image.NewGray(imgRect) + } + case mPaletted: + img = image.NewPaletted(imgRect, d.palette) + case mNRGBA: + if d.bpp == 16 { + img = image.NewNRGBA64(imgRect) + } else { + img = image.NewNRGBA(imgRect) + } + case mRGB, mRGBA: + if d.bpp == 16 { + img = image.NewRGBA64(imgRect) + } else { + img = image.NewRGBA(imgRect) + } + } + + for i := 0; i < blocksAcross; i++ { + blkW := blockWidth + if !blockPadding && i == blocksAcross-1 && d.config.Width%blockWidth != 0 { + blkW = d.config.Width % blockWidth + } + for j := 0; j < blocksDown; j++ { + blkH := blockHeight + if !blockPadding && j == blocksDown-1 && d.config.Height%blockHeight != 0 { + blkH = d.config.Height % blockHeight + } + offset := int64(blockOffsets[j*blocksAcross+i]) + n := int64(blockCounts[j*blocksAcross+i]) + switch d.firstVal(tCompression) { + + // According to the spec, Compression does not have a default value, + // but some tools interpret a missing Compression value as none so we do + // the same. + case cNone, 0: + if b, ok := d.r.(*buffer); ok { + d.buf, err = b.Slice(int(offset), int(n)) + } else { + d.buf = make([]byte, n) + _, err = d.r.ReadAt(d.buf, offset) + } + case cLZW: + r := lzw.NewReader(io.NewSectionReader(d.r, offset, n), lzw.MSB, 8) + d.buf, err = ioutil.ReadAll(r) + r.Close() + case cDeflate, cDeflateOld: + var r io.ReadCloser + r, err = zlib.NewReader(io.NewSectionReader(d.r, offset, n)) + if err != nil { + return nil, err + } + d.buf, err = ioutil.ReadAll(r) + r.Close() + case cPackBits: + d.buf, err = unpackBits(io.NewSectionReader(d.r, offset, n)) + default: + err = UnsupportedError(fmt.Sprintf("compression value %d", d.firstVal(tCompression))) + } + if err != nil { + return nil, err + } + + xmin := i * blockWidth + ymin := j * blockHeight + xmax := xmin + blkW + ymax := ymin + blkH + err = d.decode(img, xmin, ymin, xmax, ymax) + if err != nil { + return nil, err + } + } + } + return +} + +func init() { + image.RegisterFormat("tiff", leHeader, Decode, DecodeConfig) + image.RegisterFormat("tiff", beHeader, Decode, DecodeConfig) +} diff --git a/src/golang.org/x/image/tiff/reader_test.go b/src/golang.org/x/image/tiff/reader_test.go new file mode 100644 index 0000000000..f5c02e6975 --- /dev/null +++ b/src/golang.org/x/image/tiff/reader_test.go @@ -0,0 +1,377 @@ +// 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. + +package tiff + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "errors" + "image" + "io/ioutil" + "os" + "strings" + "testing" + + _ "image/png" +) + +const testdataDir = "../testdata/" + +// Read makes *buffer implements io.Reader, so that we can pass one to Decode. +func (*buffer) Read([]byte) (int, error) { + panic("unimplemented") +} + +func load(name string) (image.Image, error) { + f, err := os.Open(testdataDir + name) + if err != nil { + return nil, err + } + defer f.Close() + img, _, err := image.Decode(f) + if err != nil { + return nil, err + } + return img, nil +} + +// TestNoRPS tests decoding an image that has no RowsPerStrip tag. The tag is +// mandatory according to the spec but some software omits it in the case of a +// single strip. +func TestNoRPS(t *testing.T) { + _, err := load("no_rps.tiff") + if err != nil { + t.Fatal(err) + } +} + +// TestNoCompression tests decoding an image that has no Compression tag. This +// tag is mandatory, but most tools interpret a missing value as no +// compression. +func TestNoCompression(t *testing.T) { + _, err := load("no_compress.tiff") + if err != nil { + t.Fatal(err) + } +} + +// TestUnpackBits tests the decoding of PackBits-encoded data. +func TestUnpackBits(t *testing.T) { + var unpackBitsTests = []struct { + compressed string + uncompressed string + }{{ + // Example data from Wikipedia. + "\xfe\xaa\x02\x80\x00\x2a\xfd\xaa\x03\x80\x00\x2a\x22\xf7\xaa", + "\xaa\xaa\xaa\x80\x00\x2a\xaa\xaa\xaa\xaa\x80\x00\x2a\x22\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa", + }} + for _, u := range unpackBitsTests { + buf, err := unpackBits(strings.NewReader(u.compressed)) + if err != nil { + t.Fatal(err) + } + if string(buf) != u.uncompressed { + t.Fatalf("unpackBits: want %x, got %x", u.uncompressed, buf) + } + } +} + +func TestShortBlockData(t *testing.T) { + b, err := ioutil.ReadFile("../testdata/bw-uncompressed.tiff") + if err != nil { + t.Fatal(err) + } + // The bw-uncompressed.tiff image is a 153x55 bi-level image. This is 1 bit + // per pixel, or 20 bytes per row, times 55 rows, or 1100 bytes of pixel + // data. 1100 in hex is 0x44c, or "\x4c\x04" in little-endian. We replace + // that byte count (StripByteCounts-tagged data) by something less than + // that, so that there is not enough pixel data. + old := []byte{0x4c, 0x04} + new := []byte{0x01, 0x01} + i := bytes.Index(b, old) + if i < 0 { + t.Fatal(`could not find "\x4c\x04" byte count`) + } + if bytes.Contains(b[i+len(old):], old) { + t.Fatal(`too many occurrences of "\x4c\x04"`) + } + b[i+0] = new[0] + b[i+1] = new[1] + if _, err = Decode(bytes.NewReader(b)); err == nil { + t.Fatal("got nil error, want non-nil") + } +} + +func TestDecodeInvalidDataType(t *testing.T) { + b, err := ioutil.ReadFile("../testdata/bw-uncompressed.tiff") + if err != nil { + t.Fatal(err) + } + + // off is the offset of the ImageWidth tag. It is the offset of the overall + // IFD block (0x00000454), plus 2 for the uint16 number of IFD entries, plus 12 + // to skip the first entry. + const off = 0x00000454 + 2 + 12*1 + + if v := binary.LittleEndian.Uint16(b[off : off+2]); v != tImageWidth { + t.Fatal(`could not find ImageWidth tag`) + } + binary.LittleEndian.PutUint16(b[off+2:], uint16(len(lengths))) // invalid datatype + + if _, err = Decode(bytes.NewReader(b)); err == nil { + t.Fatal("got nil error, want non-nil") + } +} + +func compare(t *testing.T, img0, img1 image.Image) { + b0 := img0.Bounds() + b1 := img1.Bounds() + if b0.Dx() != b1.Dx() || b0.Dy() != b1.Dy() { + t.Fatalf("wrong image size: want %s, got %s", b0, b1) + } + x1 := b1.Min.X - b0.Min.X + y1 := b1.Min.Y - b0.Min.Y + for y := b0.Min.Y; y < b0.Max.Y; y++ { + for x := b0.Min.X; x < b0.Max.X; x++ { + c0 := img0.At(x, y) + c1 := img1.At(x+x1, y+y1) + r0, g0, b0, a0 := c0.RGBA() + r1, g1, b1, a1 := c1.RGBA() + if r0 != r1 || g0 != g1 || b0 != b1 || a0 != a1 { + t.Fatalf("pixel at (%d, %d) has wrong color: want %v, got %v", x, y, c0, c1) + } + } + } +} + +// TestDecode tests that decoding a PNG image and a TIFF image result in the +// same pixel data. +func TestDecode(t *testing.T) { + img0, err := load("video-001.png") + if err != nil { + t.Fatal(err) + } + img1, err := load("video-001.tiff") + if err != nil { + t.Fatal(err) + } + img2, err := load("video-001-strip-64.tiff") + if err != nil { + t.Fatal(err) + } + img3, err := load("video-001-tile-64x64.tiff") + if err != nil { + t.Fatal(err) + } + img4, err := load("video-001-16bit.tiff") + if err != nil { + t.Fatal(err) + } + + compare(t, img0, img1) + compare(t, img0, img2) + compare(t, img0, img3) + compare(t, img0, img4) +} + +// TestDecodeLZW tests that decoding a PNG image and a LZW-compressed TIFF +// image result in the same pixel data. +func TestDecodeLZW(t *testing.T) { + img0, err := load("blue-purple-pink.png") + if err != nil { + t.Fatal(err) + } + img1, err := load("blue-purple-pink.lzwcompressed.tiff") + if err != nil { + t.Fatal(err) + } + + compare(t, img0, img1) +} + +// TestDecompress tests that decoding some TIFF images that use different +// compression formats result in the same pixel data. +func TestDecompress(t *testing.T) { + var decompressTests = []string{ + "bw-uncompressed.tiff", + "bw-deflate.tiff", + "bw-packbits.tiff", + } + var img0 image.Image + for _, name := range decompressTests { + img1, err := load(name) + if err != nil { + t.Fatalf("decoding %s: %v", name, err) + } + if img0 == nil { + img0 = img1 + continue + } + compare(t, img0, img1) + } +} + +func replace(src []byte, find, repl string) ([]byte, error) { + removeSpaces := func(r rune) rune { + if r != ' ' { + return r + } + return -1 + } + + f, err := hex.DecodeString(strings.Map(removeSpaces, find)) + if err != nil { + return nil, err + } + r, err := hex.DecodeString(strings.Map(removeSpaces, repl)) + if err != nil { + return nil, err + } + dst := bytes.Replace(src, f, r, 1) + if bytes.Equal(dst, src) { + return nil, errors.New("replacement failed") + } + return dst, nil +} + +// TestZeroBitsPerSample tests that an IFD with a bitsPerSample of 0 does not +// cause a crash. +// Issue 10711. +func TestZeroBitsPerSample(t *testing.T) { + b0, err := ioutil.ReadFile(testdataDir + "bw-deflate.tiff") + if err != nil { + t.Fatal(err) + } + + // Mutate the loaded image to have the problem. + // 02 01: tag number (tBitsPerSample) + // 03 00: data type (short, or uint16) + // 01 00 00 00: count + // ?? 00 00 00: value (1 -> 0) + b1, err := replace(b0, + "02 01 03 00 01 00 00 00 01 00 00 00", + "02 01 03 00 01 00 00 00 00 00 00 00", + ) + if err != nil { + t.Fatal(err) + } + + _, err = Decode(bytes.NewReader(b1)) + if err == nil { + t.Fatal("Decode with 0 bits per sample: got nil error, want non-nil") + } +} + +// TestTileTooBig tests that we do not panic when a tile is too big compared to +// the data available. +// Issue 10712 +func TestTileTooBig(t *testing.T) { + b0, err := ioutil.ReadFile(testdataDir + "video-001-tile-64x64.tiff") + if err != nil { + t.Fatal(err) + } + + // Mutate the loaded image to have the problem. + // + // 42 01: tag number (tTileWidth) + // 03 00: data type (short, or uint16) + // 01 00 00 00: count + // xx 00 00 00: value (0x40 -> 0x44: a wider tile consumes more data + // than is available) + b1, err := replace(b0, + "42 01 03 00 01 00 00 00 40 00 00 00", + "42 01 03 00 01 00 00 00 44 00 00 00", + ) + if err != nil { + t.Fatal(err) + } + + // Turn off the predictor, which makes it possible to hit the + // place with the defect. Without this patch to the image, we run + // out of data too early, and do not hit the part of the code where + // the original panic was. + // + // 3d 01: tag number (tPredictor) + // 03 00: data type (short, or uint16) + // 01 00 00 00: count + // xx 00 00 00: value (2 -> 1: 2 = horizontal, 1 = none) + b2, err := replace(b1, + "3d 01 03 00 01 00 00 00 02 00 00 00", + "3d 01 03 00 01 00 00 00 01 00 00 00", + ) + if err != nil { + t.Fatal(err) + } + + _, err = Decode(bytes.NewReader(b2)) + if err == nil { + t.Fatal("did not expect nil error") + } +} + +// TestZeroSizedImages tests that decoding does not panic when image dimensions +// are zero, and returns a zero-sized image instead. +// Issue 10393. +func TestZeroSizedImages(t *testing.T) { + testsizes := []struct { + w, h int + }{ + {0, 0}, + {1, 0}, + {0, 1}, + {1, 1}, + } + for _, r := range testsizes { + img := image.NewRGBA(image.Rect(0, 0, r.w, r.h)) + var buf bytes.Buffer + if err := Encode(&buf, img, nil); err != nil { + t.Errorf("encode w=%d h=%d: %v", r.w, r.h, err) + continue + } + if _, err := Decode(&buf); err != nil { + t.Errorf("decode w=%d h=%d: %v", r.w, r.h, err) + } + } +} + +// TestLargeIFDEntry tests that a large IFD entry does not cause Decode to +// panic. +// Issue 10596. +func TestLargeIFDEntry(t *testing.T) { + testdata := "II*\x00\x08\x00\x00\x00\f\x000000000000" + + "00000000000000000000" + + "00000000000000000000" + + "00000000000000000000" + + "00000000000000\x17\x01\x04\x00\x01\x00" + + "\x00\xc0000000000000000000" + + "00000000000000000000" + + "00000000000000000000" + + "000000" + _, err := Decode(strings.NewReader(testdata)) + if err == nil { + t.Fatal("Decode with large IFD entry: got nil error, want non-nil") + } +} + +// benchmarkDecode benchmarks the decoding of an image. +func benchmarkDecode(b *testing.B, filename string) { + b.StopTimer() + contents, err := ioutil.ReadFile(testdataDir + filename) + if err != nil { + b.Fatal(err) + } + r := &buffer{buf: contents} + b.StartTimer() + for i := 0; i < b.N; i++ { + _, err := Decode(r) + if err != nil { + b.Fatal("Decode:", err) + } + } +} + +func BenchmarkDecodeCompressed(b *testing.B) { benchmarkDecode(b, "video-001.tiff") } +func BenchmarkDecodeUncompressed(b *testing.B) { benchmarkDecode(b, "video-001-uncompressed.tiff") } diff --git a/src/golang.org/x/image/tiff/writer.go b/src/golang.org/x/image/tiff/writer.go new file mode 100644 index 0000000000..c8a01cea71 --- /dev/null +++ b/src/golang.org/x/image/tiff/writer.go @@ -0,0 +1,438 @@ +// Copyright 2012 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. + +package tiff + +import ( + "bytes" + "compress/zlib" + "encoding/binary" + "image" + "io" + "sort" +) + +// The TIFF format allows to choose the order of the different elements freely. +// The basic structure of a TIFF file written by this package is: +// +// 1. Header (8 bytes). +// 2. Image data. +// 3. Image File Directory (IFD). +// 4. "Pointer area" for larger entries in the IFD. + +// We only write little-endian TIFF files. +var enc = binary.LittleEndian + +// An ifdEntry is a single entry in an Image File Directory. +// A value of type dtRational is composed of two 32-bit values, +// thus data contains two uints (numerator and denominator) for a single number. +type ifdEntry struct { + tag int + datatype int + data []uint32 +} + +func (e ifdEntry) putData(p []byte) { + for _, d := range e.data { + switch e.datatype { + case dtByte, dtASCII: + p[0] = byte(d) + p = p[1:] + case dtShort: + enc.PutUint16(p, uint16(d)) + p = p[2:] + case dtLong, dtRational: + enc.PutUint32(p, uint32(d)) + p = p[4:] + } + } +} + +type byTag []ifdEntry + +func (d byTag) Len() int { return len(d) } +func (d byTag) Less(i, j int) bool { return d[i].tag < d[j].tag } +func (d byTag) Swap(i, j int) { d[i], d[j] = d[j], d[i] } + +func encodeGray(w io.Writer, pix []uint8, dx, dy, stride int, predictor bool) error { + if !predictor { + return writePix(w, pix, dy, dx, stride) + } + buf := make([]byte, dx) + for y := 0; y < dy; y++ { + min := y*stride + 0 + max := y*stride + dx + off := 0 + var v0 uint8 + for i := min; i < max; i++ { + v1 := pix[i] + buf[off] = v1 - v0 + v0 = v1 + off++ + } + if _, err := w.Write(buf); err != nil { + return err + } + } + return nil +} + +func encodeGray16(w io.Writer, pix []uint8, dx, dy, stride int, predictor bool) error { + buf := make([]byte, dx*2) + for y := 0; y < dy; y++ { + min := y*stride + 0 + max := y*stride + dx*2 + off := 0 + var v0 uint16 + for i := min; i < max; i += 2 { + // An image.Gray16's Pix is in big-endian order. + v1 := uint16(pix[i])<<8 | uint16(pix[i+1]) + if predictor { + v0, v1 = v1, v1-v0 + } + // We only write little-endian TIFF files. + buf[off+0] = byte(v1) + buf[off+1] = byte(v1 >> 8) + off += 2 + } + if _, err := w.Write(buf); err != nil { + return err + } + } + return nil +} + +func encodeRGBA(w io.Writer, pix []uint8, dx, dy, stride int, predictor bool) error { + if !predictor { + return writePix(w, pix, dy, dx*4, stride) + } + buf := make([]byte, dx*4) + for y := 0; y < dy; y++ { + min := y*stride + 0 + max := y*stride + dx*4 + off := 0 + var r0, g0, b0, a0 uint8 + for i := min; i < max; i += 4 { + r1, g1, b1, a1 := pix[i+0], pix[i+1], pix[i+2], pix[i+3] + buf[off+0] = r1 - r0 + buf[off+1] = g1 - g0 + buf[off+2] = b1 - b0 + buf[off+3] = a1 - a0 + off += 4 + r0, g0, b0, a0 = r1, g1, b1, a1 + } + if _, err := w.Write(buf); err != nil { + return err + } + } + return nil +} + +func encodeRGBA64(w io.Writer, pix []uint8, dx, dy, stride int, predictor bool) error { + buf := make([]byte, dx*8) + for y := 0; y < dy; y++ { + min := y*stride + 0 + max := y*stride + dx*8 + off := 0 + var r0, g0, b0, a0 uint16 + for i := min; i < max; i += 8 { + // An image.RGBA64's Pix is in big-endian order. + r1 := uint16(pix[i+0])<<8 | uint16(pix[i+1]) + g1 := uint16(pix[i+2])<<8 | uint16(pix[i+3]) + b1 := uint16(pix[i+4])<<8 | uint16(pix[i+5]) + a1 := uint16(pix[i+6])<<8 | uint16(pix[i+7]) + if predictor { + r0, r1 = r1, r1-r0 + g0, g1 = g1, g1-g0 + b0, b1 = b1, b1-b0 + a0, a1 = a1, a1-a0 + } + // We only write little-endian TIFF files. + buf[off+0] = byte(r1) + buf[off+1] = byte(r1 >> 8) + buf[off+2] = byte(g1) + buf[off+3] = byte(g1 >> 8) + buf[off+4] = byte(b1) + buf[off+5] = byte(b1 >> 8) + buf[off+6] = byte(a1) + buf[off+7] = byte(a1 >> 8) + off += 8 + } + if _, err := w.Write(buf); err != nil { + return err + } + } + return nil +} + +func encode(w io.Writer, m image.Image, predictor bool) error { + bounds := m.Bounds() + buf := make([]byte, 4*bounds.Dx()) + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + off := 0 + if predictor { + var r0, g0, b0, a0 uint8 + for x := bounds.Min.X; x < bounds.Max.X; x++ { + r, g, b, a := m.At(x, y).RGBA() + r1 := uint8(r >> 8) + g1 := uint8(g >> 8) + b1 := uint8(b >> 8) + a1 := uint8(a >> 8) + buf[off+0] = r1 - r0 + buf[off+1] = g1 - g0 + buf[off+2] = b1 - b0 + buf[off+3] = a1 - a0 + off += 4 + r0, g0, b0, a0 = r1, g1, b1, a1 + } + } else { + for x := bounds.Min.X; x < bounds.Max.X; x++ { + r, g, b, a := m.At(x, y).RGBA() + buf[off+0] = uint8(r >> 8) + buf[off+1] = uint8(g >> 8) + buf[off+2] = uint8(b >> 8) + buf[off+3] = uint8(a >> 8) + off += 4 + } + } + if _, err := w.Write(buf); err != nil { + return err + } + } + return nil +} + +// writePix writes the internal byte array of an image to w. It is less general +// but much faster then encode. writePix is used when pix directly +// corresponds to one of the TIFF image types. +func writePix(w io.Writer, pix []byte, nrows, length, stride int) error { + if length == stride { + _, err := w.Write(pix[:nrows*length]) + return err + } + for ; nrows > 0; nrows-- { + if _, err := w.Write(pix[:length]); err != nil { + return err + } + pix = pix[stride:] + } + return nil +} + +func writeIFD(w io.Writer, ifdOffset int, d []ifdEntry) error { + var buf [ifdLen]byte + // Make space for "pointer area" containing IFD entry data + // longer than 4 bytes. + parea := make([]byte, 1024) + pstart := ifdOffset + ifdLen*len(d) + 6 + var o int // Current offset in parea. + + // The IFD has to be written with the tags in ascending order. + sort.Sort(byTag(d)) + + // Write the number of entries in this IFD. + if err := binary.Write(w, enc, uint16(len(d))); err != nil { + return err + } + for _, ent := range d { + enc.PutUint16(buf[0:2], uint16(ent.tag)) + enc.PutUint16(buf[2:4], uint16(ent.datatype)) + count := uint32(len(ent.data)) + if ent.datatype == dtRational { + count /= 2 + } + enc.PutUint32(buf[4:8], count) + datalen := int(count * lengths[ent.datatype]) + if datalen <= 4 { + ent.putData(buf[8:12]) + } else { + if (o + datalen) > len(parea) { + newlen := len(parea) + 1024 + for (o + datalen) > newlen { + newlen += 1024 + } + newarea := make([]byte, newlen) + copy(newarea, parea) + parea = newarea + } + ent.putData(parea[o : o+datalen]) + enc.PutUint32(buf[8:12], uint32(pstart+o)) + o += datalen + } + if _, err := w.Write(buf[:]); err != nil { + return err + } + } + // The IFD ends with the offset of the next IFD in the file, + // or zero if it is the last one (page 14). + if err := binary.Write(w, enc, uint32(0)); err != nil { + return err + } + _, err := w.Write(parea[:o]) + return err +} + +// Options are the encoding parameters. +type Options struct { + // Compression is the type of compression used. + Compression CompressionType + // Predictor determines whether a differencing predictor is used; + // if true, instead of each pixel's color, the color difference to the + // preceding one is saved. This improves the compression for certain + // types of images and compressors. For example, it works well for + // photos with Deflate compression. + Predictor bool +} + +// Encode writes the image m to w. opt determines the options used for +// encoding, such as the compression type. If opt is nil, an uncompressed +// image is written. +func Encode(w io.Writer, m image.Image, opt *Options) error { + d := m.Bounds().Size() + + compression := uint32(cNone) + predictor := false + if opt != nil { + compression = opt.Compression.specValue() + // The predictor field is only used with LZW. See page 64 of the spec. + predictor = opt.Predictor && compression == cLZW + } + + _, err := io.WriteString(w, leHeader) + if err != nil { + return err + } + + // Compressed data is written into a buffer first, so that we + // know the compressed size. + var buf bytes.Buffer + // dst holds the destination for the pixel data of the image -- + // either w or a writer to buf. + var dst io.Writer + // imageLen is the length of the pixel data in bytes. + // The offset of the IFD is imageLen + 8 header bytes. + var imageLen int + + switch compression { + case cNone: + dst = w + // Write IFD offset before outputting pixel data. + switch m.(type) { + case *image.Paletted: + imageLen = d.X * d.Y * 1 + case *image.Gray: + imageLen = d.X * d.Y * 1 + case *image.Gray16: + imageLen = d.X * d.Y * 2 + case *image.RGBA64: + imageLen = d.X * d.Y * 8 + case *image.NRGBA64: + imageLen = d.X * d.Y * 8 + default: + imageLen = d.X * d.Y * 4 + } + err = binary.Write(w, enc, uint32(imageLen+8)) + if err != nil { + return err + } + case cDeflate: + dst = zlib.NewWriter(&buf) + } + + pr := uint32(prNone) + photometricInterpretation := uint32(pRGB) + samplesPerPixel := uint32(4) + bitsPerSample := []uint32{8, 8, 8, 8} + extraSamples := uint32(0) + colorMap := []uint32{} + + if predictor { + pr = prHorizontal + } + switch m := m.(type) { + case *image.Paletted: + photometricInterpretation = pPaletted + samplesPerPixel = 1 + bitsPerSample = []uint32{8} + colorMap = make([]uint32, 256*3) + for i := 0; i < 256 && i < len(m.Palette); i++ { + r, g, b, _ := m.Palette[i].RGBA() + colorMap[i+0*256] = uint32(r) + colorMap[i+1*256] = uint32(g) + colorMap[i+2*256] = uint32(b) + } + err = encodeGray(dst, m.Pix, d.X, d.Y, m.Stride, predictor) + case *image.Gray: + photometricInterpretation = pBlackIsZero + samplesPerPixel = 1 + bitsPerSample = []uint32{8} + err = encodeGray(dst, m.Pix, d.X, d.Y, m.Stride, predictor) + case *image.Gray16: + photometricInterpretation = pBlackIsZero + samplesPerPixel = 1 + bitsPerSample = []uint32{16} + err = encodeGray16(dst, m.Pix, d.X, d.Y, m.Stride, predictor) + case *image.NRGBA: + extraSamples = 2 // Unassociated alpha. + err = encodeRGBA(dst, m.Pix, d.X, d.Y, m.Stride, predictor) + case *image.NRGBA64: + extraSamples = 2 // Unassociated alpha. + bitsPerSample = []uint32{16, 16, 16, 16} + err = encodeRGBA64(dst, m.Pix, d.X, d.Y, m.Stride, predictor) + case *image.RGBA: + extraSamples = 1 // Associated alpha. + err = encodeRGBA(dst, m.Pix, d.X, d.Y, m.Stride, predictor) + case *image.RGBA64: + extraSamples = 1 // Associated alpha. + bitsPerSample = []uint32{16, 16, 16, 16} + err = encodeRGBA64(dst, m.Pix, d.X, d.Y, m.Stride, predictor) + default: + extraSamples = 1 // Associated alpha. + err = encode(dst, m, predictor) + } + if err != nil { + return err + } + + if compression != cNone { + if err = dst.(io.Closer).Close(); err != nil { + return err + } + imageLen = buf.Len() + if err = binary.Write(w, enc, uint32(imageLen+8)); err != nil { + return err + } + if _, err = buf.WriteTo(w); err != nil { + return err + } + } + + ifd := []ifdEntry{ + {tImageWidth, dtShort, []uint32{uint32(d.X)}}, + {tImageLength, dtShort, []uint32{uint32(d.Y)}}, + {tBitsPerSample, dtShort, bitsPerSample}, + {tCompression, dtShort, []uint32{compression}}, + {tPhotometricInterpretation, dtShort, []uint32{photometricInterpretation}}, + {tStripOffsets, dtLong, []uint32{8}}, + {tSamplesPerPixel, dtShort, []uint32{samplesPerPixel}}, + {tRowsPerStrip, dtShort, []uint32{uint32(d.Y)}}, + {tStripByteCounts, dtLong, []uint32{uint32(imageLen)}}, + // There is currently no support for storing the image + // resolution, so give a bogus value of 72x72 dpi. + {tXResolution, dtRational, []uint32{72, 1}}, + {tYResolution, dtRational, []uint32{72, 1}}, + {tResolutionUnit, dtShort, []uint32{resPerInch}}, + } + if pr != prNone { + ifd = append(ifd, ifdEntry{tPredictor, dtShort, []uint32{pr}}) + } + if len(colorMap) != 0 { + ifd = append(ifd, ifdEntry{tColorMap, dtShort, colorMap}) + } + if extraSamples > 0 { + ifd = append(ifd, ifdEntry{tExtraSamples, dtShort, []uint32{extraSamples}}) + } + + return writeIFD(w, imageLen+8, ifd) +} diff --git a/src/golang.org/x/image/tiff/writer_test.go b/src/golang.org/x/image/tiff/writer_test.go new file mode 100644 index 0000000000..c8fb7bf37e --- /dev/null +++ b/src/golang.org/x/image/tiff/writer_test.go @@ -0,0 +1,95 @@ +// Copyright 2012 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. + +package tiff + +import ( + "bytes" + "image" + "io/ioutil" + "os" + "testing" +) + +var roundtripTests = []struct { + filename string + opts *Options +}{ + {"video-001.tiff", nil}, + {"video-001-16bit.tiff", nil}, + {"video-001-gray.tiff", nil}, + {"video-001-gray-16bit.tiff", nil}, + {"video-001-paletted.tiff", nil}, + {"bw-packbits.tiff", nil}, + {"video-001.tiff", &Options{Predictor: true}}, + {"video-001.tiff", &Options{Compression: Deflate}}, + {"video-001.tiff", &Options{Predictor: true, Compression: Deflate}}, +} + +func openImage(filename string) (image.Image, error) { + f, err := os.Open(testdataDir + filename) + if err != nil { + return nil, err + } + defer f.Close() + return Decode(f) +} + +func TestRoundtrip(t *testing.T) { + for _, rt := range roundtripTests { + img, err := openImage(rt.filename) + if err != nil { + t.Fatal(err) + } + out := new(bytes.Buffer) + err = Encode(out, img, rt.opts) + if err != nil { + t.Fatal(err) + } + + img2, err := Decode(&buffer{buf: out.Bytes()}) + if err != nil { + t.Fatal(err) + } + compare(t, img, img2) + } +} + +// TestRoundtrip2 tests that encoding and decoding an image whose +// origin is not (0, 0) gives the same thing. +func TestRoundtrip2(t *testing.T) { + m0 := image.NewRGBA(image.Rect(3, 4, 9, 8)) + for i := range m0.Pix { + m0.Pix[i] = byte(i) + } + out := new(bytes.Buffer) + if err := Encode(out, m0, nil); err != nil { + t.Fatal(err) + } + m1, err := Decode(&buffer{buf: out.Bytes()}) + if err != nil { + t.Fatal(err) + } + compare(t, m0, m1) +} + +func benchmarkEncode(b *testing.B, name string, pixelSize int) { + img, err := openImage(name) + if err != nil { + b.Fatal(err) + } + s := img.Bounds().Size() + b.SetBytes(int64(s.X * s.Y * pixelSize)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Encode(ioutil.Discard, img, nil) + } +} + +func BenchmarkEncode(b *testing.B) { benchmarkEncode(b, "video-001.tiff", 4) } +func BenchmarkEncodePaletted(b *testing.B) { benchmarkEncode(b, "video-001-paletted.tiff", 1) } +func BenchmarkEncodeGray(b *testing.B) { benchmarkEncode(b, "video-001-gray.tiff", 1) } +func BenchmarkEncodeGray16(b *testing.B) { benchmarkEncode(b, "video-001-gray-16bit.tiff", 2) } +func BenchmarkEncodeRGBA(b *testing.B) { benchmarkEncode(b, "video-001.tiff", 4) } +func BenchmarkEncodeRGBA64(b *testing.B) { benchmarkEncode(b, "video-001-16bit.tiff", 8) } diff --git a/src/golang.org/x/image/vp8/decode.go b/src/golang.org/x/image/vp8/decode.go new file mode 100644 index 0000000000..1bb50284bb --- /dev/null +++ b/src/golang.org/x/image/vp8/decode.go @@ -0,0 +1,403 @@ +// 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. + +// Package vp8 implements a decoder for the VP8 lossy image format. +// +// The VP8 specification is RFC 6386. +package vp8 // import "golang.org/x/image/vp8" + +// This file implements the top-level decoding algorithm. + +import ( + "errors" + "image" + "io" +) + +// limitReader wraps an io.Reader to read at most n bytes from it. +type limitReader struct { + r io.Reader + n int +} + +// ReadFull reads exactly len(p) bytes into p. +func (r *limitReader) ReadFull(p []byte) error { + if len(p) > r.n { + return io.ErrUnexpectedEOF + } + n, err := io.ReadFull(r.r, p) + r.n -= n + return err +} + +// FrameHeader is a frame header, as specified in section 9.1. +type FrameHeader struct { + KeyFrame bool + VersionNumber uint8 + ShowFrame bool + FirstPartitionLen uint32 + Width int + Height int + XScale uint8 + YScale uint8 +} + +const ( + nSegment = 4 + nSegmentProb = 3 +) + +// segmentHeader holds segment-related header information. +type segmentHeader struct { + useSegment bool + updateMap bool + relativeDelta bool + quantizer [nSegment]int8 + filterStrength [nSegment]int8 + prob [nSegmentProb]uint8 +} + +const ( + nRefLFDelta = 4 + nModeLFDelta = 4 +) + +// filterHeader holds filter-related header information. +type filterHeader struct { + simple bool + level int8 + sharpness uint8 + useLFDelta bool + refLFDelta [nRefLFDelta]int8 + modeLFDelta [nModeLFDelta]int8 + perSegmentLevel [nSegment]int8 +} + +// mb is the per-macroblock decode state. A decoder maintains mbw+1 of these +// as it is decoding macroblocks left-to-right and top-to-bottom: mbw for the +// macroblocks in the row above, and one for the macroblock to the left. +type mb struct { + // pred is the predictor mode for the 4 bottom or right 4x4 luma regions. + pred [4]uint8 + // nzMask is a mask of 8 bits: 4 for the bottom or right 4x4 luma regions, + // and 2 + 2 for the bottom or right 4x4 chroma regions. A 1 bit indicates + // that that region has non-zero coefficients. + nzMask uint8 + // nzY16 is a 0/1 value that is 1 if the macroblock used Y16 prediction and + // had non-zero coefficients. + nzY16 uint8 +} + +// Decoder decodes VP8 bitstreams into frames. Decoding one frame consists of +// calling Init, DecodeFrameHeader and then DecodeFrame in that order. +// A Decoder can be re-used to decode multiple frames. +type Decoder struct { + // r is the input bitsream. + r limitReader + // scratch is a scratch buffer. + scratch [8]byte + // img is the YCbCr image to decode into. + img *image.YCbCr + // mbw and mbh are the number of 16x16 macroblocks wide and high the image is. + mbw, mbh int + // frameHeader is the frame header. When decoding multiple frames, + // frames that aren't key frames will inherit the Width, Height, + // XScale and YScale of the most recent key frame. + frameHeader FrameHeader + // Other headers. + segmentHeader segmentHeader + filterHeader filterHeader + // The image data is divided into a number of independent partitions. + // There is 1 "first partition" and between 1 and 8 "other partitions" + // for coefficient data. + fp partition + op [8]partition + nOP int + // Quantization factors. + quant [nSegment]quant + // DCT/WHT coefficient decoding probabilities. + tokenProb [nPlane][nBand][nContext][nProb]uint8 + useSkipProb bool + skipProb uint8 + // Loop filter parameters. + filterParams [nSegment][2]filterParam + perMBFilterParams []filterParam + + // The eight fields below relate to the current macroblock being decoded. + // + // Segment-based adjustments. + segment int + // Per-macroblock state for the macroblock immediately left of and those + // macroblocks immediately above the current macroblock. + leftMB mb + upMB []mb + // Bitmasks for which 4x4 regions of coeff contain non-zero coefficients. + nzDCMask, nzACMask uint32 + // Predictor modes. + usePredY16 bool // The libwebp C code calls this !is_i4x4_. + predY16 uint8 + predC8 uint8 + predY4 [4][4]uint8 + + // The two fields below form a workspace for reconstructing a macroblock. + // Their specific sizes are documented in reconstruct.go. + coeff [1*16*16 + 2*8*8 + 1*4*4]int16 + ybr [1 + 16 + 1 + 8][32]uint8 +} + +// NewDecoder returns a new Decoder. +func NewDecoder() *Decoder { + return &Decoder{} +} + +// Init initializes the decoder to read at most n bytes from r. +func (d *Decoder) Init(r io.Reader, n int) { + d.r = limitReader{r, n} +} + +// DecodeFrameHeader decodes the frame header. +func (d *Decoder) DecodeFrameHeader() (fh FrameHeader, err error) { + // All frame headers are at least 3 bytes long. + b := d.scratch[:3] + if err = d.r.ReadFull(b); err != nil { + return + } + d.frameHeader.KeyFrame = (b[0] & 1) == 0 + d.frameHeader.VersionNumber = (b[0] >> 1) & 7 + d.frameHeader.ShowFrame = (b[0]>>4)&1 == 1 + d.frameHeader.FirstPartitionLen = uint32(b[0])>>5 | uint32(b[1])<<3 | uint32(b[2])<<11 + if !d.frameHeader.KeyFrame { + return d.frameHeader, nil + } + // Frame headers for key frames are an additional 7 bytes long. + b = d.scratch[:7] + if err = d.r.ReadFull(b); err != nil { + return + } + // Check the magic sync code. + if b[0] != 0x9d || b[1] != 0x01 || b[2] != 0x2a { + err = errors.New("vp8: invalid format") + return + } + d.frameHeader.Width = int(b[4]&0x3f)<<8 | int(b[3]) + d.frameHeader.Height = int(b[6]&0x3f)<<8 | int(b[5]) + d.frameHeader.XScale = b[4] >> 6 + d.frameHeader.YScale = b[6] >> 6 + d.mbw = (d.frameHeader.Width + 0x0f) >> 4 + d.mbh = (d.frameHeader.Height + 0x0f) >> 4 + d.segmentHeader = segmentHeader{ + prob: [3]uint8{0xff, 0xff, 0xff}, + } + d.tokenProb = defaultTokenProb + d.segment = 0 + return d.frameHeader, nil +} + +// ensureImg ensures that d.img is large enough to hold the decoded frame. +func (d *Decoder) ensureImg() { + if d.img != nil { + p0, p1 := d.img.Rect.Min, d.img.Rect.Max + if p0.X == 0 && p0.Y == 0 && p1.X >= 16*d.mbw && p1.Y >= 16*d.mbh { + return + } + } + m := image.NewYCbCr(image.Rect(0, 0, 16*d.mbw, 16*d.mbh), image.YCbCrSubsampleRatio420) + d.img = m.SubImage(image.Rect(0, 0, d.frameHeader.Width, d.frameHeader.Height)).(*image.YCbCr) + d.perMBFilterParams = make([]filterParam, d.mbw*d.mbh) + d.upMB = make([]mb, d.mbw) +} + +// parseSegmentHeader parses the segment header, as specified in section 9.3. +func (d *Decoder) parseSegmentHeader() { + d.segmentHeader.useSegment = d.fp.readBit(uniformProb) + if !d.segmentHeader.useSegment { + d.segmentHeader.updateMap = false + return + } + d.segmentHeader.updateMap = d.fp.readBit(uniformProb) + if d.fp.readBit(uniformProb) { + d.segmentHeader.relativeDelta = !d.fp.readBit(uniformProb) + for i := range d.segmentHeader.quantizer { + d.segmentHeader.quantizer[i] = int8(d.fp.readOptionalInt(uniformProb, 7)) + } + for i := range d.segmentHeader.filterStrength { + d.segmentHeader.filterStrength[i] = int8(d.fp.readOptionalInt(uniformProb, 6)) + } + } + if !d.segmentHeader.updateMap { + return + } + for i := range d.segmentHeader.prob { + if d.fp.readBit(uniformProb) { + d.segmentHeader.prob[i] = uint8(d.fp.readUint(uniformProb, 8)) + } else { + d.segmentHeader.prob[i] = 0xff + } + } +} + +// parseFilterHeader parses the filter header, as specified in section 9.4. +func (d *Decoder) parseFilterHeader() { + d.filterHeader.simple = d.fp.readBit(uniformProb) + d.filterHeader.level = int8(d.fp.readUint(uniformProb, 6)) + d.filterHeader.sharpness = uint8(d.fp.readUint(uniformProb, 3)) + d.filterHeader.useLFDelta = d.fp.readBit(uniformProb) + if d.filterHeader.useLFDelta && d.fp.readBit(uniformProb) { + for i := range d.filterHeader.refLFDelta { + d.filterHeader.refLFDelta[i] = int8(d.fp.readOptionalInt(uniformProb, 6)) + } + for i := range d.filterHeader.modeLFDelta { + d.filterHeader.modeLFDelta[i] = int8(d.fp.readOptionalInt(uniformProb, 6)) + } + } + if d.filterHeader.level == 0 { + return + } + if d.segmentHeader.useSegment { + for i := range d.filterHeader.perSegmentLevel { + strength := d.segmentHeader.filterStrength[i] + if d.segmentHeader.relativeDelta { + strength += d.filterHeader.level + } + d.filterHeader.perSegmentLevel[i] = strength + } + } else { + d.filterHeader.perSegmentLevel[0] = d.filterHeader.level + } + d.computeFilterParams() +} + +// parseOtherPartitions parses the other partitions, as specified in section 9.5. +func (d *Decoder) parseOtherPartitions() error { + const maxNOP = 1 << 3 + var partLens [maxNOP]int + d.nOP = 1 << d.fp.readUint(uniformProb, 2) + + // The final partition length is implied by the the remaining chunk data + // (d.r.n) and the other d.nOP-1 partition lengths. Those d.nOP-1 partition + // lengths are stored as 24-bit uints, i.e. up to 16 MiB per partition. + n := 3 * (d.nOP - 1) + partLens[d.nOP-1] = d.r.n - n + if partLens[d.nOP-1] < 0 { + return io.ErrUnexpectedEOF + } + if n > 0 { + buf := make([]byte, n) + if err := d.r.ReadFull(buf); err != nil { + return err + } + for i := 0; i < d.nOP-1; i++ { + pl := int(buf[3*i+0]) | int(buf[3*i+1])<<8 | int(buf[3*i+2])<<16 + if pl > partLens[d.nOP-1] { + return io.ErrUnexpectedEOF + } + partLens[i] = pl + partLens[d.nOP-1] -= pl + } + } + + // We check if the final partition length can also fit into a 24-bit uint. + // Strictly speaking, this isn't part of the spec, but it guards against a + // malicious WEBP image that is too large to ReadFull the encoded DCT + // coefficients into memory, whether that's because the actual WEBP file is + // too large, or whether its RIFF metadata lists too large a chunk. + if 1<<24 <= partLens[d.nOP-1] { + return errors.New("vp8: too much data to decode") + } + + buf := make([]byte, d.r.n) + if err := d.r.ReadFull(buf); err != nil { + return err + } + for i, pl := range partLens { + if i == d.nOP { + break + } + d.op[i].init(buf[:pl]) + buf = buf[pl:] + } + return nil +} + +// parseOtherHeaders parses header information other than the frame header. +func (d *Decoder) parseOtherHeaders() error { + // Initialize and parse the first partition. + firstPartition := make([]byte, d.frameHeader.FirstPartitionLen) + if err := d.r.ReadFull(firstPartition); err != nil { + return err + } + d.fp.init(firstPartition) + if d.frameHeader.KeyFrame { + // Read and ignore the color space and pixel clamp values. They are + // specified in section 9.2, but are unimplemented. + d.fp.readBit(uniformProb) + d.fp.readBit(uniformProb) + } + d.parseSegmentHeader() + d.parseFilterHeader() + if err := d.parseOtherPartitions(); err != nil { + return err + } + d.parseQuant() + if !d.frameHeader.KeyFrame { + // Golden and AltRef frames are specified in section 9.7. + // TODO(nigeltao): implement. Note that they are only used for video, not still images. + return errors.New("vp8: Golden / AltRef frames are not implemented") + } + // Read and ignore the refreshLastFrameBuffer bit, specified in section 9.8. + // It applies only to video, and not still images. + d.fp.readBit(uniformProb) + d.parseTokenProb() + d.useSkipProb = d.fp.readBit(uniformProb) + if d.useSkipProb { + d.skipProb = uint8(d.fp.readUint(uniformProb, 8)) + } + if d.fp.unexpectedEOF { + return io.ErrUnexpectedEOF + } + return nil +} + +// DecodeFrame decodes the frame and returns it as an YCbCr image. +// The image's contents are valid up until the next call to Decoder.Init. +func (d *Decoder) DecodeFrame() (*image.YCbCr, error) { + d.ensureImg() + if err := d.parseOtherHeaders(); err != nil { + return nil, err + } + // Reconstruct the rows. + for mbx := 0; mbx < d.mbw; mbx++ { + d.upMB[mbx] = mb{} + } + for mby := 0; mby < d.mbh; mby++ { + d.leftMB = mb{} + for mbx := 0; mbx < d.mbw; mbx++ { + skip := d.reconstruct(mbx, mby) + fs := d.filterParams[d.segment][btou(!d.usePredY16)] + fs.inner = fs.inner || !skip + d.perMBFilterParams[d.mbw*mby+mbx] = fs + } + } + if d.fp.unexpectedEOF { + return nil, io.ErrUnexpectedEOF + } + for i := 0; i < d.nOP; i++ { + if d.op[i].unexpectedEOF { + return nil, io.ErrUnexpectedEOF + } + } + // Apply the loop filter. + // + // Even if we are using per-segment levels, section 15 says that "loop + // filtering must be skipped entirely if loop_filter_level at either the + // frame header level or macroblock override level is 0". + if d.filterHeader.level != 0 { + if d.filterHeader.simple { + d.simpleFilter() + } else { + d.normalFilter() + } + } + return d.img, nil +} diff --git a/src/golang.org/x/image/vp8/filter.go b/src/golang.org/x/image/vp8/filter.go new file mode 100644 index 0000000000..e34a811b1c --- /dev/null +++ b/src/golang.org/x/image/vp8/filter.go @@ -0,0 +1,273 @@ +// Copyright 2014 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. + +package vp8 + +// filter2 modifies a 2-pixel wide or 2-pixel high band along an edge. +func filter2(pix []byte, level, index, iStep, jStep int) { + for n := 16; n > 0; n, index = n-1, index+iStep { + p1 := int(pix[index-2*jStep]) + p0 := int(pix[index-1*jStep]) + q0 := int(pix[index+0*jStep]) + q1 := int(pix[index+1*jStep]) + if abs(p0-q0)<<1+abs(p1-q1)>>1 > level { + continue + } + a := 3*(q0-p0) + clamp127(p1-q1) + a1 := clamp15((a + 4) >> 3) + a2 := clamp15((a + 3) >> 3) + pix[index-1*jStep] = clamp255(p0 + a2) + pix[index+0*jStep] = clamp255(q0 - a1) + } +} + +// filter246 modifies a 2-, 4- or 6-pixel wide or high band along an edge. +func filter246(pix []byte, n, level, ilevel, hlevel, index, iStep, jStep int, fourNotSix bool) { + for ; n > 0; n, index = n-1, index+iStep { + p3 := int(pix[index-4*jStep]) + p2 := int(pix[index-3*jStep]) + p1 := int(pix[index-2*jStep]) + p0 := int(pix[index-1*jStep]) + q0 := int(pix[index+0*jStep]) + q1 := int(pix[index+1*jStep]) + q2 := int(pix[index+2*jStep]) + q3 := int(pix[index+3*jStep]) + if abs(p0-q0)<<1+abs(p1-q1)>>1 > level { + continue + } + if abs(p3-p2) > ilevel || + abs(p2-p1) > ilevel || + abs(p1-p0) > ilevel || + abs(q1-q0) > ilevel || + abs(q2-q1) > ilevel || + abs(q3-q2) > ilevel { + continue + } + if abs(p1-p0) > hlevel || abs(q1-q0) > hlevel { + // Filter 2 pixels. + a := 3*(q0-p0) + clamp127(p1-q1) + a1 := clamp15((a + 4) >> 3) + a2 := clamp15((a + 3) >> 3) + pix[index-1*jStep] = clamp255(p0 + a2) + pix[index+0*jStep] = clamp255(q0 - a1) + } else if fourNotSix { + // Filter 4 pixels. + a := 3 * (q0 - p0) + a1 := clamp15((a + 4) >> 3) + a2 := clamp15((a + 3) >> 3) + a3 := (a1 + 1) >> 1 + pix[index-2*jStep] = clamp255(p1 + a3) + pix[index-1*jStep] = clamp255(p0 + a2) + pix[index+0*jStep] = clamp255(q0 - a1) + pix[index+1*jStep] = clamp255(q1 - a3) + } else { + // Filter 6 pixels. + a := clamp127(3*(q0-p0) + clamp127(p1-q1)) + a1 := (27*a + 63) >> 7 + a2 := (18*a + 63) >> 7 + a3 := (9*a + 63) >> 7 + pix[index-3*jStep] = clamp255(p2 + a3) + pix[index-2*jStep] = clamp255(p1 + a2) + pix[index-1*jStep] = clamp255(p0 + a1) + pix[index+0*jStep] = clamp255(q0 - a1) + pix[index+1*jStep] = clamp255(q1 - a2) + pix[index+2*jStep] = clamp255(q2 - a3) + } + } +} + +// simpleFilter implements the simple filter, as specified in section 15.2. +func (d *Decoder) simpleFilter() { + for mby := 0; mby < d.mbh; mby++ { + for mbx := 0; mbx < d.mbw; mbx++ { + f := d.perMBFilterParams[d.mbw*mby+mbx] + if f.level == 0 { + continue + } + l := int(f.level) + yIndex := (mby*d.img.YStride + mbx) * 16 + if mbx > 0 { + filter2(d.img.Y, l+4, yIndex, d.img.YStride, 1) + } + if f.inner { + filter2(d.img.Y, l, yIndex+0x4, d.img.YStride, 1) + filter2(d.img.Y, l, yIndex+0x8, d.img.YStride, 1) + filter2(d.img.Y, l, yIndex+0xc, d.img.YStride, 1) + } + if mby > 0 { + filter2(d.img.Y, l+4, yIndex, 1, d.img.YStride) + } + if f.inner { + filter2(d.img.Y, l, yIndex+d.img.YStride*0x4, 1, d.img.YStride) + filter2(d.img.Y, l, yIndex+d.img.YStride*0x8, 1, d.img.YStride) + filter2(d.img.Y, l, yIndex+d.img.YStride*0xc, 1, d.img.YStride) + } + } + } +} + +// normalFilter implements the normal filter, as specified in section 15.3. +func (d *Decoder) normalFilter() { + for mby := 0; mby < d.mbh; mby++ { + for mbx := 0; mbx < d.mbw; mbx++ { + f := d.perMBFilterParams[d.mbw*mby+mbx] + if f.level == 0 { + continue + } + l, il, hl := int(f.level), int(f.ilevel), int(f.hlevel) + yIndex := (mby*d.img.YStride + mbx) * 16 + cIndex := (mby*d.img.CStride + mbx) * 8 + if mbx > 0 { + filter246(d.img.Y, 16, l+4, il, hl, yIndex, d.img.YStride, 1, false) + filter246(d.img.Cb, 8, l+4, il, hl, cIndex, d.img.CStride, 1, false) + filter246(d.img.Cr, 8, l+4, il, hl, cIndex, d.img.CStride, 1, false) + } + if f.inner { + filter246(d.img.Y, 16, l, il, hl, yIndex+0x4, d.img.YStride, 1, true) + filter246(d.img.Y, 16, l, il, hl, yIndex+0x8, d.img.YStride, 1, true) + filter246(d.img.Y, 16, l, il, hl, yIndex+0xc, d.img.YStride, 1, true) + filter246(d.img.Cb, 8, l, il, hl, cIndex+0x4, d.img.CStride, 1, true) + filter246(d.img.Cr, 8, l, il, hl, cIndex+0x4, d.img.CStride, 1, true) + } + if mby > 0 { + filter246(d.img.Y, 16, l+4, il, hl, yIndex, 1, d.img.YStride, false) + filter246(d.img.Cb, 8, l+4, il, hl, cIndex, 1, d.img.CStride, false) + filter246(d.img.Cr, 8, l+4, il, hl, cIndex, 1, d.img.CStride, false) + } + if f.inner { + filter246(d.img.Y, 16, l, il, hl, yIndex+d.img.YStride*0x4, 1, d.img.YStride, true) + filter246(d.img.Y, 16, l, il, hl, yIndex+d.img.YStride*0x8, 1, d.img.YStride, true) + filter246(d.img.Y, 16, l, il, hl, yIndex+d.img.YStride*0xc, 1, d.img.YStride, true) + filter246(d.img.Cb, 8, l, il, hl, cIndex+d.img.CStride*0x4, 1, d.img.CStride, true) + filter246(d.img.Cr, 8, l, il, hl, cIndex+d.img.CStride*0x4, 1, d.img.CStride, true) + } + } + } +} + +// filterParam holds the loop filter parameters for a macroblock. +type filterParam struct { + // The first three fields are thresholds used by the loop filter to smooth + // over the edges and interior of a macroblock. level is used by both the + // simple and normal filters. The inner level and high edge variance level + // are only used by the normal filter. + level, ilevel, hlevel uint8 + // inner is whether the inner loop filter cannot be optimized out as a + // no-op for this particular macroblock. + inner bool +} + +// computeFilterParams computes the loop filter parameters, as specified in +// section 15.4. +func (d *Decoder) computeFilterParams() { + for i := range d.filterParams { + baseLevel := d.filterHeader.level + if d.segmentHeader.useSegment { + baseLevel = d.segmentHeader.filterStrength[i] + if d.segmentHeader.relativeDelta { + baseLevel += d.filterHeader.level + } + } + + for j := range d.filterParams[i] { + p := &d.filterParams[i][j] + p.inner = j != 0 + level := baseLevel + if d.filterHeader.useLFDelta { + // The libwebp C code has a "TODO: only CURRENT is handled for now." + level += d.filterHeader.refLFDelta[0] + if j != 0 { + level += d.filterHeader.modeLFDelta[0] + } + } + if level <= 0 { + p.level = 0 + continue + } + if level > 63 { + level = 63 + } + ilevel := level + if d.filterHeader.sharpness > 0 { + if d.filterHeader.sharpness > 4 { + ilevel >>= 2 + } else { + ilevel >>= 1 + } + if x := int8(9 - d.filterHeader.sharpness); ilevel > x { + ilevel = x + } + } + if ilevel < 1 { + ilevel = 1 + } + p.ilevel = uint8(ilevel) + p.level = uint8(2*level + ilevel) + if d.frameHeader.KeyFrame { + if level < 15 { + p.hlevel = 0 + } else if level < 40 { + p.hlevel = 1 + } else { + p.hlevel = 2 + } + } else { + if level < 15 { + p.hlevel = 0 + } else if level < 20 { + p.hlevel = 1 + } else if level < 40 { + p.hlevel = 2 + } else { + p.hlevel = 3 + } + } + } + } +} + +// intSize is either 32 or 64. +const intSize = 32 << (^uint(0) >> 63) + +func abs(x int) int { + // m := -1 if x < 0. m := 0 otherwise. + m := x >> (intSize - 1) + + // In two's complement representation, the negative number + // of any number (except the smallest one) can be computed + // by flipping all the bits and add 1. This is faster than + // code with a branch. + // See Hacker's Delight, section 2-4. + return (x ^ m) - m +} + +func clamp15(x int) int { + if x < -16 { + return -16 + } + if x > 15 { + return 15 + } + return x +} + +func clamp127(x int) int { + if x < -128 { + return -128 + } + if x > 127 { + return 127 + } + return x +} + +func clamp255(x int) uint8 { + if x < 0 { + return 0 + } + if x > 255 { + return 255 + } + return uint8(x) +} diff --git a/src/golang.org/x/image/vp8/idct.go b/src/golang.org/x/image/vp8/idct.go new file mode 100644 index 0000000000..929af2cc9b --- /dev/null +++ b/src/golang.org/x/image/vp8/idct.go @@ -0,0 +1,98 @@ +// 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. + +package vp8 + +// This file implements the inverse Discrete Cosine Transform and the inverse +// Walsh Hadamard Transform (WHT), as specified in sections 14.3 and 14.4. + +func clip8(i int32) uint8 { + if i < 0 { + return 0 + } + if i > 255 { + return 255 + } + return uint8(i) +} + +func (z *Decoder) inverseDCT4(y, x, coeffBase int) { + const ( + c1 = 85627 // 65536 * cos(pi/8) * sqrt(2). + c2 = 35468 // 65536 * sin(pi/8) * sqrt(2). + ) + var m [4][4]int32 + for i := 0; i < 4; i++ { + a := int32(z.coeff[coeffBase+0]) + int32(z.coeff[coeffBase+8]) + b := int32(z.coeff[coeffBase+0]) - int32(z.coeff[coeffBase+8]) + c := (int32(z.coeff[coeffBase+4])*c2)>>16 - (int32(z.coeff[coeffBase+12])*c1)>>16 + d := (int32(z.coeff[coeffBase+4])*c1)>>16 + (int32(z.coeff[coeffBase+12])*c2)>>16 + m[i][0] = a + d + m[i][1] = b + c + m[i][2] = b - c + m[i][3] = a - d + coeffBase++ + } + for j := 0; j < 4; j++ { + dc := m[0][j] + 4 + a := dc + m[2][j] + b := dc - m[2][j] + c := (m[1][j]*c2)>>16 - (m[3][j]*c1)>>16 + d := (m[1][j]*c1)>>16 + (m[3][j]*c2)>>16 + z.ybr[y+j][x+0] = clip8(int32(z.ybr[y+j][x+0]) + (a+d)>>3) + z.ybr[y+j][x+1] = clip8(int32(z.ybr[y+j][x+1]) + (b+c)>>3) + z.ybr[y+j][x+2] = clip8(int32(z.ybr[y+j][x+2]) + (b-c)>>3) + z.ybr[y+j][x+3] = clip8(int32(z.ybr[y+j][x+3]) + (a-d)>>3) + } +} + +func (z *Decoder) inverseDCT4DCOnly(y, x, coeffBase int) { + dc := (int32(z.coeff[coeffBase+0]) + 4) >> 3 + for j := 0; j < 4; j++ { + for i := 0; i < 4; i++ { + z.ybr[y+j][x+i] = clip8(int32(z.ybr[y+j][x+i]) + dc) + } + } +} + +func (z *Decoder) inverseDCT8(y, x, coeffBase int) { + z.inverseDCT4(y+0, x+0, coeffBase+0*16) + z.inverseDCT4(y+0, x+4, coeffBase+1*16) + z.inverseDCT4(y+4, x+0, coeffBase+2*16) + z.inverseDCT4(y+4, x+4, coeffBase+3*16) +} + +func (z *Decoder) inverseDCT8DCOnly(y, x, coeffBase int) { + z.inverseDCT4DCOnly(y+0, x+0, coeffBase+0*16) + z.inverseDCT4DCOnly(y+0, x+4, coeffBase+1*16) + z.inverseDCT4DCOnly(y+4, x+0, coeffBase+2*16) + z.inverseDCT4DCOnly(y+4, x+4, coeffBase+3*16) +} + +func (d *Decoder) inverseWHT16() { + var m [16]int32 + for i := 0; i < 4; i++ { + a0 := int32(d.coeff[384+0+i]) + int32(d.coeff[384+12+i]) + a1 := int32(d.coeff[384+4+i]) + int32(d.coeff[384+8+i]) + a2 := int32(d.coeff[384+4+i]) - int32(d.coeff[384+8+i]) + a3 := int32(d.coeff[384+0+i]) - int32(d.coeff[384+12+i]) + m[0+i] = a0 + a1 + m[8+i] = a0 - a1 + m[4+i] = a3 + a2 + m[12+i] = a3 - a2 + } + out := 0 + for i := 0; i < 4; i++ { + dc := m[0+i*4] + 3 + a0 := dc + m[3+i*4] + a1 := m[1+i*4] + m[2+i*4] + a2 := m[1+i*4] - m[2+i*4] + a3 := dc - m[3+i*4] + d.coeff[out+0] = int16((a0 + a1) >> 3) + d.coeff[out+16] = int16((a3 + a2) >> 3) + d.coeff[out+32] = int16((a0 - a1) >> 3) + d.coeff[out+48] = int16((a3 - a2) >> 3) + out += 64 + } +} diff --git a/src/golang.org/x/image/vp8/partition.go b/src/golang.org/x/image/vp8/partition.go new file mode 100644 index 0000000000..72288bdeb8 --- /dev/null +++ b/src/golang.org/x/image/vp8/partition.go @@ -0,0 +1,129 @@ +// 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. + +package vp8 + +// Each VP8 frame consists of between 2 and 9 bitstream partitions. +// Each partition is byte-aligned and is independently arithmetic-encoded. +// +// This file implements decoding a partition's bitstream, as specified in +// chapter 7. The implementation follows libwebp's approach instead of the +// specification's reference C implementation. For example, we use a look-up +// table instead of a for loop to recalibrate the encoded range. + +var ( + lutShift = [127]uint8{ + 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + } + lutRangeM1 = [127]uint8{ + 127, + 127, 191, + 127, 159, 191, 223, + 127, 143, 159, 175, 191, 207, 223, 239, + 127, 135, 143, 151, 159, 167, 175, 183, 191, 199, 207, 215, 223, 231, 239, 247, + 127, 131, 135, 139, 143, 147, 151, 155, 159, 163, 167, 171, 175, 179, 183, 187, + 191, 195, 199, 203, 207, 211, 215, 219, 223, 227, 231, 235, 239, 243, 247, 251, + 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, + 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, + 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, + 223, 225, 227, 229, 231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, + } +) + +// uniformProb represents a 50% probability that the next bit is 0. +const uniformProb = 128 + +// partition holds arithmetic-coded bits. +type partition struct { + // buf is the input bytes. + buf []byte + // r is how many of buf's bytes have been consumed. + r int + // rangeM1 is range minus 1, where range is in the arithmetic coding sense, + // not the Go language sense. + rangeM1 uint32 + // bits and nBits hold those bits shifted out of buf but not yet consumed. + bits uint32 + nBits uint8 + // unexpectedEOF tells whether we tried to read past buf. + unexpectedEOF bool +} + +// init initializes the partition. +func (p *partition) init(buf []byte) { + p.buf = buf + p.r = 0 + p.rangeM1 = 254 + p.bits = 0 + p.nBits = 0 + p.unexpectedEOF = false +} + +// readBit returns the next bit. +func (p *partition) readBit(prob uint8) bool { + if p.nBits < 8 { + if p.r >= len(p.buf) { + p.unexpectedEOF = true + return false + } + // Expression split for 386 compiler. + x := uint32(p.buf[p.r]) + p.bits |= x << (8 - p.nBits) + p.r++ + p.nBits += 8 + } + split := (p.rangeM1*uint32(prob))>>8 + 1 + bit := p.bits >= split<<8 + if bit { + p.rangeM1 -= split + p.bits -= split << 8 + } else { + p.rangeM1 = split - 1 + } + if p.rangeM1 < 127 { + shift := lutShift[p.rangeM1] + p.rangeM1 = uint32(lutRangeM1[p.rangeM1]) + p.bits <<= shift + p.nBits -= shift + } + return bit +} + +// readUint returns the next n-bit unsigned integer. +func (p *partition) readUint(prob, n uint8) uint32 { + var u uint32 + for n > 0 { + n-- + if p.readBit(prob) { + u |= 1 << n + } + } + return u +} + +// readInt returns the next n-bit signed integer. +func (p *partition) readInt(prob, n uint8) int32 { + u := p.readUint(prob, n) + b := p.readBit(prob) + if b { + return -int32(u) + } + return int32(u) +} + +// readOptionalInt returns the next n-bit signed integer in an encoding +// where the likely result is zero. +func (p *partition) readOptionalInt(prob, n uint8) int32 { + if !p.readBit(prob) { + return 0 + } + return p.readInt(prob, n) +} diff --git a/src/golang.org/x/image/vp8/pred.go b/src/golang.org/x/image/vp8/pred.go new file mode 100644 index 0000000000..58c2689ead --- /dev/null +++ b/src/golang.org/x/image/vp8/pred.go @@ -0,0 +1,201 @@ +// 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. + +package vp8 + +// This file implements parsing the predictor modes, as specified in chapter +// 11. + +func (d *Decoder) parsePredModeY16(mbx int) { + var p uint8 + if !d.fp.readBit(156) { + if !d.fp.readBit(163) { + p = predDC + } else { + p = predVE + } + } else if !d.fp.readBit(128) { + p = predHE + } else { + p = predTM + } + for i := 0; i < 4; i++ { + d.upMB[mbx].pred[i] = p + d.leftMB.pred[i] = p + } + d.predY16 = p +} + +func (d *Decoder) parsePredModeC8() { + if !d.fp.readBit(142) { + d.predC8 = predDC + } else if !d.fp.readBit(114) { + d.predC8 = predVE + } else if !d.fp.readBit(183) { + d.predC8 = predHE + } else { + d.predC8 = predTM + } +} + +func (d *Decoder) parsePredModeY4(mbx int) { + for j := 0; j < 4; j++ { + p := d.leftMB.pred[j] + for i := 0; i < 4; i++ { + prob := &predProb[d.upMB[mbx].pred[i]][p] + if !d.fp.readBit(prob[0]) { + p = predDC + } else if !d.fp.readBit(prob[1]) { + p = predTM + } else if !d.fp.readBit(prob[2]) { + p = predVE + } else if !d.fp.readBit(prob[3]) { + if !d.fp.readBit(prob[4]) { + p = predHE + } else if !d.fp.readBit(prob[5]) { + p = predRD + } else { + p = predVR + } + } else if !d.fp.readBit(prob[6]) { + p = predLD + } else if !d.fp.readBit(prob[7]) { + p = predVL + } else if !d.fp.readBit(prob[8]) { + p = predHD + } else { + p = predHU + } + d.predY4[j][i] = p + d.upMB[mbx].pred[i] = p + } + d.leftMB.pred[j] = p + } +} + +// predProb are the probabilities to decode a 4x4 region's predictor mode given +// the predictor modes of the regions above and left of it. +// These values are specified in section 11.5. +var predProb = [nPred][nPred][9]uint8{ + { + {231, 120, 48, 89, 115, 113, 120, 152, 112}, + {152, 179, 64, 126, 170, 118, 46, 70, 95}, + {175, 69, 143, 80, 85, 82, 72, 155, 103}, + {56, 58, 10, 171, 218, 189, 17, 13, 152}, + {114, 26, 17, 163, 44, 195, 21, 10, 173}, + {121, 24, 80, 195, 26, 62, 44, 64, 85}, + {144, 71, 10, 38, 171, 213, 144, 34, 26}, + {170, 46, 55, 19, 136, 160, 33, 206, 71}, + {63, 20, 8, 114, 114, 208, 12, 9, 226}, + {81, 40, 11, 96, 182, 84, 29, 16, 36}, + }, + { + {134, 183, 89, 137, 98, 101, 106, 165, 148}, + {72, 187, 100, 130, 157, 111, 32, 75, 80}, + {66, 102, 167, 99, 74, 62, 40, 234, 128}, + {41, 53, 9, 178, 241, 141, 26, 8, 107}, + {74, 43, 26, 146, 73, 166, 49, 23, 157}, + {65, 38, 105, 160, 51, 52, 31, 115, 128}, + {104, 79, 12, 27, 217, 255, 87, 17, 7}, + {87, 68, 71, 44, 114, 51, 15, 186, 23}, + {47, 41, 14, 110, 182, 183, 21, 17, 194}, + {66, 45, 25, 102, 197, 189, 23, 18, 22}, + }, + { + {88, 88, 147, 150, 42, 46, 45, 196, 205}, + {43, 97, 183, 117, 85, 38, 35, 179, 61}, + {39, 53, 200, 87, 26, 21, 43, 232, 171}, + {56, 34, 51, 104, 114, 102, 29, 93, 77}, + {39, 28, 85, 171, 58, 165, 90, 98, 64}, + {34, 22, 116, 206, 23, 34, 43, 166, 73}, + {107, 54, 32, 26, 51, 1, 81, 43, 31}, + {68, 25, 106, 22, 64, 171, 36, 225, 114}, + {34, 19, 21, 102, 132, 188, 16, 76, 124}, + {62, 18, 78, 95, 85, 57, 50, 48, 51}, + }, + { + {193, 101, 35, 159, 215, 111, 89, 46, 111}, + {60, 148, 31, 172, 219, 228, 21, 18, 111}, + {112, 113, 77, 85, 179, 255, 38, 120, 114}, + {40, 42, 1, 196, 245, 209, 10, 25, 109}, + {88, 43, 29, 140, 166, 213, 37, 43, 154}, + {61, 63, 30, 155, 67, 45, 68, 1, 209}, + {100, 80, 8, 43, 154, 1, 51, 26, 71}, + {142, 78, 78, 16, 255, 128, 34, 197, 171}, + {41, 40, 5, 102, 211, 183, 4, 1, 221}, + {51, 50, 17, 168, 209, 192, 23, 25, 82}, + }, + { + {138, 31, 36, 171, 27, 166, 38, 44, 229}, + {67, 87, 58, 169, 82, 115, 26, 59, 179}, + {63, 59, 90, 180, 59, 166, 93, 73, 154}, + {40, 40, 21, 116, 143, 209, 34, 39, 175}, + {47, 15, 16, 183, 34, 223, 49, 45, 183}, + {46, 17, 33, 183, 6, 98, 15, 32, 183}, + {57, 46, 22, 24, 128, 1, 54, 17, 37}, + {65, 32, 73, 115, 28, 128, 23, 128, 205}, + {40, 3, 9, 115, 51, 192, 18, 6, 223}, + {87, 37, 9, 115, 59, 77, 64, 21, 47}, + }, + { + {104, 55, 44, 218, 9, 54, 53, 130, 226}, + {64, 90, 70, 205, 40, 41, 23, 26, 57}, + {54, 57, 112, 184, 5, 41, 38, 166, 213}, + {30, 34, 26, 133, 152, 116, 10, 32, 134}, + {39, 19, 53, 221, 26, 114, 32, 73, 255}, + {31, 9, 65, 234, 2, 15, 1, 118, 73}, + {75, 32, 12, 51, 192, 255, 160, 43, 51}, + {88, 31, 35, 67, 102, 85, 55, 186, 85}, + {56, 21, 23, 111, 59, 205, 45, 37, 192}, + {55, 38, 70, 124, 73, 102, 1, 34, 98}, + }, + { + {125, 98, 42, 88, 104, 85, 117, 175, 82}, + {95, 84, 53, 89, 128, 100, 113, 101, 45}, + {75, 79, 123, 47, 51, 128, 81, 171, 1}, + {57, 17, 5, 71, 102, 57, 53, 41, 49}, + {38, 33, 13, 121, 57, 73, 26, 1, 85}, + {41, 10, 67, 138, 77, 110, 90, 47, 114}, + {115, 21, 2, 10, 102, 255, 166, 23, 6}, + {101, 29, 16, 10, 85, 128, 101, 196, 26}, + {57, 18, 10, 102, 102, 213, 34, 20, 43}, + {117, 20, 15, 36, 163, 128, 68, 1, 26}, + }, + { + {102, 61, 71, 37, 34, 53, 31, 243, 192}, + {69, 60, 71, 38, 73, 119, 28, 222, 37}, + {68, 45, 128, 34, 1, 47, 11, 245, 171}, + {62, 17, 19, 70, 146, 85, 55, 62, 70}, + {37, 43, 37, 154, 100, 163, 85, 160, 1}, + {63, 9, 92, 136, 28, 64, 32, 201, 85}, + {75, 15, 9, 9, 64, 255, 184, 119, 16}, + {86, 6, 28, 5, 64, 255, 25, 248, 1}, + {56, 8, 17, 132, 137, 255, 55, 116, 128}, + {58, 15, 20, 82, 135, 57, 26, 121, 40}, + }, + { + {164, 50, 31, 137, 154, 133, 25, 35, 218}, + {51, 103, 44, 131, 131, 123, 31, 6, 158}, + {86, 40, 64, 135, 148, 224, 45, 183, 128}, + {22, 26, 17, 131, 240, 154, 14, 1, 209}, + {45, 16, 21, 91, 64, 222, 7, 1, 197}, + {56, 21, 39, 155, 60, 138, 23, 102, 213}, + {83, 12, 13, 54, 192, 255, 68, 47, 28}, + {85, 26, 85, 85, 128, 128, 32, 146, 171}, + {18, 11, 7, 63, 144, 171, 4, 4, 246}, + {35, 27, 10, 146, 174, 171, 12, 26, 128}, + }, + { + {190, 80, 35, 99, 180, 80, 126, 54, 45}, + {85, 126, 47, 87, 176, 51, 41, 20, 32}, + {101, 75, 128, 139, 118, 146, 116, 128, 85}, + {56, 41, 15, 176, 236, 85, 37, 9, 62}, + {71, 30, 17, 119, 118, 255, 17, 18, 138}, + {101, 38, 60, 138, 55, 70, 43, 26, 142}, + {146, 36, 19, 30, 171, 255, 97, 27, 20}, + {138, 45, 61, 62, 219, 1, 81, 188, 64}, + {32, 41, 20, 117, 151, 142, 20, 21, 163}, + {112, 19, 12, 61, 195, 128, 48, 4, 24}, + }, +} diff --git a/src/golang.org/x/image/vp8/predfunc.go b/src/golang.org/x/image/vp8/predfunc.go new file mode 100644 index 0000000000..f8999582bd --- /dev/null +++ b/src/golang.org/x/image/vp8/predfunc.go @@ -0,0 +1,553 @@ +// 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. + +package vp8 + +// This file implements the predicition functions, as specified in chapter 12. +// +// For each macroblock (of 1x16x16 luma and 2x8x8 chroma coefficients), the +// luma values are either predicted as one large 16x16 region or 16 separate +// 4x4 regions. The chroma values are always predicted as one 8x8 region. +// +// For 4x4 regions, the target block's predicted values (Xs) are a function of +// its previously-decoded top and left border values, as well as a number of +// pixels from the top-right: +// +// a b c d e f g h +// p X X X X +// q X X X X +// r X X X X +// s X X X X +// +// The predictor modes are: +// - DC: all Xs = (b + c + d + e + p + q + r + s + 4) / 8. +// - TM: the first X = (b + p - a), the second X = (c + p - a), and so on. +// - VE: each X = the weighted average of its column's top value and that +// value's neighbors, i.e. averages of abc, bcd, cde or def. +// - HE: similar to VE except rows instead of columns, and the final row is +// an average of r, s and s. +// - RD, VR, LD, VL, HD, HU: these diagonal modes ("Right Down", "Vertical +// Right", etc) are more complicated and are described in section 12.3. +// All Xs are clipped to the range [0, 255]. +// +// For 8x8 and 16x16 regions, the target block's predicted values are a +// function of the top and left border values without the top-right overhang, +// i.e. without the 8x8 or 16x16 equivalent of f, g and h. Furthermore: +// - There are no diagonal predictor modes, only DC, TM, VE and HE. +// - The DC mode has variants for macroblocks in the top row and/or left +// column, i.e. for macroblocks with mby == 0 || mbx == 0. +// - The VE and HE modes take only the column top or row left values; they do +// not smooth that top/left value with its neighbors. + +// nPred is the number of predictor modes, not including the Top/Left versions +// of the DC predictor mode. +const nPred = 10 + +const ( + predDC = iota + predTM + predVE + predHE + predRD + predVR + predLD + predVL + predHD + predHU + predDCTop + predDCLeft + predDCTopLeft +) + +func checkTopLeftPred(mbx, mby int, p uint8) uint8 { + if p != predDC { + return p + } + if mbx == 0 { + if mby == 0 { + return predDCTopLeft + } + return predDCLeft + } + if mby == 0 { + return predDCTop + } + return predDC +} + +var predFunc4 = [...]func(*Decoder, int, int){ + predFunc4DC, + predFunc4TM, + predFunc4VE, + predFunc4HE, + predFunc4RD, + predFunc4VR, + predFunc4LD, + predFunc4VL, + predFunc4HD, + predFunc4HU, + nil, + nil, + nil, +} + +var predFunc8 = [...]func(*Decoder, int, int){ + predFunc8DC, + predFunc8TM, + predFunc8VE, + predFunc8HE, + nil, + nil, + nil, + nil, + nil, + nil, + predFunc8DCTop, + predFunc8DCLeft, + predFunc8DCTopLeft, +} + +var predFunc16 = [...]func(*Decoder, int, int){ + predFunc16DC, + predFunc16TM, + predFunc16VE, + predFunc16HE, + nil, + nil, + nil, + nil, + nil, + nil, + predFunc16DCTop, + predFunc16DCLeft, + predFunc16DCTopLeft, +} + +func predFunc4DC(z *Decoder, y, x int) { + sum := uint32(4) + for i := 0; i < 4; i++ { + sum += uint32(z.ybr[y-1][x+i]) + } + for j := 0; j < 4; j++ { + sum += uint32(z.ybr[y+j][x-1]) + } + avg := uint8(sum / 8) + for j := 0; j < 4; j++ { + for i := 0; i < 4; i++ { + z.ybr[y+j][x+i] = avg + } + } +} + +func predFunc4TM(z *Decoder, y, x int) { + delta0 := -int32(z.ybr[y-1][x-1]) + for j := 0; j < 4; j++ { + delta1 := delta0 + int32(z.ybr[y+j][x-1]) + for i := 0; i < 4; i++ { + delta2 := delta1 + int32(z.ybr[y-1][x+i]) + z.ybr[y+j][x+i] = uint8(clip(delta2, 0, 255)) + } + } +} + +func predFunc4VE(z *Decoder, y, x int) { + a := int32(z.ybr[y-1][x-1]) + b := int32(z.ybr[y-1][x+0]) + c := int32(z.ybr[y-1][x+1]) + d := int32(z.ybr[y-1][x+2]) + e := int32(z.ybr[y-1][x+3]) + f := int32(z.ybr[y-1][x+4]) + abc := uint8((a + 2*b + c + 2) / 4) + bcd := uint8((b + 2*c + d + 2) / 4) + cde := uint8((c + 2*d + e + 2) / 4) + def := uint8((d + 2*e + f + 2) / 4) + for j := 0; j < 4; j++ { + z.ybr[y+j][x+0] = abc + z.ybr[y+j][x+1] = bcd + z.ybr[y+j][x+2] = cde + z.ybr[y+j][x+3] = def + } +} + +func predFunc4HE(z *Decoder, y, x int) { + s := int32(z.ybr[y+3][x-1]) + r := int32(z.ybr[y+2][x-1]) + q := int32(z.ybr[y+1][x-1]) + p := int32(z.ybr[y+0][x-1]) + a := int32(z.ybr[y-1][x-1]) + ssr := uint8((s + 2*s + r + 2) / 4) + srq := uint8((s + 2*r + q + 2) / 4) + rqp := uint8((r + 2*q + p + 2) / 4) + apq := uint8((a + 2*p + q + 2) / 4) + for i := 0; i < 4; i++ { + z.ybr[y+0][x+i] = apq + z.ybr[y+1][x+i] = rqp + z.ybr[y+2][x+i] = srq + z.ybr[y+3][x+i] = ssr + } +} + +func predFunc4RD(z *Decoder, y, x int) { + s := int32(z.ybr[y+3][x-1]) + r := int32(z.ybr[y+2][x-1]) + q := int32(z.ybr[y+1][x-1]) + p := int32(z.ybr[y+0][x-1]) + a := int32(z.ybr[y-1][x-1]) + b := int32(z.ybr[y-1][x+0]) + c := int32(z.ybr[y-1][x+1]) + d := int32(z.ybr[y-1][x+2]) + e := int32(z.ybr[y-1][x+3]) + srq := uint8((s + 2*r + q + 2) / 4) + rqp := uint8((r + 2*q + p + 2) / 4) + qpa := uint8((q + 2*p + a + 2) / 4) + pab := uint8((p + 2*a + b + 2) / 4) + abc := uint8((a + 2*b + c + 2) / 4) + bcd := uint8((b + 2*c + d + 2) / 4) + cde := uint8((c + 2*d + e + 2) / 4) + z.ybr[y+0][x+0] = pab + z.ybr[y+0][x+1] = abc + z.ybr[y+0][x+2] = bcd + z.ybr[y+0][x+3] = cde + z.ybr[y+1][x+0] = qpa + z.ybr[y+1][x+1] = pab + z.ybr[y+1][x+2] = abc + z.ybr[y+1][x+3] = bcd + z.ybr[y+2][x+0] = rqp + z.ybr[y+2][x+1] = qpa + z.ybr[y+2][x+2] = pab + z.ybr[y+2][x+3] = abc + z.ybr[y+3][x+0] = srq + z.ybr[y+3][x+1] = rqp + z.ybr[y+3][x+2] = qpa + z.ybr[y+3][x+3] = pab +} + +func predFunc4VR(z *Decoder, y, x int) { + r := int32(z.ybr[y+2][x-1]) + q := int32(z.ybr[y+1][x-1]) + p := int32(z.ybr[y+0][x-1]) + a := int32(z.ybr[y-1][x-1]) + b := int32(z.ybr[y-1][x+0]) + c := int32(z.ybr[y-1][x+1]) + d := int32(z.ybr[y-1][x+2]) + e := int32(z.ybr[y-1][x+3]) + ab := uint8((a + b + 1) / 2) + bc := uint8((b + c + 1) / 2) + cd := uint8((c + d + 1) / 2) + de := uint8((d + e + 1) / 2) + rqp := uint8((r + 2*q + p + 2) / 4) + qpa := uint8((q + 2*p + a + 2) / 4) + pab := uint8((p + 2*a + b + 2) / 4) + abc := uint8((a + 2*b + c + 2) / 4) + bcd := uint8((b + 2*c + d + 2) / 4) + cde := uint8((c + 2*d + e + 2) / 4) + z.ybr[y+0][x+0] = ab + z.ybr[y+0][x+1] = bc + z.ybr[y+0][x+2] = cd + z.ybr[y+0][x+3] = de + z.ybr[y+1][x+0] = pab + z.ybr[y+1][x+1] = abc + z.ybr[y+1][x+2] = bcd + z.ybr[y+1][x+3] = cde + z.ybr[y+2][x+0] = qpa + z.ybr[y+2][x+1] = ab + z.ybr[y+2][x+2] = bc + z.ybr[y+2][x+3] = cd + z.ybr[y+3][x+0] = rqp + z.ybr[y+3][x+1] = pab + z.ybr[y+3][x+2] = abc + z.ybr[y+3][x+3] = bcd +} + +func predFunc4LD(z *Decoder, y, x int) { + a := int32(z.ybr[y-1][x+0]) + b := int32(z.ybr[y-1][x+1]) + c := int32(z.ybr[y-1][x+2]) + d := int32(z.ybr[y-1][x+3]) + e := int32(z.ybr[y-1][x+4]) + f := int32(z.ybr[y-1][x+5]) + g := int32(z.ybr[y-1][x+6]) + h := int32(z.ybr[y-1][x+7]) + abc := uint8((a + 2*b + c + 2) / 4) + bcd := uint8((b + 2*c + d + 2) / 4) + cde := uint8((c + 2*d + e + 2) / 4) + def := uint8((d + 2*e + f + 2) / 4) + efg := uint8((e + 2*f + g + 2) / 4) + fgh := uint8((f + 2*g + h + 2) / 4) + ghh := uint8((g + 2*h + h + 2) / 4) + z.ybr[y+0][x+0] = abc + z.ybr[y+0][x+1] = bcd + z.ybr[y+0][x+2] = cde + z.ybr[y+0][x+3] = def + z.ybr[y+1][x+0] = bcd + z.ybr[y+1][x+1] = cde + z.ybr[y+1][x+2] = def + z.ybr[y+1][x+3] = efg + z.ybr[y+2][x+0] = cde + z.ybr[y+2][x+1] = def + z.ybr[y+2][x+2] = efg + z.ybr[y+2][x+3] = fgh + z.ybr[y+3][x+0] = def + z.ybr[y+3][x+1] = efg + z.ybr[y+3][x+2] = fgh + z.ybr[y+3][x+3] = ghh +} + +func predFunc4VL(z *Decoder, y, x int) { + a := int32(z.ybr[y-1][x+0]) + b := int32(z.ybr[y-1][x+1]) + c := int32(z.ybr[y-1][x+2]) + d := int32(z.ybr[y-1][x+3]) + e := int32(z.ybr[y-1][x+4]) + f := int32(z.ybr[y-1][x+5]) + g := int32(z.ybr[y-1][x+6]) + h := int32(z.ybr[y-1][x+7]) + ab := uint8((a + b + 1) / 2) + bc := uint8((b + c + 1) / 2) + cd := uint8((c + d + 1) / 2) + de := uint8((d + e + 1) / 2) + abc := uint8((a + 2*b + c + 2) / 4) + bcd := uint8((b + 2*c + d + 2) / 4) + cde := uint8((c + 2*d + e + 2) / 4) + def := uint8((d + 2*e + f + 2) / 4) + efg := uint8((e + 2*f + g + 2) / 4) + fgh := uint8((f + 2*g + h + 2) / 4) + z.ybr[y+0][x+0] = ab + z.ybr[y+0][x+1] = bc + z.ybr[y+0][x+2] = cd + z.ybr[y+0][x+3] = de + z.ybr[y+1][x+0] = abc + z.ybr[y+1][x+1] = bcd + z.ybr[y+1][x+2] = cde + z.ybr[y+1][x+3] = def + z.ybr[y+2][x+0] = bc + z.ybr[y+2][x+1] = cd + z.ybr[y+2][x+2] = de + z.ybr[y+2][x+3] = efg + z.ybr[y+3][x+0] = bcd + z.ybr[y+3][x+1] = cde + z.ybr[y+3][x+2] = def + z.ybr[y+3][x+3] = fgh +} + +func predFunc4HD(z *Decoder, y, x int) { + s := int32(z.ybr[y+3][x-1]) + r := int32(z.ybr[y+2][x-1]) + q := int32(z.ybr[y+1][x-1]) + p := int32(z.ybr[y+0][x-1]) + a := int32(z.ybr[y-1][x-1]) + b := int32(z.ybr[y-1][x+0]) + c := int32(z.ybr[y-1][x+1]) + d := int32(z.ybr[y-1][x+2]) + sr := uint8((s + r + 1) / 2) + rq := uint8((r + q + 1) / 2) + qp := uint8((q + p + 1) / 2) + pa := uint8((p + a + 1) / 2) + srq := uint8((s + 2*r + q + 2) / 4) + rqp := uint8((r + 2*q + p + 2) / 4) + qpa := uint8((q + 2*p + a + 2) / 4) + pab := uint8((p + 2*a + b + 2) / 4) + abc := uint8((a + 2*b + c + 2) / 4) + bcd := uint8((b + 2*c + d + 2) / 4) + z.ybr[y+0][x+0] = pa + z.ybr[y+0][x+1] = pab + z.ybr[y+0][x+2] = abc + z.ybr[y+0][x+3] = bcd + z.ybr[y+1][x+0] = qp + z.ybr[y+1][x+1] = qpa + z.ybr[y+1][x+2] = pa + z.ybr[y+1][x+3] = pab + z.ybr[y+2][x+0] = rq + z.ybr[y+2][x+1] = rqp + z.ybr[y+2][x+2] = qp + z.ybr[y+2][x+3] = qpa + z.ybr[y+3][x+0] = sr + z.ybr[y+3][x+1] = srq + z.ybr[y+3][x+2] = rq + z.ybr[y+3][x+3] = rqp +} + +func predFunc4HU(z *Decoder, y, x int) { + s := int32(z.ybr[y+3][x-1]) + r := int32(z.ybr[y+2][x-1]) + q := int32(z.ybr[y+1][x-1]) + p := int32(z.ybr[y+0][x-1]) + pq := uint8((p + q + 1) / 2) + qr := uint8((q + r + 1) / 2) + rs := uint8((r + s + 1) / 2) + pqr := uint8((p + 2*q + r + 2) / 4) + qrs := uint8((q + 2*r + s + 2) / 4) + rss := uint8((r + 2*s + s + 2) / 4) + sss := uint8(s) + z.ybr[y+0][x+0] = pq + z.ybr[y+0][x+1] = pqr + z.ybr[y+0][x+2] = qr + z.ybr[y+0][x+3] = qrs + z.ybr[y+1][x+0] = qr + z.ybr[y+1][x+1] = qrs + z.ybr[y+1][x+2] = rs + z.ybr[y+1][x+3] = rss + z.ybr[y+2][x+0] = rs + z.ybr[y+2][x+1] = rss + z.ybr[y+2][x+2] = sss + z.ybr[y+2][x+3] = sss + z.ybr[y+3][x+0] = sss + z.ybr[y+3][x+1] = sss + z.ybr[y+3][x+2] = sss + z.ybr[y+3][x+3] = sss +} + +func predFunc8DC(z *Decoder, y, x int) { + sum := uint32(8) + for i := 0; i < 8; i++ { + sum += uint32(z.ybr[y-1][x+i]) + } + for j := 0; j < 8; j++ { + sum += uint32(z.ybr[y+j][x-1]) + } + avg := uint8(sum / 16) + for j := 0; j < 8; j++ { + for i := 0; i < 8; i++ { + z.ybr[y+j][x+i] = avg + } + } +} + +func predFunc8TM(z *Decoder, y, x int) { + delta0 := -int32(z.ybr[y-1][x-1]) + for j := 0; j < 8; j++ { + delta1 := delta0 + int32(z.ybr[y+j][x-1]) + for i := 0; i < 8; i++ { + delta2 := delta1 + int32(z.ybr[y-1][x+i]) + z.ybr[y+j][x+i] = uint8(clip(delta2, 0, 255)) + } + } +} + +func predFunc8VE(z *Decoder, y, x int) { + for j := 0; j < 8; j++ { + for i := 0; i < 8; i++ { + z.ybr[y+j][x+i] = z.ybr[y-1][x+i] + } + } +} + +func predFunc8HE(z *Decoder, y, x int) { + for j := 0; j < 8; j++ { + for i := 0; i < 8; i++ { + z.ybr[y+j][x+i] = z.ybr[y+j][x-1] + } + } +} + +func predFunc8DCTop(z *Decoder, y, x int) { + sum := uint32(4) + for j := 0; j < 8; j++ { + sum += uint32(z.ybr[y+j][x-1]) + } + avg := uint8(sum / 8) + for j := 0; j < 8; j++ { + for i := 0; i < 8; i++ { + z.ybr[y+j][x+i] = avg + } + } +} + +func predFunc8DCLeft(z *Decoder, y, x int) { + sum := uint32(4) + for i := 0; i < 8; i++ { + sum += uint32(z.ybr[y-1][x+i]) + } + avg := uint8(sum / 8) + for j := 0; j < 8; j++ { + for i := 0; i < 8; i++ { + z.ybr[y+j][x+i] = avg + } + } +} + +func predFunc8DCTopLeft(z *Decoder, y, x int) { + for j := 0; j < 8; j++ { + for i := 0; i < 8; i++ { + z.ybr[y+j][x+i] = 0x80 + } + } +} + +func predFunc16DC(z *Decoder, y, x int) { + sum := uint32(16) + for i := 0; i < 16; i++ { + sum += uint32(z.ybr[y-1][x+i]) + } + for j := 0; j < 16; j++ { + sum += uint32(z.ybr[y+j][x-1]) + } + avg := uint8(sum / 32) + for j := 0; j < 16; j++ { + for i := 0; i < 16; i++ { + z.ybr[y+j][x+i] = avg + } + } +} + +func predFunc16TM(z *Decoder, y, x int) { + delta0 := -int32(z.ybr[y-1][x-1]) + for j := 0; j < 16; j++ { + delta1 := delta0 + int32(z.ybr[y+j][x-1]) + for i := 0; i < 16; i++ { + delta2 := delta1 + int32(z.ybr[y-1][x+i]) + z.ybr[y+j][x+i] = uint8(clip(delta2, 0, 255)) + } + } +} + +func predFunc16VE(z *Decoder, y, x int) { + for j := 0; j < 16; j++ { + for i := 0; i < 16; i++ { + z.ybr[y+j][x+i] = z.ybr[y-1][x+i] + } + } +} + +func predFunc16HE(z *Decoder, y, x int) { + for j := 0; j < 16; j++ { + for i := 0; i < 16; i++ { + z.ybr[y+j][x+i] = z.ybr[y+j][x-1] + } + } +} + +func predFunc16DCTop(z *Decoder, y, x int) { + sum := uint32(8) + for j := 0; j < 16; j++ { + sum += uint32(z.ybr[y+j][x-1]) + } + avg := uint8(sum / 16) + for j := 0; j < 16; j++ { + for i := 0; i < 16; i++ { + z.ybr[y+j][x+i] = avg + } + } +} + +func predFunc16DCLeft(z *Decoder, y, x int) { + sum := uint32(8) + for i := 0; i < 16; i++ { + sum += uint32(z.ybr[y-1][x+i]) + } + avg := uint8(sum / 16) + for j := 0; j < 16; j++ { + for i := 0; i < 16; i++ { + z.ybr[y+j][x+i] = avg + } + } +} + +func predFunc16DCTopLeft(z *Decoder, y, x int) { + for j := 0; j < 16; j++ { + for i := 0; i < 16; i++ { + z.ybr[y+j][x+i] = 0x80 + } + } +} diff --git a/src/golang.org/x/image/vp8/quant.go b/src/golang.org/x/image/vp8/quant.go new file mode 100644 index 0000000000..da4361604f --- /dev/null +++ b/src/golang.org/x/image/vp8/quant.go @@ -0,0 +1,98 @@ +// 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. + +package vp8 + +// This file implements parsing the quantization factors. + +// quant are DC/AC quantization factors. +type quant struct { + y1 [2]uint16 + y2 [2]uint16 + uv [2]uint16 +} + +// clip clips x to the range [min, max] inclusive. +func clip(x, min, max int32) int32 { + if x < min { + return min + } + if x > max { + return max + } + return x +} + +// parseQuant parses the quantization factors, as specified in section 9.6. +func (d *Decoder) parseQuant() { + baseQ0 := d.fp.readUint(uniformProb, 7) + dqy1DC := d.fp.readOptionalInt(uniformProb, 4) + const dqy1AC = 0 + dqy2DC := d.fp.readOptionalInt(uniformProb, 4) + dqy2AC := d.fp.readOptionalInt(uniformProb, 4) + dquvDC := d.fp.readOptionalInt(uniformProb, 4) + dquvAC := d.fp.readOptionalInt(uniformProb, 4) + for i := 0; i < nSegment; i++ { + q := int32(baseQ0) + if d.segmentHeader.useSegment { + if d.segmentHeader.relativeDelta { + q += int32(d.segmentHeader.quantizer[i]) + } else { + q = int32(d.segmentHeader.quantizer[i]) + } + } + d.quant[i].y1[0] = dequantTableDC[clip(q+dqy1DC, 0, 127)] + d.quant[i].y1[1] = dequantTableAC[clip(q+dqy1AC, 0, 127)] + d.quant[i].y2[0] = dequantTableDC[clip(q+dqy2DC, 0, 127)] * 2 + d.quant[i].y2[1] = dequantTableAC[clip(q+dqy2AC, 0, 127)] * 155 / 100 + if d.quant[i].y2[1] < 8 { + d.quant[i].y2[1] = 8 + } + // The 117 is not a typo. The dequant_init function in the spec's Reference + // Decoder Source Code (http://tools.ietf.org/html/rfc6386#section-9.6 Page 145) + // says to clamp the LHS value at 132, which is equal to dequantTableDC[117]. + d.quant[i].uv[0] = dequantTableDC[clip(q+dquvDC, 0, 117)] + d.quant[i].uv[1] = dequantTableAC[clip(q+dquvAC, 0, 127)] + } +} + +// The dequantization tables are specified in section 14.1. +var ( + dequantTableDC = [128]uint16{ + 4, 5, 6, 7, 8, 9, 10, 10, + 11, 12, 13, 14, 15, 16, 17, 17, + 18, 19, 20, 20, 21, 21, 22, 22, + 23, 23, 24, 25, 25, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, + 37, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, 62, 63, 64, 65, 66, + 67, 68, 69, 70, 71, 72, 73, 74, + 75, 76, 76, 77, 78, 79, 80, 81, + 82, 83, 84, 85, 86, 87, 88, 89, + 91, 93, 95, 96, 98, 100, 101, 102, + 104, 106, 108, 110, 112, 114, 116, 118, + 122, 124, 126, 128, 130, 132, 134, 136, + 138, 140, 143, 145, 148, 151, 154, 157, + } + dequantTableAC = [128]uint16{ + 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 60, + 62, 64, 66, 68, 70, 72, 74, 76, + 78, 80, 82, 84, 86, 88, 90, 92, + 94, 96, 98, 100, 102, 104, 106, 108, + 110, 112, 114, 116, 119, 122, 125, 128, + 131, 134, 137, 140, 143, 146, 149, 152, + 155, 158, 161, 164, 167, 170, 173, 177, + 181, 185, 189, 193, 197, 201, 205, 209, + 213, 217, 221, 225, 229, 234, 239, 245, + 249, 254, 259, 264, 269, 274, 279, 284, + } +) diff --git a/src/golang.org/x/image/vp8/reconstruct.go b/src/golang.org/x/image/vp8/reconstruct.go new file mode 100644 index 0000000000..c1cc4b532d --- /dev/null +++ b/src/golang.org/x/image/vp8/reconstruct.go @@ -0,0 +1,442 @@ +// 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. + +package vp8 + +// This file implements decoding DCT/WHT residual coefficients and +// reconstructing YCbCr data equal to predicted values plus residuals. +// +// There are 1*16*16 + 2*8*8 + 1*4*4 coefficients per macroblock: +// - 1*16*16 luma DCT coefficients, +// - 2*8*8 chroma DCT coefficients, and +// - 1*4*4 luma WHT coefficients. +// Coefficients are read in lots of 16, and the later coefficients in each lot +// are often zero. +// +// The YCbCr data consists of 1*16*16 luma values and 2*8*8 chroma values, +// plus previously decoded values along the top and left borders. The combined +// values are laid out as a [1+16+1+8][32]uint8 so that vertically adjacent +// samples are 32 bytes apart. In detail, the layout is: +// +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// . . . . . . . a b b b b b b b b b b b b b b b b c c c c . . . . 0 +// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 1 +// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 2 +// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 3 +// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y c c c c . . . . 4 +// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 5 +// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 6 +// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 7 +// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y c c c c . . . . 8 +// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 9 +// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 10 +// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 11 +// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y c c c c . . . . 12 +// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 13 +// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 14 +// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 15 +// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 16 +// . . . . . . . e f f f f f f f f . . . . . . . g h h h h h h h h 17 +// . . . . . . . i B B B B B B B B . . . . . . . j R R R R R R R R 18 +// . . . . . . . i B B B B B B B B . . . . . . . j R R R R R R R R 19 +// . . . . . . . i B B B B B B B B . . . . . . . j R R R R R R R R 20 +// . . . . . . . i B B B B B B B B . . . . . . . j R R R R R R R R 21 +// . . . . . . . i B B B B B B B B . . . . . . . j R R R R R R R R 22 +// . . . . . . . i B B B B B B B B . . . . . . . j R R R R R R R R 23 +// . . . . . . . i B B B B B B B B . . . . . . . j R R R R R R R R 24 +// . . . . . . . i B B B B B B B B . . . . . . . j R R R R R R R R 25 +// +// Y, B and R are the reconstructed luma (Y) and chroma (B, R) values. +// The Y values are predicted (either as one 16x16 region or 16 4x4 regions) +// based on the row above's Y values (some combination of {abc} or {dYC}) and +// the column left's Y values (either {ad} or {bY}). Similarly, B and R values +// are predicted on the row above and column left of their respective 8x8 +// region: {efi} for B, {ghj} for R. +// +// For uppermost macroblocks (i.e. those with mby == 0), the {abcefgh} values +// are initialized to 0x81. Otherwise, they are copied from the bottom row of +// the macroblock above. The {c} values are then duplicated from row 0 to rows +// 4, 8 and 12 of the ybr workspace. +// Similarly, for leftmost macroblocks (i.e. those with mbx == 0), the {adeigj} +// values are initialized to 0x7f. Otherwise, they are copied from the right +// column of the macroblock to the left. +// For the top-left macroblock (with mby == 0 && mbx == 0), {aeg} is 0x81. +// +// When moving from one macroblock to the next horizontally, the {adeigj} +// values can simply be copied from the workspace to itself, shifted by 8 or +// 16 columns. When moving from one macroblock to the next vertically, +// filtering can occur and hence the row values have to be copied from the +// post-filtered image instead of the pre-filtered workspace. + +const ( + bCoeffBase = 1*16*16 + 0*8*8 + rCoeffBase = 1*16*16 + 1*8*8 + whtCoeffBase = 1*16*16 + 2*8*8 +) + +const ( + ybrYX = 8 + ybrYY = 1 + ybrBX = 8 + ybrBY = 18 + ybrRX = 24 + ybrRY = 18 +) + +// prepareYBR prepares the {abcdefghij} elements of ybr. +func (d *Decoder) prepareYBR(mbx, mby int) { + if mbx == 0 { + for y := 0; y < 17; y++ { + d.ybr[y][7] = 0x81 + } + for y := 17; y < 26; y++ { + d.ybr[y][7] = 0x81 + d.ybr[y][23] = 0x81 + } + } else { + for y := 0; y < 17; y++ { + d.ybr[y][7] = d.ybr[y][7+16] + } + for y := 17; y < 26; y++ { + d.ybr[y][7] = d.ybr[y][15] + d.ybr[y][23] = d.ybr[y][31] + } + } + if mby == 0 { + for x := 7; x < 28; x++ { + d.ybr[0][x] = 0x7f + } + for x := 7; x < 16; x++ { + d.ybr[17][x] = 0x7f + } + for x := 23; x < 32; x++ { + d.ybr[17][x] = 0x7f + } + } else { + for i := 0; i < 16; i++ { + d.ybr[0][8+i] = d.img.Y[(16*mby-1)*d.img.YStride+16*mbx+i] + } + for i := 0; i < 8; i++ { + d.ybr[17][8+i] = d.img.Cb[(8*mby-1)*d.img.CStride+8*mbx+i] + } + for i := 0; i < 8; i++ { + d.ybr[17][24+i] = d.img.Cr[(8*mby-1)*d.img.CStride+8*mbx+i] + } + if mbx == d.mbw-1 { + for i := 16; i < 20; i++ { + d.ybr[0][8+i] = d.img.Y[(16*mby-1)*d.img.YStride+16*mbx+15] + } + } else { + for i := 16; i < 20; i++ { + d.ybr[0][8+i] = d.img.Y[(16*mby-1)*d.img.YStride+16*mbx+i] + } + } + } + for y := 4; y < 16; y += 4 { + d.ybr[y][24] = d.ybr[0][24] + d.ybr[y][25] = d.ybr[0][25] + d.ybr[y][26] = d.ybr[0][26] + d.ybr[y][27] = d.ybr[0][27] + } +} + +// btou converts a bool to a 0/1 value. +func btou(b bool) uint8 { + if b { + return 1 + } + return 0 +} + +// pack packs four 0/1 values into four bits of a uint32. +func pack(x [4]uint8, shift int) uint32 { + u := uint32(x[0])<<0 | uint32(x[1])<<1 | uint32(x[2])<<2 | uint32(x[3])<<3 + return u << uint(shift) +} + +// unpack unpacks four 0/1 values from a four-bit value. +var unpack = [16][4]uint8{ + {0, 0, 0, 0}, + {1, 0, 0, 0}, + {0, 1, 0, 0}, + {1, 1, 0, 0}, + {0, 0, 1, 0}, + {1, 0, 1, 0}, + {0, 1, 1, 0}, + {1, 1, 1, 0}, + {0, 0, 0, 1}, + {1, 0, 0, 1}, + {0, 1, 0, 1}, + {1, 1, 0, 1}, + {0, 0, 1, 1}, + {1, 0, 1, 1}, + {0, 1, 1, 1}, + {1, 1, 1, 1}, +} + +var ( + // The mapping from 4x4 region position to band is specified in section 13.3. + bands = [17]uint8{0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 0} + // Category probabilties are specified in section 13.2. + // Decoding categories 1 and 2 are done inline. + cat3456 = [4][12]uint8{ + {173, 148, 140, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {176, 155, 140, 135, 0, 0, 0, 0, 0, 0, 0, 0}, + {180, 157, 141, 134, 130, 0, 0, 0, 0, 0, 0, 0}, + {254, 254, 243, 230, 196, 177, 153, 140, 133, 130, 129, 0}, + } + // The zigzag order is: + // 0 1 5 6 + // 2 4 7 12 + // 3 8 11 13 + // 9 10 14 15 + zigzag = [16]uint8{0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15} +) + +// parseResiduals4 parses a 4x4 region of residual coefficients, as specified +// in section 13.3, and returns a 0/1 value indicating whether there was at +// least one non-zero coefficient. +// r is the partition to read bits from. +// plane and context describe which token probability table to use. context is +// either 0, 1 or 2, and equals how many of the macroblock left and macroblock +// above have non-zero coefficients. +// quant are the DC/AC quantization factors. +// skipFirstCoeff is whether the DC coefficient has already been parsed. +// coeffBase is the base index of d.coeff to write to. +func (d *Decoder) parseResiduals4(r *partition, plane int, context uint8, quant [2]uint16, skipFirstCoeff bool, coeffBase int) uint8 { + prob, n := &d.tokenProb[plane], 0 + if skipFirstCoeff { + n = 1 + } + p := prob[bands[n]][context] + if !r.readBit(p[0]) { + return 0 + } + for n != 16 { + n++ + if !r.readBit(p[1]) { + p = prob[bands[n]][0] + continue + } + var v uint32 + if !r.readBit(p[2]) { + v = 1 + p = prob[bands[n]][1] + } else { + if !r.readBit(p[3]) { + if !r.readBit(p[4]) { + v = 2 + } else { + v = 3 + r.readUint(p[5], 1) + } + } else if !r.readBit(p[6]) { + if !r.readBit(p[7]) { + // Category 1. + v = 5 + r.readUint(159, 1) + } else { + // Category 2. + v = 7 + 2*r.readUint(165, 1) + r.readUint(145, 1) + } + } else { + // Categories 3, 4, 5 or 6. + b1 := r.readUint(p[8], 1) + b0 := r.readUint(p[9+b1], 1) + cat := 2*b1 + b0 + tab := &cat3456[cat] + v = 0 + for i := 0; tab[i] != 0; i++ { + v *= 2 + v += r.readUint(tab[i], 1) + } + v += 3 + (8 << cat) + } + p = prob[bands[n]][2] + } + z := zigzag[n-1] + c := int32(v) * int32(quant[btou(z > 0)]) + if r.readBit(uniformProb) { + c = -c + } + d.coeff[coeffBase+int(z)] = int16(c) + if n == 16 || !r.readBit(p[0]) { + return 1 + } + } + return 1 +} + +// parseResiduals parses the residuals and returns whether inner loop filtering +// should be skipped for this macroblock. +func (d *Decoder) parseResiduals(mbx, mby int) (skip bool) { + partition := &d.op[mby&(d.nOP-1)] + plane := planeY1SansY2 + quant := &d.quant[d.segment] + + // Parse the DC coefficient of each 4x4 luma region. + if d.usePredY16 { + nz := d.parseResiduals4(partition, planeY2, d.leftMB.nzY16+d.upMB[mbx].nzY16, quant.y2, false, whtCoeffBase) + d.leftMB.nzY16 = nz + d.upMB[mbx].nzY16 = nz + d.inverseWHT16() + plane = planeY1WithY2 + } + + var ( + nzDC, nzAC [4]uint8 + nzDCMask, nzACMask uint32 + coeffBase int + ) + + // Parse the luma coefficients. + lnz := unpack[d.leftMB.nzMask&0x0f] + unz := unpack[d.upMB[mbx].nzMask&0x0f] + for y := 0; y < 4; y++ { + nz := lnz[y] + for x := 0; x < 4; x++ { + nz = d.parseResiduals4(partition, plane, nz+unz[x], quant.y1, d.usePredY16, coeffBase) + unz[x] = nz + nzAC[x] = nz + nzDC[x] = btou(d.coeff[coeffBase] != 0) + coeffBase += 16 + } + lnz[y] = nz + nzDCMask |= pack(nzDC, y*4) + nzACMask |= pack(nzAC, y*4) + } + lnzMask := pack(lnz, 0) + unzMask := pack(unz, 0) + + // Parse the chroma coefficients. + lnz = unpack[d.leftMB.nzMask>>4] + unz = unpack[d.upMB[mbx].nzMask>>4] + for c := 0; c < 4; c += 2 { + for y := 0; y < 2; y++ { + nz := lnz[y+c] + for x := 0; x < 2; x++ { + nz = d.parseResiduals4(partition, planeUV, nz+unz[x+c], quant.uv, false, coeffBase) + unz[x+c] = nz + nzAC[y*2+x] = nz + nzDC[y*2+x] = btou(d.coeff[coeffBase] != 0) + coeffBase += 16 + } + lnz[y+c] = nz + } + nzDCMask |= pack(nzDC, 16+c*2) + nzACMask |= pack(nzAC, 16+c*2) + } + lnzMask |= pack(lnz, 4) + unzMask |= pack(unz, 4) + + // Save decoder state. + d.leftMB.nzMask = uint8(lnzMask) + d.upMB[mbx].nzMask = uint8(unzMask) + d.nzDCMask = nzDCMask + d.nzACMask = nzACMask + + // Section 15.1 of the spec says that "Steps 2 and 4 [of the loop filter] + // are skipped... [if] there is no DCT coefficient coded for the whole + // macroblock." + return nzDCMask == 0 && nzACMask == 0 +} + +// reconstructMacroblock applies the predictor functions and adds the inverse- +// DCT transformed residuals to recover the YCbCr data. +func (d *Decoder) reconstructMacroblock(mbx, mby int) { + if d.usePredY16 { + p := checkTopLeftPred(mbx, mby, d.predY16) + predFunc16[p](d, 1, 8) + for j := 0; j < 4; j++ { + for i := 0; i < 4; i++ { + n := 4*j + i + y := 4*j + 1 + x := 4*i + 8 + mask := uint32(1) << uint(n) + if d.nzACMask&mask != 0 { + d.inverseDCT4(y, x, 16*n) + } else if d.nzDCMask&mask != 0 { + d.inverseDCT4DCOnly(y, x, 16*n) + } + } + } + } else { + for j := 0; j < 4; j++ { + for i := 0; i < 4; i++ { + n := 4*j + i + y := 4*j + 1 + x := 4*i + 8 + predFunc4[d.predY4[j][i]](d, y, x) + mask := uint32(1) << uint(n) + if d.nzACMask&mask != 0 { + d.inverseDCT4(y, x, 16*n) + } else if d.nzDCMask&mask != 0 { + d.inverseDCT4DCOnly(y, x, 16*n) + } + } + } + } + p := checkTopLeftPred(mbx, mby, d.predC8) + predFunc8[p](d, ybrBY, ybrBX) + if d.nzACMask&0x0f0000 != 0 { + d.inverseDCT8(ybrBY, ybrBX, bCoeffBase) + } else if d.nzDCMask&0x0f0000 != 0 { + d.inverseDCT8DCOnly(ybrBY, ybrBX, bCoeffBase) + } + predFunc8[p](d, ybrRY, ybrRX) + if d.nzACMask&0xf00000 != 0 { + d.inverseDCT8(ybrRY, ybrRX, rCoeffBase) + } else if d.nzDCMask&0xf00000 != 0 { + d.inverseDCT8DCOnly(ybrRY, ybrRX, rCoeffBase) + } +} + +// reconstruct reconstructs one macroblock and returns whether inner loop +// filtering should be skipped for it. +func (d *Decoder) reconstruct(mbx, mby int) (skip bool) { + if d.segmentHeader.updateMap { + if !d.fp.readBit(d.segmentHeader.prob[0]) { + d.segment = int(d.fp.readUint(d.segmentHeader.prob[1], 1)) + } else { + d.segment = int(d.fp.readUint(d.segmentHeader.prob[2], 1)) + 2 + } + } + if d.useSkipProb { + skip = d.fp.readBit(d.skipProb) + } + // Prepare the workspace. + for i := range d.coeff { + d.coeff[i] = 0 + } + d.prepareYBR(mbx, mby) + // Parse the predictor modes. + d.usePredY16 = d.fp.readBit(145) + if d.usePredY16 { + d.parsePredModeY16(mbx) + } else { + d.parsePredModeY4(mbx) + } + d.parsePredModeC8() + // Parse the residuals. + if !skip { + skip = d.parseResiduals(mbx, mby) + } else { + if d.usePredY16 { + d.leftMB.nzY16 = 0 + d.upMB[mbx].nzY16 = 0 + } + d.leftMB.nzMask = 0 + d.upMB[mbx].nzMask = 0 + d.nzDCMask = 0 + d.nzACMask = 0 + } + // Reconstruct the YCbCr data and copy it to the image. + d.reconstructMacroblock(mbx, mby) + for i, y := (mby*d.img.YStride+mbx)*16, 0; y < 16; i, y = i+d.img.YStride, y+1 { + copy(d.img.Y[i:i+16], d.ybr[ybrYY+y][ybrYX:ybrYX+16]) + } + for i, y := (mby*d.img.CStride+mbx)*8, 0; y < 8; i, y = i+d.img.CStride, y+1 { + copy(d.img.Cb[i:i+8], d.ybr[ybrBY+y][ybrBX:ybrBX+8]) + copy(d.img.Cr[i:i+8], d.ybr[ybrRY+y][ybrRX:ybrRX+8]) + } + return skip +} diff --git a/src/golang.org/x/image/vp8/token.go b/src/golang.org/x/image/vp8/token.go new file mode 100644 index 0000000000..da99cf0f9e --- /dev/null +++ b/src/golang.org/x/image/vp8/token.go @@ -0,0 +1,381 @@ +// 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. + +package vp8 + +// This file contains token probabilities for decoding DCT/WHT coefficients, as +// specified in chapter 13. + +func (d *Decoder) parseTokenProb() { + for i := range d.tokenProb { + for j := range d.tokenProb[i] { + for k := range d.tokenProb[i][j] { + for l := range d.tokenProb[i][j][k] { + if d.fp.readBit(tokenProbUpdateProb[i][j][k][l]) { + d.tokenProb[i][j][k][l] = uint8(d.fp.readUint(uniformProb, 8)) + } + } + } + } + } +} + +// The plane enumeration is specified in section 13.3. +const ( + planeY1WithY2 = iota + planeY2 + planeUV + planeY1SansY2 + nPlane +) + +const ( + nBand = 8 + nContext = 3 + nProb = 11 +) + +// Token probability update probabilities are specified in section 13.4. +var tokenProbUpdateProb = [nPlane][nBand][nContext][nProb]uint8{ + { + { + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {176, 246, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {223, 241, 252, 255, 255, 255, 255, 255, 255, 255, 255}, + {249, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 244, 252, 255, 255, 255, 255, 255, 255, 255, 255}, + {234, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 246, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {239, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {251, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {251, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 254, 253, 255, 254, 255, 255, 255, 255, 255, 255}, + {250, 255, 254, 255, 254, 255, 255, 255, 255, 255, 255}, + {254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + }, + { + { + {217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {225, 252, 241, 253, 255, 255, 254, 255, 255, 255, 255}, + {234, 250, 241, 250, 253, 255, 253, 254, 255, 255, 255}, + }, + { + {255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {223, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {238, 253, 254, 254, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {249, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {247, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255}, + {250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + }, + { + { + {186, 251, 250, 255, 255, 255, 255, 255, 255, 255, 255}, + {234, 251, 244, 254, 255, 255, 255, 255, 255, 255, 255}, + {251, 251, 243, 253, 254, 255, 254, 255, 255, 255, 255}, + }, + { + {255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {236, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {251, 253, 253, 254, 254, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + }, + { + { + {248, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {250, 254, 252, 254, 255, 255, 255, 255, 255, 255, 255}, + {248, 254, 249, 253, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255}, + {246, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255}, + {252, 254, 251, 254, 254, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 254, 252, 255, 255, 255, 255, 255, 255, 255, 255}, + {248, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255}, + {253, 255, 254, 254, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {245, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {253, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 251, 253, 255, 255, 255, 255, 255, 255, 255, 255}, + {252, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {249, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 255, 253, 255, 255, 255, 255, 255, 255, 255, 255}, + {250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + }, +} + +// Default token probabilities are specified in section 13.5. +var defaultTokenProb = [nPlane][nBand][nContext][nProb]uint8{ + { + { + {128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128}, + {128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128}, + {128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128}, + }, + { + {253, 136, 254, 255, 228, 219, 128, 128, 128, 128, 128}, + {189, 129, 242, 255, 227, 213, 255, 219, 128, 128, 128}, + {106, 126, 227, 252, 214, 209, 255, 255, 128, 128, 128}, + }, + { + {1, 98, 248, 255, 236, 226, 255, 255, 128, 128, 128}, + {181, 133, 238, 254, 221, 234, 255, 154, 128, 128, 128}, + {78, 134, 202, 247, 198, 180, 255, 219, 128, 128, 128}, + }, + { + {1, 185, 249, 255, 243, 255, 128, 128, 128, 128, 128}, + {184, 150, 247, 255, 236, 224, 128, 128, 128, 128, 128}, + {77, 110, 216, 255, 236, 230, 128, 128, 128, 128, 128}, + }, + { + {1, 101, 251, 255, 241, 255, 128, 128, 128, 128, 128}, + {170, 139, 241, 252, 236, 209, 255, 255, 128, 128, 128}, + {37, 116, 196, 243, 228, 255, 255, 255, 128, 128, 128}, + }, + { + {1, 204, 254, 255, 245, 255, 128, 128, 128, 128, 128}, + {207, 160, 250, 255, 238, 128, 128, 128, 128, 128, 128}, + {102, 103, 231, 255, 211, 171, 128, 128, 128, 128, 128}, + }, + { + {1, 152, 252, 255, 240, 255, 128, 128, 128, 128, 128}, + {177, 135, 243, 255, 234, 225, 128, 128, 128, 128, 128}, + {80, 129, 211, 255, 194, 224, 128, 128, 128, 128, 128}, + }, + { + {1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128}, + {246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128}, + {255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128}, + }, + }, + { + { + {198, 35, 237, 223, 193, 187, 162, 160, 145, 155, 62}, + {131, 45, 198, 221, 172, 176, 220, 157, 252, 221, 1}, + {68, 47, 146, 208, 149, 167, 221, 162, 255, 223, 128}, + }, + { + {1, 149, 241, 255, 221, 224, 255, 255, 128, 128, 128}, + {184, 141, 234, 253, 222, 220, 255, 199, 128, 128, 128}, + {81, 99, 181, 242, 176, 190, 249, 202, 255, 255, 128}, + }, + { + {1, 129, 232, 253, 214, 197, 242, 196, 255, 255, 128}, + {99, 121, 210, 250, 201, 198, 255, 202, 128, 128, 128}, + {23, 91, 163, 242, 170, 187, 247, 210, 255, 255, 128}, + }, + { + {1, 200, 246, 255, 234, 255, 128, 128, 128, 128, 128}, + {109, 178, 241, 255, 231, 245, 255, 255, 128, 128, 128}, + {44, 130, 201, 253, 205, 192, 255, 255, 128, 128, 128}, + }, + { + {1, 132, 239, 251, 219, 209, 255, 165, 128, 128, 128}, + {94, 136, 225, 251, 218, 190, 255, 255, 128, 128, 128}, + {22, 100, 174, 245, 186, 161, 255, 199, 128, 128, 128}, + }, + { + {1, 182, 249, 255, 232, 235, 128, 128, 128, 128, 128}, + {124, 143, 241, 255, 227, 234, 128, 128, 128, 128, 128}, + {35, 77, 181, 251, 193, 211, 255, 205, 128, 128, 128}, + }, + { + {1, 157, 247, 255, 236, 231, 255, 255, 128, 128, 128}, + {121, 141, 235, 255, 225, 227, 255, 255, 128, 128, 128}, + {45, 99, 188, 251, 195, 217, 255, 224, 128, 128, 128}, + }, + { + {1, 1, 251, 255, 213, 255, 128, 128, 128, 128, 128}, + {203, 1, 248, 255, 255, 128, 128, 128, 128, 128, 128}, + {137, 1, 177, 255, 224, 255, 128, 128, 128, 128, 128}, + }, + }, + { + { + {253, 9, 248, 251, 207, 208, 255, 192, 128, 128, 128}, + {175, 13, 224, 243, 193, 185, 249, 198, 255, 255, 128}, + {73, 17, 171, 221, 161, 179, 236, 167, 255, 234, 128}, + }, + { + {1, 95, 247, 253, 212, 183, 255, 255, 128, 128, 128}, + {239, 90, 244, 250, 211, 209, 255, 255, 128, 128, 128}, + {155, 77, 195, 248, 188, 195, 255, 255, 128, 128, 128}, + }, + { + {1, 24, 239, 251, 218, 219, 255, 205, 128, 128, 128}, + {201, 51, 219, 255, 196, 186, 128, 128, 128, 128, 128}, + {69, 46, 190, 239, 201, 218, 255, 228, 128, 128, 128}, + }, + { + {1, 191, 251, 255, 255, 128, 128, 128, 128, 128, 128}, + {223, 165, 249, 255, 213, 255, 128, 128, 128, 128, 128}, + {141, 124, 248, 255, 255, 128, 128, 128, 128, 128, 128}, + }, + { + {1, 16, 248, 255, 255, 128, 128, 128, 128, 128, 128}, + {190, 36, 230, 255, 236, 255, 128, 128, 128, 128, 128}, + {149, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128}, + }, + { + {1, 226, 255, 128, 128, 128, 128, 128, 128, 128, 128}, + {247, 192, 255, 128, 128, 128, 128, 128, 128, 128, 128}, + {240, 128, 255, 128, 128, 128, 128, 128, 128, 128, 128}, + }, + { + {1, 134, 252, 255, 255, 128, 128, 128, 128, 128, 128}, + {213, 62, 250, 255, 255, 128, 128, 128, 128, 128, 128}, + {55, 93, 255, 128, 128, 128, 128, 128, 128, 128, 128}, + }, + { + {128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128}, + {128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128}, + {128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128}, + }, + }, + { + { + {202, 24, 213, 235, 186, 191, 220, 160, 240, 175, 255}, + {126, 38, 182, 232, 169, 184, 228, 174, 255, 187, 128}, + {61, 46, 138, 219, 151, 178, 240, 170, 255, 216, 128}, + }, + { + {1, 112, 230, 250, 199, 191, 247, 159, 255, 255, 128}, + {166, 109, 228, 252, 211, 215, 255, 174, 128, 128, 128}, + {39, 77, 162, 232, 172, 180, 245, 178, 255, 255, 128}, + }, + { + {1, 52, 220, 246, 198, 199, 249, 220, 255, 255, 128}, + {124, 74, 191, 243, 183, 193, 250, 221, 255, 255, 128}, + {24, 71, 130, 219, 154, 170, 243, 182, 255, 255, 128}, + }, + { + {1, 182, 225, 249, 219, 240, 255, 224, 128, 128, 128}, + {149, 150, 226, 252, 216, 205, 255, 171, 128, 128, 128}, + {28, 108, 170, 242, 183, 194, 254, 223, 255, 255, 128}, + }, + { + {1, 81, 230, 252, 204, 203, 255, 192, 128, 128, 128}, + {123, 102, 209, 247, 188, 196, 255, 233, 128, 128, 128}, + {20, 95, 153, 243, 164, 173, 255, 203, 128, 128, 128}, + }, + { + {1, 222, 248, 255, 216, 213, 128, 128, 128, 128, 128}, + {168, 175, 246, 252, 235, 205, 255, 255, 128, 128, 128}, + {47, 116, 215, 255, 211, 212, 255, 255, 128, 128, 128}, + }, + { + {1, 121, 236, 253, 212, 214, 255, 255, 128, 128, 128}, + {141, 84, 213, 252, 201, 202, 255, 219, 128, 128, 128}, + {42, 80, 160, 240, 162, 185, 255, 205, 128, 128, 128}, + }, + { + {1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128}, + {244, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128}, + {238, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128}, + }, + }, +} diff --git a/src/golang.org/x/image/vp8l/decode.go b/src/golang.org/x/image/vp8l/decode.go new file mode 100644 index 0000000000..4319487016 --- /dev/null +++ b/src/golang.org/x/image/vp8l/decode.go @@ -0,0 +1,603 @@ +// Copyright 2014 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. + +// Package vp8l implements a decoder for the VP8L lossless image format. +// +// The VP8L specification is at: +// https://developers.google.com/speed/webp/docs/riff_container +package vp8l // import "golang.org/x/image/vp8l" + +import ( + "bufio" + "errors" + "image" + "image/color" + "io" +) + +var ( + errInvalidCodeLengths = errors.New("vp8l: invalid code lengths") + errInvalidHuffmanTree = errors.New("vp8l: invalid Huffman tree") +) + +// colorCacheMultiplier is the multiplier used for the color cache hash +// function, specified in section 4.2.3. +const colorCacheMultiplier = 0x1e35a7bd + +// distanceMapTable is the look-up table for distanceMap. +var distanceMapTable = [120]uint8{ + 0x18, 0x07, 0x17, 0x19, 0x28, 0x06, 0x27, 0x29, 0x16, 0x1a, + 0x26, 0x2a, 0x38, 0x05, 0x37, 0x39, 0x15, 0x1b, 0x36, 0x3a, + 0x25, 0x2b, 0x48, 0x04, 0x47, 0x49, 0x14, 0x1c, 0x35, 0x3b, + 0x46, 0x4a, 0x24, 0x2c, 0x58, 0x45, 0x4b, 0x34, 0x3c, 0x03, + 0x57, 0x59, 0x13, 0x1d, 0x56, 0x5a, 0x23, 0x2d, 0x44, 0x4c, + 0x55, 0x5b, 0x33, 0x3d, 0x68, 0x02, 0x67, 0x69, 0x12, 0x1e, + 0x66, 0x6a, 0x22, 0x2e, 0x54, 0x5c, 0x43, 0x4d, 0x65, 0x6b, + 0x32, 0x3e, 0x78, 0x01, 0x77, 0x79, 0x53, 0x5d, 0x11, 0x1f, + 0x64, 0x6c, 0x42, 0x4e, 0x76, 0x7a, 0x21, 0x2f, 0x75, 0x7b, + 0x31, 0x3f, 0x63, 0x6d, 0x52, 0x5e, 0x00, 0x74, 0x7c, 0x41, + 0x4f, 0x10, 0x20, 0x62, 0x6e, 0x30, 0x73, 0x7d, 0x51, 0x5f, + 0x40, 0x72, 0x7e, 0x61, 0x6f, 0x50, 0x71, 0x7f, 0x60, 0x70, +} + +// distanceMap maps a LZ77 backwards reference distance to a two-dimensional +// pixel offset, specified in section 4.2.2. +func distanceMap(w int32, code uint32) int32 { + if int32(code) > int32(len(distanceMapTable)) { + return int32(code) - int32(len(distanceMapTable)) + } + distCode := int32(distanceMapTable[code-1]) + yOffset := distCode >> 4 + xOffset := 8 - distCode&0xf + if d := yOffset*w + xOffset; d >= 1 { + return d + } + return 1 +} + +// decoder holds the bit-stream for a VP8L image. +type decoder struct { + r io.ByteReader + bits uint32 + nBits uint32 +} + +// read reads the next n bits from the decoder's bit-stream. +func (d *decoder) read(n uint32) (uint32, error) { + for d.nBits < n { + c, err := d.r.ReadByte() + if err != nil { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + return 0, err + } + d.bits |= uint32(c) << d.nBits + d.nBits += 8 + } + u := d.bits & (1<>= n + d.nBits -= n + return u, nil +} + +// decodeTransform decodes the next transform and the width of the image after +// transformation (or equivalently, before inverse transformation), specified +// in section 3. +func (d *decoder) decodeTransform(w int32, h int32) (t transform, newWidth int32, err error) { + t.oldWidth = w + t.transformType, err = d.read(2) + if err != nil { + return transform{}, 0, err + } + switch t.transformType { + case transformTypePredictor, transformTypeCrossColor: + t.bits, err = d.read(3) + if err != nil { + return transform{}, 0, err + } + t.bits += 2 + t.pix, err = d.decodePix(nTiles(w, t.bits), nTiles(h, t.bits), 0, false) + if err != nil { + return transform{}, 0, err + } + case transformTypeSubtractGreen: + // No-op. + case transformTypeColorIndexing: + nColors, err := d.read(8) + if err != nil { + return transform{}, 0, err + } + nColors++ + t.bits = 0 + switch { + case nColors <= 2: + t.bits = 3 + case nColors <= 4: + t.bits = 2 + case nColors <= 16: + t.bits = 1 + } + w = nTiles(w, t.bits) + pix, err := d.decodePix(int32(nColors), 1, 4*256, false) + if err != nil { + return transform{}, 0, err + } + for p := 4; p < len(pix); p += 4 { + pix[p+0] += pix[p-4] + pix[p+1] += pix[p-3] + pix[p+2] += pix[p-2] + pix[p+3] += pix[p-1] + } + // The spec says that "if the index is equal or larger than color_table_size, + // the argb color value should be set to 0x00000000 (transparent black)." + // We re-slice up to 256 4-byte pixels. + t.pix = pix[:4*256] + } + return t, w, nil +} + +// repeatsCodeLength is the minimum code length for repeated codes. +const repeatsCodeLength = 16 + +// These magic numbers are specified at the end of section 5.2.2. +// The 3-length arrays apply to code lengths >= repeatsCodeLength. +var ( + codeLengthCodeOrder = [19]uint8{ + 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + } + repeatBits = [3]uint8{2, 3, 7} + repeatOffsets = [3]uint8{3, 3, 11} +) + +// decodeCodeLengths decodes a Huffman tree's code lengths which are themselves +// encoded via a Huffman tree, specified in section 5.2.2. +func (d *decoder) decodeCodeLengths(dst []uint32, codeLengthCodeLengths []uint32) error { + h := hTree{} + if err := h.build(codeLengthCodeLengths); err != nil { + return err + } + + maxSymbol := len(dst) + useLength, err := d.read(1) + if err != nil { + return err + } + if useLength != 0 { + n, err := d.read(3) + if err != nil { + return err + } + n = 2 + 2*n + ms, err := d.read(n) + if err != nil { + return err + } + maxSymbol = int(ms) + 2 + if maxSymbol > len(dst) { + return errInvalidCodeLengths + } + } + + // The spec says that "if code 16 [meaning repeat] is used before + // a non-zero value has been emitted, a value of 8 is repeated." + prevCodeLength := uint32(8) + + for symbol := 0; symbol < len(dst); { + if maxSymbol == 0 { + break + } + maxSymbol-- + codeLength, err := h.next(d) + if err != nil { + return err + } + if codeLength < repeatsCodeLength { + dst[symbol] = codeLength + symbol++ + if codeLength != 0 { + prevCodeLength = codeLength + } + continue + } + + repeat, err := d.read(uint32(repeatBits[codeLength-repeatsCodeLength])) + if err != nil { + return err + } + repeat += uint32(repeatOffsets[codeLength-repeatsCodeLength]) + if symbol+int(repeat) > len(dst) { + return errInvalidCodeLengths + } + // A code length of 16 repeats the previous non-zero code. + // A code length of 17 or 18 repeats zeroes. + cl := uint32(0) + if codeLength == 16 { + cl = prevCodeLength + } + for ; repeat > 0; repeat-- { + dst[symbol] = cl + symbol++ + } + } + return nil +} + +// decodeHuffmanTree decodes a Huffman tree into h. +func (d *decoder) decodeHuffmanTree(h *hTree, alphabetSize uint32) error { + useSimple, err := d.read(1) + if err != nil { + return err + } + if useSimple != 0 { + nSymbols, err := d.read(1) + if err != nil { + return err + } + nSymbols++ + firstSymbolLengthCode, err := d.read(1) + if err != nil { + return err + } + firstSymbolLengthCode = 7*firstSymbolLengthCode + 1 + var symbols [2]uint32 + symbols[0], err = d.read(firstSymbolLengthCode) + if err != nil { + return err + } + if nSymbols == 2 { + symbols[1], err = d.read(8) + if err != nil { + return err + } + } + return h.buildSimple(nSymbols, symbols, alphabetSize) + } + + nCodes, err := d.read(4) + if err != nil { + return err + } + nCodes += 4 + if int(nCodes) > len(codeLengthCodeOrder) { + return errInvalidHuffmanTree + } + codeLengthCodeLengths := [len(codeLengthCodeOrder)]uint32{} + for i := uint32(0); i < nCodes; i++ { + codeLengthCodeLengths[codeLengthCodeOrder[i]], err = d.read(3) + if err != nil { + return err + } + } + codeLengths := make([]uint32, alphabetSize) + if err = d.decodeCodeLengths(codeLengths, codeLengthCodeLengths[:]); err != nil { + return err + } + return h.build(codeLengths) +} + +const ( + huffGreen = 0 + huffRed = 1 + huffBlue = 2 + huffAlpha = 3 + huffDistance = 4 + nHuff = 5 +) + +// hGroup is an array of 5 Huffman trees. +type hGroup [nHuff]hTree + +// decodeHuffmanGroups decodes the one or more hGroups used to decode the pixel +// data. If one hGroup is used for the entire image, then hPix and hBits will +// be zero. If more than one hGroup is used, then hPix contains the meta-image +// that maps tiles to hGroup index, and hBits contains the log-2 tile size. +func (d *decoder) decodeHuffmanGroups(w int32, h int32, topLevel bool, ccBits uint32) ( + hGroups []hGroup, hPix []byte, hBits uint32, err error) { + + maxHGroupIndex := 0 + if topLevel { + useMeta, err := d.read(1) + if err != nil { + return nil, nil, 0, err + } + if useMeta != 0 { + hBits, err = d.read(3) + if err != nil { + return nil, nil, 0, err + } + hBits += 2 + hPix, err = d.decodePix(nTiles(w, hBits), nTiles(h, hBits), 0, false) + if err != nil { + return nil, nil, 0, err + } + for p := 0; p < len(hPix); p += 4 { + i := int(hPix[p])<<8 | int(hPix[p+1]) + if maxHGroupIndex < i { + maxHGroupIndex = i + } + } + } + } + hGroups = make([]hGroup, maxHGroupIndex+1) + for i := range hGroups { + for j, alphabetSize := range alphabetSizes { + if j == 0 && ccBits > 0 { + alphabetSize += 1 << ccBits + } + if err := d.decodeHuffmanTree(&hGroups[i][j], alphabetSize); err != nil { + return nil, nil, 0, err + } + } + } + return hGroups, hPix, hBits, nil +} + +const ( + nLiteralCodes = 256 + nLengthCodes = 24 + nDistanceCodes = 40 +) + +var alphabetSizes = [nHuff]uint32{ + nLiteralCodes + nLengthCodes, + nLiteralCodes, + nLiteralCodes, + nLiteralCodes, + nDistanceCodes, +} + +// decodePix decodes pixel data, specified in section 5.2.2. +func (d *decoder) decodePix(w int32, h int32, minCap int32, topLevel bool) ([]byte, error) { + // Decode the color cache parameters. + ccBits, ccShift, ccEntries := uint32(0), uint32(0), ([]uint32)(nil) + useColorCache, err := d.read(1) + if err != nil { + return nil, err + } + if useColorCache != 0 { + ccBits, err = d.read(4) + if err != nil { + return nil, err + } + if ccBits < 1 || 11 < ccBits { + return nil, errors.New("vp8l: invalid color cache parameters") + } + ccShift = 32 - ccBits + ccEntries = make([]uint32, 1<>hBits) + (x >> hBits)) + hg = &hGroups[uint32(hPix[i])<<8|uint32(hPix[i+1])] + } + + green, err := hg[huffGreen].next(d) + if err != nil { + return nil, err + } + switch { + case green < nLiteralCodes: + // We have a literal pixel. + red, err := hg[huffRed].next(d) + if err != nil { + return nil, err + } + blue, err := hg[huffBlue].next(d) + if err != nil { + return nil, err + } + alpha, err := hg[huffAlpha].next(d) + if err != nil { + return nil, err + } + pix[p+0] = uint8(red) + pix[p+1] = uint8(green) + pix[p+2] = uint8(blue) + pix[p+3] = uint8(alpha) + p += 4 + + x++ + if x == w { + x, y = 0, y+1 + } + lookupHG = hMask != 0 && x&hMask == 0 + + case green < nLiteralCodes+nLengthCodes: + // We have a LZ77 backwards reference. + length, err := d.lz77Param(green - nLiteralCodes) + if err != nil { + return nil, err + } + distSym, err := hg[huffDistance].next(d) + if err != nil { + return nil, err + } + distCode, err := d.lz77Param(distSym) + if err != nil { + return nil, err + } + dist := distanceMap(w, distCode) + pEnd := p + 4*int(length) + q := p - 4*int(dist) + qEnd := pEnd - 4*int(dist) + if p < 0 || len(pix) < pEnd || q < 0 || len(pix) < qEnd { + return nil, errors.New("vp8l: invalid LZ77 parameters") + } + for ; p < pEnd; p, q = p+1, q+1 { + pix[p] = pix[q] + } + + x += int32(length) + for x >= w { + x, y = x-w, y+1 + } + lookupHG = hMask != 0 + + default: + // We have a color cache lookup. First, insert previous pixels + // into the cache. Note that VP8L assumes ARGB order, but the + // Go image.RGBA type is in RGBA order. + for ; cachedP < p; cachedP += 4 { + argb := uint32(pix[cachedP+0])<<16 | + uint32(pix[cachedP+1])<<8 | + uint32(pix[cachedP+2])<<0 | + uint32(pix[cachedP+3])<<24 + ccEntries[(argb*colorCacheMultiplier)>>ccShift] = argb + } + green -= nLiteralCodes + nLengthCodes + if int(green) >= len(ccEntries) { + return nil, errors.New("vp8l: invalid color cache index") + } + argb := ccEntries[green] + pix[p+0] = uint8(argb >> 16) + pix[p+1] = uint8(argb >> 8) + pix[p+2] = uint8(argb >> 0) + pix[p+3] = uint8(argb >> 24) + p += 4 + + x++ + if x == w { + x, y = 0, y+1 + } + lookupHG = hMask != 0 && x&hMask == 0 + } + } + return pix, nil +} + +// lz77Param returns the next LZ77 parameter: a length or a distance, specified +// in section 4.2.2. +func (d *decoder) lz77Param(symbol uint32) (uint32, error) { + if symbol < 4 { + return symbol + 1, nil + } + extraBits := (symbol - 2) >> 1 + offset := (2 + symbol&1) << extraBits + n, err := d.read(extraBits) + if err != nil { + return 0, err + } + return offset + n + 1, nil +} + +// decodeHeader decodes the VP8L header from r. +func decodeHeader(r io.Reader) (d *decoder, w int32, h int32, err error) { + rr, ok := r.(io.ByteReader) + if !ok { + rr = bufio.NewReader(r) + } + d = &decoder{r: rr} + magic, err := d.read(8) + if err != nil { + return nil, 0, 0, err + } + if magic != 0x2f { + return nil, 0, 0, errors.New("vp8l: invalid header") + } + width, err := d.read(14) + if err != nil { + return nil, 0, 0, err + } + width++ + height, err := d.read(14) + if err != nil { + return nil, 0, 0, err + } + height++ + _, err = d.read(1) // Read and ignore the hasAlpha hint. + if err != nil { + return nil, 0, 0, err + } + version, err := d.read(3) + if err != nil { + return nil, 0, 0, err + } + if version != 0 { + return nil, 0, 0, errors.New("vp8l: invalid version") + } + return d, int32(width), int32(height), nil +} + +// DecodeConfig decodes the color model and dimensions of a VP8L image from r. +func DecodeConfig(r io.Reader) (image.Config, error) { + _, w, h, err := decodeHeader(r) + if err != nil { + return image.Config{}, err + } + return image.Config{ + ColorModel: color.NRGBAModel, + Width: int(w), + Height: int(h), + }, nil +} + +// Decode decodes a VP8L image from r. +func Decode(r io.Reader) (image.Image, error) { + d, w, h, err := decodeHeader(r) + if err != nil { + return nil, err + } + // Decode the transforms. + var ( + nTransforms int + transforms [nTransformTypes]transform + transformsSeen [nTransformTypes]bool + originalW = w + ) + for { + more, err := d.read(1) + if err != nil { + return nil, err + } + if more == 0 { + break + } + var t transform + t, w, err = d.decodeTransform(w, h) + if err != nil { + return nil, err + } + if transformsSeen[t.transformType] { + return nil, errors.New("vp8l: repeated transform") + } + transformsSeen[t.transformType] = true + transforms[nTransforms] = t + nTransforms++ + } + // Decode the transformed pixels. + pix, err := d.decodePix(w, h, 0, true) + if err != nil { + return nil, err + } + // Apply the inverse transformations. + for i := nTransforms - 1; i >= 0; i-- { + t := &transforms[i] + pix = inverseTransforms[t.transformType](t, pix, h) + } + return &image.NRGBA{ + Pix: pix, + Stride: 4 * int(originalW), + Rect: image.Rect(0, 0, int(originalW), int(h)), + }, nil +} diff --git a/src/golang.org/x/image/vp8l/huffman.go b/src/golang.org/x/image/vp8l/huffman.go new file mode 100644 index 0000000000..36368a872b --- /dev/null +++ b/src/golang.org/x/image/vp8l/huffman.go @@ -0,0 +1,245 @@ +// Copyright 2014 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. + +package vp8l + +import ( + "io" +) + +// reverseBits reverses the bits in a byte. +var reverseBits = [256]uint8{ + 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, + 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, + 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, + 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, + 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, + 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, + 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, + 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, + 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, + 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, + 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, + 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, + 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, + 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, + 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, + 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff, +} + +// hNode is a node in a Huffman tree. +type hNode struct { + // symbol is the symbol held by this node. + symbol uint32 + // children, if positive, is the hTree.nodes index of the first of + // this node's two children. Zero means an uninitialized node, + // and -1 means a leaf node. + children int32 +} + +const leafNode = -1 + +// lutSize is the log-2 size of an hTree's look-up table. +const lutSize, lutMask = 7, 1<<7 - 1 + +// hTree is a Huffman tree. +type hTree struct { + // nodes are the nodes of the Huffman tree. During construction, + // len(nodes) grows from 1 up to cap(nodes) by steps of two. + // After construction, len(nodes) == cap(nodes), and both equal + // 2*theNumberOfSymbols - 1. + nodes []hNode + // lut is a look-up table for walking the nodes. The x in lut[x] is + // the next lutSize bits in the bit-stream. The low 8 bits of lut[x] + // equals 1 plus the number of bits in the next code, or 0 if the + // next code requires more than lutSize bits. The high 24 bits are: + // - the symbol, if the code requires lutSize or fewer bits, or + // - the hTree.nodes index to start the tree traversal from, if + // the next code requires more than lutSize bits. + lut [1 << lutSize]uint32 +} + +// insert inserts into the hTree a symbol whose encoding is the least +// significant codeLength bits of code. +func (h *hTree) insert(symbol uint32, code uint32, codeLength uint32) error { + if symbol > 0xffff || codeLength > 0xfe { + return errInvalidHuffmanTree + } + baseCode := uint32(0) + if codeLength > lutSize { + baseCode = uint32(reverseBits[(code>>(codeLength-lutSize))&0xff]) >> (8 - lutSize) + } else { + baseCode = uint32(reverseBits[code&0xff]) >> (8 - codeLength) + for i := 0; i < 1<<(lutSize-codeLength); i++ { + h.lut[baseCode|uint32(i)< 0; { + codeLength-- + if int(n) > len(h.nodes) { + return errInvalidHuffmanTree + } + switch h.nodes[n].children { + case leafNode: + return errInvalidHuffmanTree + case 0: + if len(h.nodes) == cap(h.nodes) { + return errInvalidHuffmanTree + } + // Create two empty child nodes. + h.nodes[n].children = int32(len(h.nodes)) + h.nodes = h.nodes[:len(h.nodes)+2] + } + n = uint32(h.nodes[n].children) + 1&(code>>codeLength) + jump-- + if jump == 0 && h.lut[baseCode] == 0 { + h.lut[baseCode] = n << 8 + } + } + + switch h.nodes[n].children { + case leafNode: + // No-op. + case 0: + // Turn the uninitialized node into a leaf. + h.nodes[n].children = leafNode + default: + return errInvalidHuffmanTree + } + h.nodes[n].symbol = symbol + return nil +} + +// codeLengthsToCodes returns the canonical Huffman codes implied by the +// sequence of code lengths. +func codeLengthsToCodes(codeLengths []uint32) ([]uint32, error) { + maxCodeLength := uint32(0) + for _, cl := range codeLengths { + if maxCodeLength < cl { + maxCodeLength = cl + } + } + const maxAllowedCodeLength = 15 + if len(codeLengths) == 0 || maxCodeLength > maxAllowedCodeLength { + return nil, errInvalidHuffmanTree + } + histogram := [maxAllowedCodeLength + 1]uint32{} + for _, cl := range codeLengths { + histogram[cl]++ + } + currCode, nextCodes := uint32(0), [maxAllowedCodeLength + 1]uint32{} + for cl := 1; cl < len(nextCodes); cl++ { + currCode = (currCode + histogram[cl-1]) << 1 + nextCodes[cl] = currCode + } + codes := make([]uint32, len(codeLengths)) + for symbol, cl := range codeLengths { + if cl > 0 { + codes[symbol] = nextCodes[cl] + nextCodes[cl]++ + } + } + return codes, nil +} + +// build builds a canonical Huffman tree from the given code lengths. +func (h *hTree) build(codeLengths []uint32) error { + // Calculate the number of symbols. + var nSymbols, lastSymbol uint32 + for symbol, cl := range codeLengths { + if cl != 0 { + nSymbols++ + lastSymbol = uint32(symbol) + } + } + if nSymbols == 0 { + return errInvalidHuffmanTree + } + h.nodes = make([]hNode, 1, 2*nSymbols-1) + // Handle the trivial case. + if nSymbols == 1 { + if len(codeLengths) <= int(lastSymbol) { + return errInvalidHuffmanTree + } + return h.insert(lastSymbol, 0, 0) + } + // Handle the non-trivial case. + codes, err := codeLengthsToCodes(codeLengths) + if err != nil { + return err + } + for symbol, cl := range codeLengths { + if cl > 0 { + if err := h.insert(uint32(symbol), codes[symbol], cl); err != nil { + return err + } + } + } + return nil +} + +// buildSimple builds a Huffman tree with 1 or 2 symbols. +func (h *hTree) buildSimple(nSymbols uint32, symbols [2]uint32, alphabetSize uint32) error { + h.nodes = make([]hNode, 1, 2*nSymbols-1) + for i := uint32(0); i < nSymbols; i++ { + if symbols[i] >= alphabetSize { + return errInvalidHuffmanTree + } + if err := h.insert(symbols[i], i, nSymbols-1); err != nil { + return err + } + } + return nil +} + +// next returns the next Huffman-encoded symbol from the bit-stream d. +func (h *hTree) next(d *decoder) (uint32, error) { + var n uint32 + // Read enough bits so that we can use the look-up table. + if d.nBits < lutSize { + c, err := d.r.ReadByte() + if err != nil { + if err == io.EOF { + // There are no more bytes of data, but we may still be able + // to read the next symbol out of the previously read bits. + goto slowPath + } + return 0, err + } + d.bits |= uint32(c) << d.nBits + d.nBits += 8 + } + // Use the look-up table. + n = h.lut[d.bits&lutMask] + if b := n & 0xff; b != 0 { + b-- + d.bits >>= b + d.nBits -= b + return n >> 8, nil + } + n >>= 8 + d.bits >>= lutSize + d.nBits -= lutSize + +slowPath: + for h.nodes[n].children != leafNode { + if d.nBits == 0 { + c, err := d.r.ReadByte() + if err != nil { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + return 0, err + } + d.bits = uint32(c) + d.nBits = 8 + } + n = uint32(h.nodes[n].children) + 1&d.bits + d.bits >>= 1 + d.nBits-- + } + return h.nodes[n].symbol, nil +} diff --git a/src/golang.org/x/image/vp8l/transform.go b/src/golang.org/x/image/vp8l/transform.go new file mode 100644 index 0000000000..06543dacb3 --- /dev/null +++ b/src/golang.org/x/image/vp8l/transform.go @@ -0,0 +1,299 @@ +// Copyright 2014 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. + +package vp8l + +// This file deals with image transforms, specified in section 3. + +// nTiles returns the number of tiles needed to cover size pixels, where each +// tile's side is 1<> bits +} + +const ( + transformTypePredictor = 0 + transformTypeCrossColor = 1 + transformTypeSubtractGreen = 2 + transformTypeColorIndexing = 3 + nTransformTypes = 4 +) + +// transform holds the parameters for an invertible transform. +type transform struct { + // transformType is the type of the transform. + transformType uint32 + // oldWidth is the width of the image before transformation (or + // equivalently, after inverse transformation). The color-indexing + // transform can reduce the width. For example, a 50-pixel-wide + // image that only needs 4 bits (half a byte) per color index can + // be transformed into a 25-pixel-wide image. + oldWidth int32 + // bits is the log-2 size of the transform's tiles, for the predictor + // and cross-color transforms. 8>>bits is the number of bits per + // color index, for the color-index transform. + bits uint32 + // pix is the tile values, for the predictor and cross-color + // transforms, and the color palette, for the color-index transform. + pix []byte +} + +var inverseTransforms = [nTransformTypes]func(*transform, []byte, int32) []byte{ + transformTypePredictor: inversePredictor, + transformTypeCrossColor: inverseCrossColor, + transformTypeSubtractGreen: inverseSubtractGreen, + transformTypeColorIndexing: inverseColorIndexing, +} + +func inversePredictor(t *transform, pix []byte, h int32) []byte { + if t.oldWidth == 0 || h == 0 { + return pix + } + // The first pixel's predictor is mode 0 (opaque black). + pix[3] += 0xff + p, mask := int32(4), int32(1)<> t.bits) * tilesPerRow + predictorMode := t.pix[q+1] & 0x0f + q += 4 + for x := int32(1); x < t.oldWidth; x++ { + if x&mask == 0 { + predictorMode = t.pix[q+1] & 0x0f + q += 4 + } + switch predictorMode { + case 0: // Opaque black. + pix[p+3] += 0xff + + case 1: // L. + pix[p+0] += pix[p-4] + pix[p+1] += pix[p-3] + pix[p+2] += pix[p-2] + pix[p+3] += pix[p-1] + + case 2: // T. + pix[p+0] += pix[top+0] + pix[p+1] += pix[top+1] + pix[p+2] += pix[top+2] + pix[p+3] += pix[top+3] + + case 3: // TR. + pix[p+0] += pix[top+4] + pix[p+1] += pix[top+5] + pix[p+2] += pix[top+6] + pix[p+3] += pix[top+7] + + case 4: // TL. + pix[p+0] += pix[top-4] + pix[p+1] += pix[top-3] + pix[p+2] += pix[top-2] + pix[p+3] += pix[top-1] + + case 5: // Average2(Average2(L, TR), T). + pix[p+0] += avg2(avg2(pix[p-4], pix[top+4]), pix[top+0]) + pix[p+1] += avg2(avg2(pix[p-3], pix[top+5]), pix[top+1]) + pix[p+2] += avg2(avg2(pix[p-2], pix[top+6]), pix[top+2]) + pix[p+3] += avg2(avg2(pix[p-1], pix[top+7]), pix[top+3]) + + case 6: // Average2(L, TL). + pix[p+0] += avg2(pix[p-4], pix[top-4]) + pix[p+1] += avg2(pix[p-3], pix[top-3]) + pix[p+2] += avg2(pix[p-2], pix[top-2]) + pix[p+3] += avg2(pix[p-1], pix[top-1]) + + case 7: // Average2(L, T). + pix[p+0] += avg2(pix[p-4], pix[top+0]) + pix[p+1] += avg2(pix[p-3], pix[top+1]) + pix[p+2] += avg2(pix[p-2], pix[top+2]) + pix[p+3] += avg2(pix[p-1], pix[top+3]) + + case 8: // Average2(TL, T). + pix[p+0] += avg2(pix[top-4], pix[top+0]) + pix[p+1] += avg2(pix[top-3], pix[top+1]) + pix[p+2] += avg2(pix[top-2], pix[top+2]) + pix[p+3] += avg2(pix[top-1], pix[top+3]) + + case 9: // Average2(T, TR). + pix[p+0] += avg2(pix[top+0], pix[top+4]) + pix[p+1] += avg2(pix[top+1], pix[top+5]) + pix[p+2] += avg2(pix[top+2], pix[top+6]) + pix[p+3] += avg2(pix[top+3], pix[top+7]) + + case 10: // Average2(Average2(L, TL), Average2(T, TR)). + pix[p+0] += avg2(avg2(pix[p-4], pix[top-4]), avg2(pix[top+0], pix[top+4])) + pix[p+1] += avg2(avg2(pix[p-3], pix[top-3]), avg2(pix[top+1], pix[top+5])) + pix[p+2] += avg2(avg2(pix[p-2], pix[top-2]), avg2(pix[top+2], pix[top+6])) + pix[p+3] += avg2(avg2(pix[p-1], pix[top-1]), avg2(pix[top+3], pix[top+7])) + + case 11: // Select(L, T, TL). + l0 := int32(pix[p-4]) + l1 := int32(pix[p-3]) + l2 := int32(pix[p-2]) + l3 := int32(pix[p-1]) + c0 := int32(pix[top-4]) + c1 := int32(pix[top-3]) + c2 := int32(pix[top-2]) + c3 := int32(pix[top-1]) + t0 := int32(pix[top+0]) + t1 := int32(pix[top+1]) + t2 := int32(pix[top+2]) + t3 := int32(pix[top+3]) + l := abs(c0-t0) + abs(c1-t1) + abs(c2-t2) + abs(c3-t3) + t := abs(c0-l0) + abs(c1-l1) + abs(c2-l2) + abs(c3-l3) + if l < t { + pix[p+0] += uint8(l0) + pix[p+1] += uint8(l1) + pix[p+2] += uint8(l2) + pix[p+3] += uint8(l3) + } else { + pix[p+0] += uint8(t0) + pix[p+1] += uint8(t1) + pix[p+2] += uint8(t2) + pix[p+3] += uint8(t3) + } + + case 12: // ClampAddSubtractFull(L, T, TL). + pix[p+0] += clampAddSubtractFull(pix[p-4], pix[top+0], pix[top-4]) + pix[p+1] += clampAddSubtractFull(pix[p-3], pix[top+1], pix[top-3]) + pix[p+2] += clampAddSubtractFull(pix[p-2], pix[top+2], pix[top-2]) + pix[p+3] += clampAddSubtractFull(pix[p-1], pix[top+3], pix[top-1]) + + case 13: // ClampAddSubtractHalf(Average2(L, T), TL). + pix[p+0] += clampAddSubtractHalf(avg2(pix[p-4], pix[top+0]), pix[top-4]) + pix[p+1] += clampAddSubtractHalf(avg2(pix[p-3], pix[top+1]), pix[top-3]) + pix[p+2] += clampAddSubtractHalf(avg2(pix[p-2], pix[top+2]), pix[top-2]) + pix[p+3] += clampAddSubtractHalf(avg2(pix[p-1], pix[top+3]), pix[top-1]) + } + p, top = p+4, top+4 + } + } + return pix +} + +func inverseCrossColor(t *transform, pix []byte, h int32) []byte { + var greenToRed, greenToBlue, redToBlue int32 + p, mask, tilesPerRow := int32(0), int32(1)<> t.bits) * tilesPerRow + for x := int32(0); x < t.oldWidth; x++ { + if x&mask == 0 { + redToBlue = int32(int8(t.pix[q+0])) + greenToBlue = int32(int8(t.pix[q+1])) + greenToRed = int32(int8(t.pix[q+2])) + q += 4 + } + red := pix[p+0] + green := pix[p+1] + blue := pix[p+2] + red += uint8(uint32(greenToRed*int32(int8(green))) >> 5) + blue += uint8(uint32(greenToBlue*int32(int8(green))) >> 5) + blue += uint8(uint32(redToBlue*int32(int8(red))) >> 5) + pix[p+0] = red + pix[p+2] = blue + p += 4 + } + } + return pix +} + +func inverseSubtractGreen(t *transform, pix []byte, h int32) []byte { + for p := 0; p < len(pix); p += 4 { + green := pix[p+1] + pix[p+0] += green + pix[p+2] += green + } + return pix +} + +func inverseColorIndexing(t *transform, pix []byte, h int32) []byte { + if t.bits == 0 { + for p := 0; p < len(pix); p += 4 { + i := 4 * uint32(pix[p+1]) + pix[p+0] = t.pix[i+0] + pix[p+1] = t.pix[i+1] + pix[p+2] = t.pix[i+2] + pix[p+3] = t.pix[i+3] + } + return pix + } + + vMask, xMask, bitsPerPixel := uint32(0), int32(0), uint32(8>>t.bits) + switch t.bits { + case 1: + vMask, xMask = 0x0f, 0x01 + case 2: + vMask, xMask = 0x03, 0x03 + case 3: + vMask, xMask = 0x01, 0x07 + } + + d, p, v, dst := 0, 0, uint32(0), make([]byte, 4*t.oldWidth*h) + for y := int32(0); y < h; y++ { + for x := int32(0); x < t.oldWidth; x++ { + if x&xMask == 0 { + v = uint32(pix[p+1]) + p += 4 + } + + i := 4 * (v & vMask) + dst[d+0] = t.pix[i+0] + dst[d+1] = t.pix[i+1] + dst[d+2] = t.pix[i+2] + dst[d+3] = t.pix[i+3] + d += 4 + + v >>= bitsPerPixel + } + } + return dst +} + +func abs(x int32) int32 { + if x < 0 { + return -x + } + return x +} + +func avg2(a, b uint8) uint8 { + return uint8((int32(a) + int32(b)) / 2) +} + +func clampAddSubtractFull(a, b, c uint8) uint8 { + x := int32(a) + int32(b) - int32(c) + if x < 0 { + return 0 + } + if x > 255 { + return 255 + } + return uint8(x) +} + +func clampAddSubtractHalf(a, b uint8) uint8 { + x := int32(a) + (int32(a)-int32(b))/2 + if x < 0 { + return 0 + } + if x > 255 { + return 255 + } + return uint8(x) +} diff --git a/src/golang.org/x/image/webp/decode.go b/src/golang.org/x/image/webp/decode.go new file mode 100644 index 0000000000..60fb5560c3 --- /dev/null +++ b/src/golang.org/x/image/webp/decode.go @@ -0,0 +1,275 @@ +// 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. + +// Package webp implements a decoder for WEBP images. +// +// WEBP is defined at: +// https://developers.google.com/speed/webp/docs/riff_container +package webp // import "golang.org/x/image/webp" + +import ( + "bytes" + "errors" + "image" + "image/color" + "io" + + "golang.org/x/image/riff" + "golang.org/x/image/vp8" + "golang.org/x/image/vp8l" + "golang.org/x/image/webp/nycbcra" +) + +var errInvalidFormat = errors.New("webp: invalid format") + +var ( + fccALPH = riff.FourCC{'A', 'L', 'P', 'H'} + fccVP8 = riff.FourCC{'V', 'P', '8', ' '} + fccVP8L = riff.FourCC{'V', 'P', '8', 'L'} + fccVP8X = riff.FourCC{'V', 'P', '8', 'X'} + fccWEBP = riff.FourCC{'W', 'E', 'B', 'P'} +) + +func decode(r io.Reader, configOnly bool) (image.Image, image.Config, error) { + formType, riffReader, err := riff.NewReader(r) + if err != nil { + return nil, image.Config{}, err + } + if formType != fccWEBP { + return nil, image.Config{}, errInvalidFormat + } + + var ( + alpha []byte + alphaStride int + wantAlpha bool + widthMinusOne uint32 + heightMinusOne uint32 + buf [10]byte + ) + for { + chunkID, chunkLen, chunkData, err := riffReader.Next() + if err == io.EOF { + err = errInvalidFormat + } + if err != nil { + return nil, image.Config{}, err + } + + switch chunkID { + case fccALPH: + if !wantAlpha { + return nil, image.Config{}, errInvalidFormat + } + wantAlpha = false + // Read the Pre-processing | Filter | Compression byte. + if _, err := io.ReadFull(chunkData, buf[:1]); err != nil { + if err == io.EOF { + err = errInvalidFormat + } + return nil, image.Config{}, err + } + alpha, alphaStride, err = readAlpha(chunkData, widthMinusOne, heightMinusOne, buf[0]&0x03) + if err != nil { + return nil, image.Config{}, err + } + unfilterAlpha(alpha, alphaStride, (buf[0]>>2)&0x03) + + case fccVP8: + if wantAlpha || int32(chunkLen) < 0 { + return nil, image.Config{}, errInvalidFormat + } + d := vp8.NewDecoder() + d.Init(chunkData, int(chunkLen)) + fh, err := d.DecodeFrameHeader() + if err != nil { + return nil, image.Config{}, err + } + if configOnly { + return nil, image.Config{ + ColorModel: color.YCbCrModel, + Width: fh.Width, + Height: fh.Height, + }, nil + } + m, err := d.DecodeFrame() + if err != nil { + return nil, image.Config{}, err + } + if alpha != nil { + return &nycbcra.Image{ + YCbCr: *m, + A: alpha, + AStride: alphaStride, + }, image.Config{}, nil + } + return m, image.Config{}, nil + + case fccVP8L: + if wantAlpha || alpha != nil { + return nil, image.Config{}, errInvalidFormat + } + if configOnly { + c, err := vp8l.DecodeConfig(chunkData) + return nil, c, err + } + m, err := vp8l.Decode(chunkData) + return m, image.Config{}, err + + case fccVP8X: + if chunkLen != 10 { + return nil, image.Config{}, errInvalidFormat + } + if _, err := io.ReadFull(chunkData, buf[:10]); err != nil { + return nil, image.Config{}, err + } + const ( + animationBit = 1 << 1 + xmpMetadataBit = 1 << 2 + exifMetadataBit = 1 << 3 + alphaBit = 1 << 4 + iccProfileBit = 1 << 5 + ) + if buf[0] != alphaBit { + return nil, image.Config{}, errors.New("webp: non-Alpha VP8X is not implemented") + } + widthMinusOne = uint32(buf[4]) | uint32(buf[5])<<8 | uint32(buf[6])<<16 + heightMinusOne = uint32(buf[7]) | uint32(buf[8])<<8 | uint32(buf[9])<<16 + if configOnly { + return nil, image.Config{ + ColorModel: nycbcra.ColorModel, + Width: int(widthMinusOne) + 1, + Height: int(heightMinusOne) + 1, + }, nil + } + wantAlpha = true + + default: + return nil, image.Config{}, errInvalidFormat + } + } +} + +func readAlpha(chunkData io.Reader, widthMinusOne, heightMinusOne uint32, compression byte) ( + alpha []byte, alphaStride int, err error) { + + switch compression { + case 0: + w := int(widthMinusOne) + 1 + h := int(heightMinusOne) + 1 + alpha = make([]byte, w*h) + if _, err := io.ReadFull(chunkData, alpha); err != nil { + return nil, 0, err + } + return alpha, w, nil + + case 1: + // Read the VP8L-compressed alpha values. First, synthesize a 5-byte VP8L header: + // a 1-byte magic number, a 14-bit widthMinusOne, a 14-bit heightMinusOne, + // a 1-bit (ignored, zero) alphaIsUsed and a 3-bit (zero) version. + // TODO(nigeltao): be more efficient than decoding an *image.NRGBA just to + // extract the green values to a separately allocated []byte. Fixing this + // will require changes to the vp8l package's API. + if widthMinusOne > 0x3fff || heightMinusOne > 0x3fff { + return nil, 0, errors.New("webp: invalid format") + } + alphaImage, err := vp8l.Decode(io.MultiReader( + bytes.NewReader([]byte{ + 0x2f, // VP8L magic number. + uint8(widthMinusOne), + uint8(widthMinusOne>>8) | uint8(heightMinusOne<<6), + uint8(heightMinusOne >> 2), + uint8(heightMinusOne >> 10), + }), + chunkData, + )) + if err != nil { + return nil, 0, err + } + // The green values of the inner NRGBA image are the alpha values of the + // outer NYCbCrA image. + pix := alphaImage.(*image.NRGBA).Pix + alpha = make([]byte, len(pix)/4) + for i := range alpha { + alpha[i] = pix[4*i+1] + } + return alpha, int(widthMinusOne) + 1, nil + } + return nil, 0, errInvalidFormat +} + +func unfilterAlpha(alpha []byte, alphaStride int, filter byte) { + if len(alpha) == 0 || alphaStride == 0 { + return + } + switch filter { + case 1: // Horizontal filter. + for i := 1; i < alphaStride; i++ { + alpha[i] += alpha[i-1] + } + for i := alphaStride; i < len(alpha); i += alphaStride { + // The first column is equivalent to the vertical filter. + alpha[i] += alpha[i-alphaStride] + + for j := 1; j < alphaStride; j++ { + alpha[i+j] += alpha[i+j-1] + } + } + + case 2: // Vertical filter. + // The first row is equivalent to the horizontal filter. + for i := 1; i < alphaStride; i++ { + alpha[i] += alpha[i-1] + } + + for i := alphaStride; i < len(alpha); i++ { + alpha[i] += alpha[i-alphaStride] + } + + case 3: // Gradient filter. + // The first row is equivalent to the horizontal filter. + for i := 1; i < alphaStride; i++ { + alpha[i] += alpha[i-1] + } + + for i := alphaStride; i < len(alpha); i += alphaStride { + // The first column is equivalent to the vertical filter. + alpha[i] += alpha[i-alphaStride] + + // The interior is predicted on the three top/left pixels. + for j := 1; j < alphaStride; j++ { + c := int(alpha[i+j-alphaStride-1]) + b := int(alpha[i+j-alphaStride]) + a := int(alpha[i+j-1]) + x := a + b - c + if x < 0 { + x = 0 + } else if x > 255 { + x = 255 + } + alpha[i+j] += uint8(x) + } + } + } +} + +// Decode reads a WEBP image from r and returns it as an image.Image. +func Decode(r io.Reader) (image.Image, error) { + m, _, err := decode(r, false) + if err != nil { + return nil, err + } + return m, err +} + +// DecodeConfig returns the color model and dimensions of a WEBP image without +// decoding the entire image. +func DecodeConfig(r io.Reader) (image.Config, error) { + _, c, err := decode(r, true) + return c, err +} + +func init() { + image.RegisterFormat("webp", "RIFF????WEBPVP8", Decode, DecodeConfig) +} diff --git a/src/golang.org/x/image/webp/decode_test.go b/src/golang.org/x/image/webp/decode_test.go new file mode 100644 index 0000000000..4b69f90d79 --- /dev/null +++ b/src/golang.org/x/image/webp/decode_test.go @@ -0,0 +1,296 @@ +// Copyright 2014 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. + +package webp + +import ( + "bytes" + "fmt" + "image" + "image/png" + "io/ioutil" + "os" + "strings" + "testing" + + "golang.org/x/image/webp/nycbcra" +) + +// hex is like fmt.Sprintf("% x", x) but also inserts dots every 16 bytes, to +// delineate VP8 macroblock boundaries. +func hex(x []byte) string { + buf := new(bytes.Buffer) + for len(x) > 0 { + n := len(x) + if n > 16 { + n = 16 + } + fmt.Fprintf(buf, " . % x", x[:n]) + x = x[n:] + } + return buf.String() +} + +func testDecodeLossy(t *testing.T, tc string, withAlpha bool) { + webpFilename := "../testdata/" + tc + ".lossy.webp" + pngFilename := webpFilename + ".ycbcr.png" + if withAlpha { + webpFilename = "../testdata/" + tc + ".lossy-with-alpha.webp" + pngFilename = webpFilename + ".nycbcra.png" + } + + f0, err := os.Open(webpFilename) + if err != nil { + t.Errorf("%s: Open WEBP: %v", tc, err) + return + } + defer f0.Close() + img0, err := Decode(f0) + if err != nil { + t.Errorf("%s: Decode WEBP: %v", tc, err) + return + } + + var ( + m0 *image.YCbCr + a0 *nycbcra.Image + ok bool + ) + if withAlpha { + a0, ok = img0.(*nycbcra.Image) + if ok { + m0 = &a0.YCbCr + } + } else { + m0, ok = img0.(*image.YCbCr) + } + if !ok || m0.SubsampleRatio != image.YCbCrSubsampleRatio420 { + t.Errorf("%s: decoded WEBP image is not a 4:2:0 YCbCr or 4:2:0 NYCbCrA", tc) + return + } + // w2 and h2 are the half-width and half-height, rounded up. + w, h := m0.Bounds().Dx(), m0.Bounds().Dy() + w2, h2 := int((w+1)/2), int((h+1)/2) + + f1, err := os.Open(pngFilename) + if err != nil { + t.Errorf("%s: Open PNG: %v", tc, err) + return + } + defer f1.Close() + img1, err := png.Decode(f1) + if err != nil { + t.Errorf("%s: Open PNG: %v", tc, err) + return + } + + // The split-into-YCbCr-planes golden image is a 2*w2 wide and h+h2 high + // (or 2*h+h2 high, if with Alpha) gray image arranged in IMC4 format: + // YYYY + // YYYY + // BBRR + // AAAA + // See http://www.fourcc.org/yuv.php#IMC4 + pngW, pngH := 2*w2, h+h2 + if withAlpha { + pngH += h + } + if got, want := img1.Bounds(), image.Rect(0, 0, pngW, pngH); got != want { + t.Errorf("%s: bounds0: got %v, want %v", tc, got, want) + return + } + m1, ok := img1.(*image.Gray) + if !ok { + t.Errorf("%s: decoded PNG image is not a Gray", tc) + return + } + + type plane struct { + name string + m0Pix []uint8 + m0Stride int + m1Rect image.Rectangle + } + planes := []plane{ + {"Y", m0.Y, m0.YStride, image.Rect(0, 0, w, h)}, + {"Cb", m0.Cb, m0.CStride, image.Rect(0*w2, h, 1*w2, h+h2)}, + {"Cr", m0.Cr, m0.CStride, image.Rect(1*w2, h, 2*w2, h+h2)}, + } + if withAlpha { + planes = append(planes, plane{ + "A", a0.A, a0.AStride, image.Rect(0, h+h2, w, 2*h+h2), + }) + } + + for _, plane := range planes { + dx := plane.m1Rect.Dx() + nDiff, diff := 0, make([]byte, dx) + for j, y := 0, plane.m1Rect.Min.Y; y < plane.m1Rect.Max.Y; j, y = j+1, y+1 { + got := plane.m0Pix[j*plane.m0Stride:][:dx] + want := m1.Pix[y*m1.Stride+plane.m1Rect.Min.X:][:dx] + if bytes.Equal(got, want) { + continue + } + nDiff++ + if nDiff > 10 { + t.Errorf("%s: %s plane: more rows differ", tc, plane.name) + break + } + for i := range got { + diff[i] = got[i] - want[i] + } + t.Errorf("%s: %s plane: m0 row %d, m1 row %d\ngot %s\nwant%s\ndiff%s", + tc, plane.name, j, y, hex(got), hex(want), hex(diff)) + } + } +} + +func TestDecodeVP8(t *testing.T) { + testCases := []string{ + "blue-purple-pink", + "blue-purple-pink-large.no-filter", + "blue-purple-pink-large.simple-filter", + "blue-purple-pink-large.normal-filter", + "video-001", + "yellow_rose", + } + + for _, tc := range testCases { + testDecodeLossy(t, tc, false) + } +} + +func TestDecodeVP8XAlpha(t *testing.T) { + testCases := []string{ + "yellow_rose", + } + + for _, tc := range testCases { + testDecodeLossy(t, tc, true) + } +} + +func TestDecodeVP8L(t *testing.T) { + testCases := []string{ + "blue-purple-pink", + "blue-purple-pink-large", + "gopher-doc.1bpp", + "gopher-doc.2bpp", + "gopher-doc.4bpp", + "gopher-doc.8bpp", + "tux", + "yellow_rose", + } + +loop: + for _, tc := range testCases { + f0, err := os.Open("../testdata/" + tc + ".lossless.webp") + if err != nil { + t.Errorf("%s: Open WEBP: %v", tc, err) + continue + } + defer f0.Close() + img0, err := Decode(f0) + if err != nil { + t.Errorf("%s: Decode WEBP: %v", tc, err) + continue + } + m0, ok := img0.(*image.NRGBA) + if !ok { + t.Errorf("%s: WEBP image is %T, want *image.NRGBA", tc, img0) + continue + } + + f1, err := os.Open("../testdata/" + tc + ".png") + if err != nil { + t.Errorf("%s: Open PNG: %v", tc, err) + continue + } + defer f1.Close() + img1, err := png.Decode(f1) + if err != nil { + t.Errorf("%s: Decode PNG: %v", tc, err) + continue + } + m1, ok := img1.(*image.NRGBA) + if !ok { + rgba1, ok := img1.(*image.RGBA) + if !ok { + t.Fatalf("%s: PNG image is %T, want *image.NRGBA", tc, img1) + continue + } + if !rgba1.Opaque() { + t.Fatalf("%s: PNG image is non-opaque *image.RGBA, want *image.NRGBA", tc) + continue + } + // The image is fully opaque, so we can re-interpret the RGBA pixels + // as NRGBA pixels. + m1 = &image.NRGBA{ + Pix: rgba1.Pix, + Stride: rgba1.Stride, + Rect: rgba1.Rect, + } + } + + b0, b1 := m0.Bounds(), m1.Bounds() + if b0 != b1 { + t.Errorf("%s: bounds: got %v, want %v", tc, b0, b1) + continue + } + for i := range m0.Pix { + if m0.Pix[i] != m1.Pix[i] { + y := i / m0.Stride + x := (i - y*m0.Stride) / 4 + i = 4 * (y*m0.Stride + x) + t.Errorf("%s: at (%d, %d):\ngot %02x %02x %02x %02x\nwant %02x %02x %02x %02x", + tc, x, y, + m0.Pix[i+0], m0.Pix[i+1], m0.Pix[i+2], m0.Pix[i+3], + m1.Pix[i+0], m1.Pix[i+1], m1.Pix[i+2], m1.Pix[i+3], + ) + continue loop + } + } + } +} + +// TestDecodePartitionTooLarge tests that decoding a malformed WEBP image +// doesn't try to allocate an unreasonable amount of memory. This WEBP image +// claims a RIFF chunk length of 0x12345678 bytes (291 MiB) compressed, +// independent of the actual image size (0 pixels wide * 0 pixels high). +// +// This is based on golang.org/issue/10790. +func TestDecodePartitionTooLarge(t *testing.T) { + data := "RIFF\xff\xff\xff\x7fWEBPVP8 " + + "\x78\x56\x34\x12" + // RIFF chunk length. + "\xbd\x01\x00\x14\x00\x00\xb2\x34\x0a\x9d\x01\x2a\x96\x00\x67\x00" + _, err := Decode(strings.NewReader(data)) + if err == nil { + t.Fatal("got nil error, want non-nil") + } + if got, want := err.Error(), "too much data"; !strings.Contains(got, want) { + t.Fatalf("got error %q, want something containing %q", got, want) + } +} + +func benchmarkDecode(b *testing.B, filename string) { + data, err := ioutil.ReadFile("../testdata/blue-purple-pink-large." + filename + ".webp") + if err != nil { + b.Fatal(err) + } + s := string(data) + cfg, err := DecodeConfig(strings.NewReader(s)) + if err != nil { + b.Fatal(err) + } + b.SetBytes(int64(cfg.Width * cfg.Height * 4)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Decode(strings.NewReader(s)) + } +} + +func BenchmarkDecodeVP8NoFilter(b *testing.B) { benchmarkDecode(b, "no-filter.lossy") } +func BenchmarkDecodeVP8SimpleFilter(b *testing.B) { benchmarkDecode(b, "simple-filter.lossy") } +func BenchmarkDecodeVP8NormalFilter(b *testing.B) { benchmarkDecode(b, "normal-filter.lossy") } +func BenchmarkDecodeVP8L(b *testing.B) { benchmarkDecode(b, "lossless") } diff --git a/src/golang.org/x/image/webp/nycbcra/nycbcra.go b/src/golang.org/x/image/webp/nycbcra/nycbcra.go new file mode 100644 index 0000000000..2e1fe05f34 --- /dev/null +++ b/src/golang.org/x/image/webp/nycbcra/nycbcra.go @@ -0,0 +1,186 @@ +// Copyright 2014 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. + +// Package nycbcra provides non-alpha-premultiplied Y'CbCr-with-alpha image and +// color types. +package nycbcra // import "golang.org/x/image/webp/nycbcra" + +import ( + "image" + "image/color" +) + +// TODO: move this to the standard image and image/color packages, so that the +// image/draw package can have fast-path code. Moving would rename: +// nycbcra.Color to color.NYCbCrA +// nycbcra.ColorModel to color.NYCbCrAModel +// nycbcra.Image to image.NYCbCrA + +// Color represents a non-alpha-premultiplied Y'CbCr-with-alpha color, having +// 8 bits each for one luma, two chroma and one alpha component. +type Color struct { + color.YCbCr + A uint8 +} + +func (c Color) RGBA() (r, g, b, a uint32) { + r8, g8, b8 := color.YCbCrToRGB(c.Y, c.Cb, c.Cr) + a = uint32(c.A) * 0x101 + r = uint32(r8) * 0x101 * a / 0xffff + g = uint32(g8) * 0x101 * a / 0xffff + b = uint32(b8) * 0x101 * a / 0xffff + return +} + +// ColorModel is the Model for non-alpha-premultiplied Y'CbCr-with-alpha colors. +var ColorModel color.Model = color.ModelFunc(nYCbCrAModel) + +func nYCbCrAModel(c color.Color) color.Color { + switch c := c.(type) { + case Color: + return c + case color.YCbCr: + return Color{c, 0xff} + } + r, g, b, a := c.RGBA() + + // Convert from alpha-premultiplied to non-alpha-premultiplied. + if a != 0 { + r = (r * 0xffff) / a + g = (g * 0xffff) / a + b = (b * 0xffff) / a + } + + y, u, v := color.RGBToYCbCr(uint8(r>>8), uint8(g>>8), uint8(b>>8)) + return Color{color.YCbCr{Y: y, Cb: u, Cr: v}, uint8(a >> 8)} +} + +// Image is an in-memory image of non-alpha-premultiplied Y'CbCr-with-alpha +// colors. A and AStride are analogous to the Y and YStride fields of the +// embedded YCbCr. +type Image struct { + image.YCbCr + A []uint8 + AStride int +} + +func (p *Image) ColorModel() color.Model { + return ColorModel +} + +func (p *Image) At(x, y int) color.Color { + return p.NYCbCrAAt(x, y) +} + +func (p *Image) NYCbCrAAt(x, y int) Color { + if !(image.Point{X: x, Y: y}.In(p.Rect)) { + return Color{} + } + yi := p.YOffset(x, y) + ci := p.COffset(x, y) + ai := p.AOffset(x, y) + return Color{ + color.YCbCr{ + Y: p.Y[yi], + Cb: p.Cb[ci], + Cr: p.Cr[ci], + }, + p.A[ai], + } +} + +// AOffset returns the index of the first element of A that corresponds to +// the pixel at (x, y). +func (p *Image) AOffset(x, y int) int { + return (y-p.Rect.Min.Y)*p.AStride + (x - p.Rect.Min.X) +} + +// SubImage returns an image representing the portion of the image p visible +// through r. The returned value shares pixels with the original image. +func (p *Image) SubImage(r image.Rectangle) image.Image { + // TODO: share code with image.NewYCbCr when this type moves into the + // standard image package. + r = r.Intersect(p.Rect) + // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside + // either r1 or r2 if the intersection is empty. Without explicitly checking for + // this, the Pix[i:] expression below can panic. + if r.Empty() { + return &Image{ + YCbCr: image.YCbCr{ + SubsampleRatio: p.SubsampleRatio, + }, + } + } + yi := p.YOffset(r.Min.X, r.Min.Y) + ci := p.COffset(r.Min.X, r.Min.Y) + ai := p.AOffset(r.Min.X, r.Min.Y) + return &Image{ + YCbCr: image.YCbCr{ + Y: p.Y[yi:], + Cb: p.Cb[ci:], + Cr: p.Cr[ci:], + SubsampleRatio: p.SubsampleRatio, + YStride: p.YStride, + CStride: p.CStride, + Rect: r, + }, + A: p.A[ai:], + AStride: p.AStride, + } +} + +// Opaque scans the entire image and reports whether it is fully opaque. +func (p *Image) Opaque() bool { + if p.Rect.Empty() { + return true + } + i0, i1 := 0, p.Rect.Dx() + for y := p.Rect.Min.Y; y < p.Rect.Max.Y; y++ { + for _, a := range p.A[i0:i1] { + if a != 0xff { + return false + } + } + i0 += p.AStride + i1 += p.AStride + } + return true +} + +// New returns a new Image with the given bounds and subsample ratio. +func New(r image.Rectangle, subsampleRatio image.YCbCrSubsampleRatio) *Image { + // TODO: share code with image.NewYCbCr when this type moves into the + // standard image package. + w, h, cw, ch := r.Dx(), r.Dy(), 0, 0 + switch subsampleRatio { + case image.YCbCrSubsampleRatio422: + cw = (r.Max.X+1)/2 - r.Min.X/2 + ch = h + case image.YCbCrSubsampleRatio420: + cw = (r.Max.X+1)/2 - r.Min.X/2 + ch = (r.Max.Y+1)/2 - r.Min.Y/2 + case image.YCbCrSubsampleRatio440: + cw = w + ch = (r.Max.Y+1)/2 - r.Min.Y/2 + default: + // Default to 4:4:4 subsampling. + cw = w + ch = h + } + b := make([]byte, 2*w*h+2*cw*ch) + // TODO: use s[i:j:k] notation to set the cap. + return &Image{ + YCbCr: image.YCbCr{ + Y: b[:w*h], + Cb: b[w*h+0*cw*ch : w*h+1*cw*ch], + Cr: b[w*h+1*cw*ch : w*h+2*cw*ch], + SubsampleRatio: subsampleRatio, + YStride: w, + CStride: cw, + Rect: r, + }, + A: b[w*h+2*cw*ch:], + AStride: w, + } +} diff --git a/src/golang.org/x/mobile/.gitattributes b/src/golang.org/x/mobile/.gitattributes new file mode 100644 index 0000000000..d2f212e5da --- /dev/null +++ b/src/golang.org/x/mobile/.gitattributes @@ -0,0 +1,10 @@ +# Treat all files in this repo as binary, with no git magic updating +# line endings. Windows users contributing to Go will need to use a +# modern version of git and editors capable of LF line endings. +# +# We'll prevent accidental CRLF line endings from entering the repo +# via the git-review gofmt checks. +# +# See golang.org/issue/9281 + +* -text diff --git a/src/golang.org/x/mobile/.gitignore b/src/golang.org/x/mobile/.gitignore new file mode 100644 index 0000000000..b1feb1bf0e --- /dev/null +++ b/src/golang.org/x/mobile/.gitignore @@ -0,0 +1,4 @@ +# Add no patterns to .hgignore except for files generated by the build. +last-change +*.apk +*.app \ No newline at end of file diff --git a/src/golang.org/x/mobile/AUTHORS b/src/golang.org/x/mobile/AUTHORS new file mode 100644 index 0000000000..15167cd746 --- /dev/null +++ b/src/golang.org/x/mobile/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at http://tip.golang.org/AUTHORS. diff --git a/src/golang.org/x/mobile/CONTRIBUTING.md b/src/golang.org/x/mobile/CONTRIBUTING.md new file mode 100644 index 0000000000..88dff59bc7 --- /dev/null +++ b/src/golang.org/x/mobile/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# Contributing to Go + +Go is an open source project. + +It is the work of hundreds of contributors. We appreciate your help! + + +## Filing issues + +When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions: + +1. What version of Go are you using (`go version`)? +2. What operating system and processor architecture are you using? +3. What did you do? +4. What did you expect to see? +5. What did you see instead? + +General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker. +The gophers there will answer or ask you to file an issue if you've tripped over a bug. + +## Contributing code + +Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html) +before sending patches. + +**We do not accept GitHub pull requests** +(we use [Gerrit](https://code.google.com/p/gerrit/) instead for code review). + +Unless otherwise noted, the Go source files are distributed under +the BSD-style license found in the LICENSE file. + diff --git a/src/golang.org/x/mobile/CONTRIBUTORS b/src/golang.org/x/mobile/CONTRIBUTORS new file mode 100644 index 0000000000..1c4577e968 --- /dev/null +++ b/src/golang.org/x/mobile/CONTRIBUTORS @@ -0,0 +1,3 @@ +# This source code was written by the Go contributors. +# The master list of contributors is in the main Go distribution, +# visible at http://tip.golang.org/CONTRIBUTORS. diff --git a/src/golang.org/x/mobile/LICENSE b/src/golang.org/x/mobile/LICENSE new file mode 100644 index 0000000000..6a66aea5ea --- /dev/null +++ b/src/golang.org/x/mobile/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/golang.org/x/mobile/PATENTS b/src/golang.org/x/mobile/PATENTS new file mode 100644 index 0000000000..733099041f --- /dev/null +++ b/src/golang.org/x/mobile/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google 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, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/src/golang.org/x/mobile/README.md b/src/golang.org/x/mobile/README.md new file mode 100644 index 0000000000..3c35a871c4 --- /dev/null +++ b/src/golang.org/x/mobile/README.md @@ -0,0 +1,29 @@ +# Go support for Mobile devices + +The Go mobile repository holds packages and build tools for using Go on mobile platforms. + +Package documentation serves as a starting point: + +- [Building all-Go apps](https://golang.org/x/mobile/app) +- [Building libraries for SDK apps](https://golang.org/x/mobile/cmd/gobind) + +![Caution image](doc/caution.png) + +The Go Mobile project is experimental. Use this at your own risk. +While we are working hard to improve it, neither Google nor the Go +team can provide end-user support. + +This is early work and installing the build system requires Go 1.5. +Follow the build instructions on +[godoc.org/golang.org/x/mobile/cmd/gomobile](https://golang.org/x/mobile/cmd/gomobile) +to install the gomobile command and +[build the basic example](https://golang.org/x/mobile/example/basic). + +-- + +Contributions to Go are appreciated. See https://golang.org/doc/contribute.html. + +* Bugs can be filed at the [Go issue tracker](https://golang.org/issue/new). +* Feature requests should preliminary be discussed on +[golang-nuts](https://groups.google.com/forum/#!forum/golang-nuts) +mailing list. diff --git a/src/golang.org/x/mobile/app/GoNativeActivity.java b/src/golang.org/x/mobile/app/GoNativeActivity.java new file mode 100644 index 0000000000..4a1234bb96 --- /dev/null +++ b/src/golang.org/x/mobile/app/GoNativeActivity.java @@ -0,0 +1,51 @@ +package org.golang.app; + +import android.app.Activity; +import android.app.NativeActivity; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.util.Log; + +public class GoNativeActivity extends NativeActivity { + private static GoNativeActivity goNativeActivity; + + public GoNativeActivity() { + super(); + goNativeActivity = this; + } + + String getTmpdir() { + return getCacheDir().getAbsolutePath(); + } + + private void load() { + // Interestingly, NativeActivity uses a different method + // to find native code to execute, avoiding + // System.loadLibrary. The result is Java methods + // implemented in C with JNIEXPORT (and JNI_OnLoad) are not + // available unless an explicit call to System.loadLibrary + // is done. So we do it here, borrowing the name of the + // library from the same AndroidManifest.xml metadata used + // by NativeActivity. + try { + ActivityInfo ai = getPackageManager().getActivityInfo( + getIntent().getComponent(), PackageManager.GET_META_DATA); + if (ai.metaData == null) { + Log.e("Go", "loadLibrary: no manifest metadata found"); + return; + } + String libName = ai.metaData.getString("android.app.lib_name"); + System.loadLibrary(libName); + } catch (Exception e) { + Log.e("Go", "loadLibrary failed", e); + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + load(); + super.onCreate(savedInstanceState); + } +} diff --git a/src/golang.org/x/mobile/app/android.c b/src/golang.org/x/mobile/app/android.c new file mode 100644 index 0000000000..6733e2b8b9 --- /dev/null +++ b/src/golang.org/x/mobile/app/android.c @@ -0,0 +1,178 @@ +// Copyright 2014 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 android + +#include +#include +#include +#include +#include +#include +#include "_cgo_export.h" + +#define LOG_INFO(...) __android_log_print(ANDROID_LOG_INFO, "Go", __VA_ARGS__) +#define LOG_FATAL(...) __android_log_print(ANDROID_LOG_FATAL, "Go", __VA_ARGS__) + +static jclass find_class(JNIEnv *env, const char *class_name) { + jclass clazz = (*env)->FindClass(env, class_name); + if (clazz == NULL) { + (*env)->ExceptionClear(env); + LOG_FATAL("cannot find %s", class_name); + return NULL; + } + return clazz; +} + +static jmethodID find_method(JNIEnv *env, jclass clazz, const char *name, const char *sig) { + jmethodID m = (*env)->GetMethodID(env, clazz, name, sig); + if (m == 0) { + (*env)->ExceptionClear(env); + LOG_FATAL("cannot find method %s %s", name, sig); + return 0; + } + return m; +} + +jint JNI_OnLoad(JavaVM* vm, void* reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) { + return -1; + } + + // Load classes here, which uses the correct ClassLoader. + current_ctx_clazz = find_class(env, "org/golang/app/GoNativeActivity"); + current_ctx_clazz = (jclass)(*env)->NewGlobalRef(env, current_ctx_clazz); + + return JNI_VERSION_1_6; +} + +int main_running = 0; + +// Entry point from our subclassed NativeActivity. +// +// By here, the Go runtime has been initialized (as we are running in +// -buildmode=c-shared) but the first time it is called, Go's main.main +// hasn't been called yet. +// +// The Activity may be created and destroyed multiple times throughout +// the life of a single process. Each time, onCreate is called. +void ANativeActivity_onCreate(ANativeActivity *activity, void* savedState, size_t savedStateSize) { + if (!main_running) { + JNIEnv* env = activity->env; + + // Note that activity->clazz is mis-named. + JavaVM* current_vm = activity->vm; + jobject current_ctx = activity->clazz; + + setCurrentContext(current_vm, (*env)->NewGlobalRef(env, current_ctx)); + + // Set TMPDIR. + jmethodID gettmpdir = find_method(env, current_ctx_clazz, "getTmpdir", "()Ljava/lang/String;"); + jstring jpath = (jstring)(*env)->CallObjectMethod(env, current_ctx, gettmpdir, NULL); + const char* tmpdir = (*env)->GetStringUTFChars(env, jpath, NULL); + if (setenv("TMPDIR", tmpdir, 1) != 0) { + LOG_INFO("setenv(\"TMPDIR\", \"%s\", 1) failed: %d", tmpdir, errno); + } + (*env)->ReleaseStringUTFChars(env, jpath, tmpdir); + + // Call the Go main.main. + uintptr_t mainPC = (uintptr_t)dlsym(RTLD_DEFAULT, "main.main"); + if (!mainPC) { + LOG_FATAL("missing main.main"); + } + callMain(mainPC); + main_running = 1; + } + + // These functions match the methods on Activity, described at + // http://developer.android.com/reference/android/app/Activity.html + // + // Note that onNativeWindowResized is not called on resize. Avoid it. + // https://code.google.com/p/android/issues/detail?id=180645 + activity->callbacks->onStart = onStart; + activity->callbacks->onResume = onResume; + activity->callbacks->onSaveInstanceState = onSaveInstanceState; + activity->callbacks->onPause = onPause; + activity->callbacks->onStop = onStop; + activity->callbacks->onDestroy = onDestroy; + activity->callbacks->onWindowFocusChanged = onWindowFocusChanged; + activity->callbacks->onNativeWindowCreated = onNativeWindowCreated; + activity->callbacks->onNativeWindowRedrawNeeded = onNativeWindowRedrawNeeded; + activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed; + activity->callbacks->onInputQueueCreated = onInputQueueCreated; + activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed; + activity->callbacks->onConfigurationChanged = onConfigurationChanged; + activity->callbacks->onLowMemory = onLowMemory; + + onCreate(activity); +} + +// TODO(crawshaw): Test configuration on more devices. +const EGLint RGB_888[] = { + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_BLUE_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_RED_SIZE, 8, + EGL_DEPTH_SIZE, 16, + EGL_CONFIG_CAVEAT, EGL_NONE, + EGL_NONE +}; + +EGLDisplay display = NULL; +EGLSurface surface = NULL; + +char* initEGLDisplay() { + display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (!eglInitialize(display, 0, 0)) { + return "EGL initialize failed"; + } + return NULL; +} + +char* createEGLSurface(ANativeWindow* window) { + char* err; + EGLint numConfigs, format; + EGLConfig config; + EGLContext context; + + if (display == 0) { + if ((err = initEGLDisplay()) != NULL) { + return err; + } + } + + if (!eglChooseConfig(display, RGB_888, &config, 1, &numConfigs)) { + return "EGL choose RGB_888 config failed"; + } + if (numConfigs <= 0) { + return "EGL no config found"; + } + + eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format); + if (ANativeWindow_setBuffersGeometry(window, 0, 0, format) != 0) { + return "EGL set buffers geometry failed"; + } + + surface = eglCreateWindowSurface(display, config, window, NULL); + if (surface == EGL_NO_SURFACE) { + return "EGL create surface failed"; + } + + const EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; + context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs); + + if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) { + return "eglMakeCurrent failed"; + } + return NULL; +} + +char* destroyEGLSurface() { + if (!eglDestroySurface(display, surface)) { + return "EGL destroy surface failed"; + } + return NULL; +} diff --git a/src/golang.org/x/mobile/app/android.go b/src/golang.org/x/mobile/app/android.go new file mode 100644 index 0000000000..416ce647d8 --- /dev/null +++ b/src/golang.org/x/mobile/app/android.go @@ -0,0 +1,413 @@ +// Copyright 2014 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 android + +/* +Android Apps are built with -buildmode=c-shared. They are loaded by a +running Java process. + +Before any entry point is reached, a global constructor initializes the +Go runtime, calling all Go init functions. All cgo calls will block +until this is complete. Next JNI_OnLoad is called. When that is +complete, one of two entry points is called. + +All-Go apps built using NativeActivity enter at ANativeActivity_onCreate. + +Go libraries (for example, those built with gomobile bind) do not use +the app package initialization. +*/ + +package app + +/* +#cgo LDFLAGS: -landroid -llog -lEGL -lGLESv2 + +#include +#include +#include +#include +#include +#include +#include + +jclass current_ctx_clazz; + +jclass app_find_class(JNIEnv* env, const char* name); + +EGLDisplay display; +EGLSurface surface; + +char* initEGLDisplay(); +char* createEGLSurface(ANativeWindow* window); +char* destroyEGLSurface(); +*/ +import "C" +import ( + "fmt" + "log" + "os" + "runtime" + "time" + "unsafe" + + "golang.org/x/mobile/app/internal/callfn" + "golang.org/x/mobile/event/config" + "golang.org/x/mobile/event/lifecycle" + "golang.org/x/mobile/event/paint" + "golang.org/x/mobile/event/touch" + "golang.org/x/mobile/geom" + "golang.org/x/mobile/gl" + "golang.org/x/mobile/internal/mobileinit" +) + +//export setCurrentContext +func setCurrentContext(vm *C.JavaVM, ctx C.jobject) { + mobileinit.SetCurrentContext(unsafe.Pointer(vm), unsafe.Pointer(ctx)) +} + +//export callMain +func callMain(mainPC uintptr) { + for _, name := range []string{"TMPDIR", "PATH", "LD_LIBRARY_PATH"} { + n := C.CString(name) + os.Setenv(name, C.GoString(C.getenv(n))) + C.free(unsafe.Pointer(n)) + } + + // Set timezone. + // + // Note that Android zoneinfo is stored in /system/usr/share/zoneinfo, + // but it is in some kind of packed TZiff file that we do not support + // yet. As a stopgap, we build a fixed zone using the tm_zone name. + var curtime C.time_t + var curtm C.struct_tm + C.time(&curtime) + C.localtime_r(&curtime, &curtm) + tzOffset := int(curtm.tm_gmtoff) + tz := C.GoString(curtm.tm_zone) + time.Local = time.FixedZone(tz, tzOffset) + + go callfn.CallFn(mainPC) +} + +//export onStart +func onStart(activity *C.ANativeActivity) { +} + +//export onResume +func onResume(activity *C.ANativeActivity) { +} + +//export onSaveInstanceState +func onSaveInstanceState(activity *C.ANativeActivity, outSize *C.size_t) unsafe.Pointer { + return nil +} + +//export onPause +func onPause(activity *C.ANativeActivity) { +} + +//export onStop +func onStop(activity *C.ANativeActivity) { +} + +//export onCreate +func onCreate(activity *C.ANativeActivity) { + // Set the initial configuration. + // + // Note we use unbuffered channels to talk to the activity loop, and + // NativeActivity calls these callbacks sequentially, so configuration + // will be set before <-windowRedrawNeeded is processed. + windowConfigChange <- windowConfigRead(activity) +} + +//export onDestroy +func onDestroy(activity *C.ANativeActivity) { +} + +//export onWindowFocusChanged +func onWindowFocusChanged(activity *C.ANativeActivity, hasFocus int) { +} + +//export onNativeWindowCreated +func onNativeWindowCreated(activity *C.ANativeActivity, w *C.ANativeWindow) { + windowCreated <- w +} + +//export onNativeWindowRedrawNeeded +func onNativeWindowRedrawNeeded(activity *C.ANativeActivity, window *C.ANativeWindow) { + // Called on orientation change and window resize. + // Send a request for redraw, and block this function + // until a complete draw and buffer swap is completed. + // This is required by the redraw documentation to + // avoid bad draws. + windowRedrawNeeded <- window + <-windowRedrawDone +} + +//export onNativeWindowDestroyed +func onNativeWindowDestroyed(activity *C.ANativeActivity, window *C.ANativeWindow) { + windowDestroyed <- window +} + +//export onInputQueueCreated +func onInputQueueCreated(activity *C.ANativeActivity, q *C.AInputQueue) { + inputQueue <- q +} + +//export onInputQueueDestroyed +func onInputQueueDestroyed(activity *C.ANativeActivity, q *C.AInputQueue) { + inputQueue <- nil +} + +//export onContentRectChanged +func onContentRectChanged(activity *C.ANativeActivity, rect *C.ARect) { +} + +type windowConfig struct { + // TODO(crawshaw): report orientation + // ACONFIGURATION_ORIENTATION_ANY + // ACONFIGURATION_ORIENTATION_PORT + // ACONFIGURATION_ORIENTATION_LAND + // ACONFIGURATION_ORIENTATION_SQUARE + // Needs to be merged with iOS's notion of orientation first. + orientation int + pixelsPerPt float32 +} + +func windowConfigRead(activity *C.ANativeActivity) windowConfig { + aconfig := C.AConfiguration_new() + C.AConfiguration_fromAssetManager(aconfig, activity.assetManager) + orient := C.AConfiguration_getOrientation(aconfig) + density := C.AConfiguration_getDensity(aconfig) + C.AConfiguration_delete(aconfig) + + var dpi int + switch density { + case C.ACONFIGURATION_DENSITY_DEFAULT: + dpi = 160 + case C.ACONFIGURATION_DENSITY_LOW, + C.ACONFIGURATION_DENSITY_MEDIUM, + 213, // C.ACONFIGURATION_DENSITY_TV + C.ACONFIGURATION_DENSITY_HIGH, + 320, // ACONFIGURATION_DENSITY_XHIGH + 480, // ACONFIGURATION_DENSITY_XXHIGH + 640: // ACONFIGURATION_DENSITY_XXXHIGH + dpi = int(density) + case C.ACONFIGURATION_DENSITY_NONE: + log.Print("android device reports no screen density") + dpi = 72 + default: + log.Printf("android device reports unknown density: %d", density) + // All we can do is guess. + if density > 0 { + dpi = int(density) + } else { + dpi = 72 + } + } + + return windowConfig{ + orientation: int(orient), + pixelsPerPt: float32(dpi) / 72, + } +} + +//export onConfigurationChanged +func onConfigurationChanged(activity *C.ANativeActivity) { + // A rotation event first triggers onConfigurationChanged, then + // calls onNativeWindowRedrawNeeded. We extract the orientation + // here and save it for the redraw event. + windowConfigChange <- windowConfigRead(activity) +} + +//export onLowMemory +func onLowMemory(activity *C.ANativeActivity) { +} + +var ( + inputQueue = make(chan *C.AInputQueue) + windowDestroyed = make(chan *C.ANativeWindow) + windowCreated = make(chan *C.ANativeWindow) + windowRedrawNeeded = make(chan *C.ANativeWindow) + windowRedrawDone = make(chan struct{}) + windowConfigChange = make(chan windowConfig) +) + +func init() { + registerGLViewportFilter() +} + +func main(f func(App)) { + // Preserve this OS thread for the GL context created below. + runtime.LockOSThread() + + donec := make(chan struct{}) + go func() { + f(app{}) + close(donec) + }() + + var q *C.AInputQueue + + // Android can send a windowRedrawNeeded event any time, including + // in the middle of a paint cycle. The redraw event may have changed + // the size of the screen, so any partial painting is now invalidated. + // We must also not return to Android (via sending on windowRedrawDone) + // until a complete paint with the new configuration is complete. + // + // When a windowRedrawNeeded request comes in, we increment redrawGen + // (Gen is short for generation number), and do not make a paint cycle + // visible on <-endPaint unless Generation agrees. If possible, + // windowRedrawDone is signalled, allowing onNativeWindowRedrawNeeded + // to return. + var redrawGen uint32 + + for { + if q != nil { + processEvents(q) + } + select { + case <-windowCreated: + case q = <-inputQueue: + case <-donec: + return + case cfg := <-windowConfigChange: + // TODO save orientation + pixelsPerPt = cfg.pixelsPerPt + case w := <-windowRedrawNeeded: + newWindow := C.surface == nil + if newWindow { + if errStr := C.createEGLSurface(w); errStr != nil { + log.Printf("app: %s (%s)", C.GoString(errStr), eglGetError()) + return + } + } + sendLifecycle(lifecycle.StageFocused) + widthPx := int(C.ANativeWindow_getWidth(w)) + heightPx := int(C.ANativeWindow_getHeight(w)) + eventsIn <- config.Event{ + WidthPx: widthPx, + HeightPx: heightPx, + WidthPt: geom.Pt(float32(widthPx) / pixelsPerPt), + HeightPt: geom.Pt(float32(heightPx) / pixelsPerPt), + PixelsPerPt: pixelsPerPt, + } + redrawGen++ + if newWindow { + // New window, begin paint loop. + eventsIn <- paint.Event{redrawGen} + } + case <-windowDestroyed: + if C.surface != nil { + if errStr := C.destroyEGLSurface(); errStr != nil { + log.Printf("app: %s (%s)", C.GoString(errStr), eglGetError()) + return + } + } + C.surface = nil + sendLifecycle(lifecycle.StageAlive) + case <-gl.WorkAvailable: + gl.DoWork() + case p := <-endPaint: + if p.Generation != redrawGen { + continue + } + if C.surface != nil { + // eglSwapBuffers blocks until vsync. + if C.eglSwapBuffers(C.display, C.surface) == C.EGL_FALSE { + log.Printf("app: failed to swap buffers (%s)", eglGetError()) + } + } + select { + case windowRedrawDone <- struct{}{}: + default: + } + if C.surface != nil { + redrawGen++ + eventsIn <- paint.Event{redrawGen} + } + } + } +} + +func processEvents(queue *C.AInputQueue) { + var event *C.AInputEvent + for C.AInputQueue_getEvent(queue, &event) >= 0 { + if C.AInputQueue_preDispatchEvent(queue, event) != 0 { + continue + } + processEvent(event) + C.AInputQueue_finishEvent(queue, event, 0) + } +} + +func processEvent(e *C.AInputEvent) { + switch C.AInputEvent_getType(e) { + case C.AINPUT_EVENT_TYPE_KEY: + log.Printf("TODO input event: key") + case C.AINPUT_EVENT_TYPE_MOTION: + // At most one of the events in this batch is an up or down event; get its index and change. + upDownIndex := C.size_t(C.AMotionEvent_getAction(e)&C.AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> C.AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT + upDownType := touch.TypeMove + switch C.AMotionEvent_getAction(e) & C.AMOTION_EVENT_ACTION_MASK { + case C.AMOTION_EVENT_ACTION_DOWN, C.AMOTION_EVENT_ACTION_POINTER_DOWN: + upDownType = touch.TypeBegin + case C.AMOTION_EVENT_ACTION_UP, C.AMOTION_EVENT_ACTION_POINTER_UP: + upDownType = touch.TypeEnd + } + + for i, n := C.size_t(0), C.AMotionEvent_getPointerCount(e); i < n; i++ { + t := touch.TypeMove + if i == upDownIndex { + t = upDownType + } + eventsIn <- touch.Event{ + X: float32(C.AMotionEvent_getX(e, i)), + Y: float32(C.AMotionEvent_getY(e, i)), + Sequence: touch.Sequence(C.AMotionEvent_getPointerId(e, i)), + Type: t, + } + } + default: + log.Printf("unknown input event, type=%d", C.AInputEvent_getType(e)) + } +} + +func eglGetError() string { + switch errNum := C.eglGetError(); errNum { + case C.EGL_SUCCESS: + return "EGL_SUCCESS" + case C.EGL_NOT_INITIALIZED: + return "EGL_NOT_INITIALIZED" + case C.EGL_BAD_ACCESS: + return "EGL_BAD_ACCESS" + case C.EGL_BAD_ALLOC: + return "EGL_BAD_ALLOC" + case C.EGL_BAD_ATTRIBUTE: + return "EGL_BAD_ATTRIBUTE" + case C.EGL_BAD_CONTEXT: + return "EGL_BAD_CONTEXT" + case C.EGL_BAD_CONFIG: + return "EGL_BAD_CONFIG" + case C.EGL_BAD_CURRENT_SURFACE: + return "EGL_BAD_CURRENT_SURFACE" + case C.EGL_BAD_DISPLAY: + return "EGL_BAD_DISPLAY" + case C.EGL_BAD_SURFACE: + return "EGL_BAD_SURFACE" + case C.EGL_BAD_MATCH: + return "EGL_BAD_MATCH" + case C.EGL_BAD_PARAMETER: + return "EGL_BAD_PARAMETER" + case C.EGL_BAD_NATIVE_PIXMAP: + return "EGL_BAD_NATIVE_PIXMAP" + case C.EGL_BAD_NATIVE_WINDOW: + return "EGL_BAD_NATIVE_WINDOW" + case C.EGL_CONTEXT_LOST: + return "EGL_CONTEXT_LOST" + default: + return fmt.Sprintf("Unknown EGL err: %d", errNum) + } +} diff --git a/src/golang.org/x/mobile/app/app.go b/src/golang.org/x/mobile/app/app.go new file mode 100644 index 0000000000..b9a11e2bdf --- /dev/null +++ b/src/golang.org/x/mobile/app/app.go @@ -0,0 +1,184 @@ +// Copyright 2014 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 + +package app + +import ( + "golang.org/x/mobile/event/config" + "golang.org/x/mobile/event/lifecycle" + "golang.org/x/mobile/event/paint" + "golang.org/x/mobile/gl" + _ "golang.org/x/mobile/internal/mobileinit" +) + +// Main is called by the main.main function to run the mobile application. +// +// It calls f on the App, in a separate goroutine, as some OS-specific +// libraries require being on 'the main thread'. +func Main(f func(App)) { + main(f) +} + +// App is how a GUI mobile application interacts with the OS. +type App interface { + // Events returns the events channel. It carries events from the system to + // the app. The type of such events include: + // - config.Event + // - lifecycle.Event + // - mouse.Event + // - paint.Event + // - touch.Event + // from the golang.org/x/mobile/event/etc packages. Other packages may + // define other event types that are carried on this channel. + Events() <-chan interface{} + + // Send sends an event on the events channel. It does not block. + Send(event interface{}) + + // EndPaint flushes any pending OpenGL commands or buffers to the screen. + // If EndPaint is called with an old generation number, it is ignored. + EndPaint(paint.Event) +} + +var ( + lifecycleStage = lifecycle.StageDead + pixelsPerPt = float32(1) + + eventsOut = make(chan interface{}) + eventsIn = pump(eventsOut) + endPaint = make(chan paint.Event, 1) +) + +func sendLifecycle(to lifecycle.Stage) { + if lifecycleStage == to { + return + } + eventsIn <- lifecycle.Event{ + From: lifecycleStage, + To: to, + } + lifecycleStage = to +} + +type app struct{} + +func (app) Events() <-chan interface{} { + return eventsOut +} + +func (app) Send(event interface{}) { + eventsIn <- event +} + +func (app) EndPaint(e paint.Event) { + // gl.Flush is a lightweight (on modern GL drivers) blocking call + // that ensures all GL functions pending in the gl package have + // been passed onto the GL driver before the app package attempts + // to swap the screen buffer. + // + // This enforces that the final receive (for this paint cycle) on + // gl.WorkAvailable happens before the send on endPaint. + gl.Flush() + endPaint <- e +} + +var filters []func(interface{}) interface{} + +// Filter calls each registered event filter function in sequence. +func Filter(event interface{}) interface{} { + for _, f := range filters { + event = f(event) + } + return event +} + +// RegisterFilter registers a event filter function to be called by Filter. The +// function can return a different event, or return nil to consume the event, +// but the function can also return its argument unchanged, where its purpose +// is to trigger a side effect rather than modify the event. +// +// RegisterFilter should only be called from init functions. +func RegisterFilter(f func(interface{}) interface{}) { + filters = append(filters, f) +} + +type stopPumping struct{} + +// pump returns a channel src such that sending on src will eventually send on +// dst, in order, but that src will always be ready to send/receive soon, even +// if dst currently isn't. It is effectively an infinitely buffered channel. +// +// In particular, goroutine A sending on src will not deadlock even if goroutine +// B that's responsible for receiving on dst is currently blocked trying to +// send to A on a separate channel. +// +// Send a stopPumping on the src channel to close the dst channel after all queued +// events are sent on dst. After that, other goroutines can still send to src, +// so that such sends won't block forever, but such events will be ignored. +func pump(dst chan interface{}) (src chan interface{}) { + src = make(chan interface{}) + go func() { + // initialSize is the initial size of the circular buffer. It must be a + // power of 2. + const initialSize = 16 + i, j, buf, mask := 0, 0, make([]interface{}, initialSize), initialSize-1 + + maybeSrc := src + for { + maybeDst := dst + if i == j { + maybeDst = nil + } + if maybeDst == nil && maybeSrc == nil { + break + } + + select { + case maybeDst <- buf[i&mask]: + buf[i&mask] = nil + i++ + + case e := <-maybeSrc: + if _, ok := e.(stopPumping); ok { + maybeSrc = nil + continue + } + + // Allocate a bigger buffer if necessary. + if i+len(buf) == j { + b := make([]interface{}, 2*len(buf)) + n := copy(b, buf[j&mask:]) + copy(b[n:], buf[:j&mask]) + i, j = 0, len(buf) + buf, mask = b, len(b)-1 + } + + buf[j&mask] = e + j++ + } + } + + close(dst) + // Block forever. + for range src { + } + }() + return src +} + +// TODO: do this for all build targets, not just linux (x11 and Android)? If +// so, should package gl instead of this package call RegisterFilter?? +// +// TODO: does Android need this?? It seems to work without it (Nexus 7, +// KitKat). If only x11 needs this, should we move this to x11.go?? +func registerGLViewportFilter() { + RegisterFilter(func(e interface{}) interface{} { + if e, ok := e.(config.Event); ok { + gl.Viewport(0, 0, e.WidthPx, e.HeightPx) + } + return e + }) +} diff --git a/src/golang.org/x/mobile/app/app_test.go b/src/golang.org/x/mobile/app/app_test.go new file mode 100644 index 0000000000..921d901cb6 --- /dev/null +++ b/src/golang.org/x/mobile/app/app_test.go @@ -0,0 +1,138 @@ +// Copyright 2015 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. + +package app_test + +import ( + "fmt" + "io/ioutil" + "net" + "os" + "os/exec" + "strings" + "testing" + "time" + + "golang.org/x/mobile/app/internal/apptest" +) + +// TestAndroidApp tests the lifecycle, event, and window semantics of a +// simple android app. +// +// Beyond testing the app package, the goal is to eventually have +// helper libraries that make tests like these easy to write. Hopefully +// having a user of such a fictional package will help illuminate the way. +func TestAndroidApp(t *testing.T) { + if _, err := exec.Command("which", "adb").CombinedOutput(); err != nil { + t.Skip("command adb not found, skipping") + } + + run(t, "gomobile", "version") + + origWD, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + + tmpdir, err := ioutil.TempDir("", "app-test-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpdir) + + if err := os.Chdir(tmpdir); err != nil { + t.Fatal(err) + } + defer os.Chdir(origWD) + + run(t, "gomobile", "install", "golang.org/x/mobile/app/internal/testapp") + + ln, err := net.Listen("tcp4", "localhost:0") + if err != nil { + t.Fatal(err) + } + defer ln.Close() + localaddr := fmt.Sprintf("tcp:%d", ln.Addr().(*net.TCPAddr).Port) + t.Logf("local address: %s", localaddr) + + exec.Command("adb", "reverse", "--remove", "tcp:"+apptest.Port).Run() // ignore failure + run(t, "adb", "reverse", "tcp:"+apptest.Port, localaddr) + + const ( + KeycodePower = "26" + KeycodeUnlock = "82" + ) + + run(t, "adb", "shell", "input", "keyevent", KeycodePower) + run(t, "adb", "shell", "input", "keyevent", KeycodeUnlock) + + // start testapp + run(t, + "adb", "shell", "am", "start", "-n", + "org.golang.testapp/org.golang.app.GoNativeActivity", + ) + + var conn net.Conn + connDone := make(chan struct{}) + go func() { + conn, err = ln.Accept() + connDone <- struct{}{} + }() + + select { + case <-time.After(5 * time.Second): + t.Fatal("timeout waiting for testapp to dial host") + case <-connDone: + if err != nil { + t.Fatalf("ln.Accept: %v", err) + } + } + defer conn.Close() + comm := &apptest.Comm{ + Conn: conn, + Fatalf: t.Fatalf, + Printf: t.Logf, + } + + var PixelsPerPt float32 + + comm.Recv("hello_from_testapp") + comm.Send("hello_from_host") + comm.Recv("lifecycle_visible") + comm.Recv("config", &PixelsPerPt) + if PixelsPerPt < 0.1 { + t.Fatalf("bad PixelsPerPt: %f", PixelsPerPt) + } + comm.Recv("paint") + + var x, y int + var ty string + + tap(t, 50, 60) + comm.Recv("touch", &ty, &x, &y) + if ty != "begin" || x != 50 || y != 60 { + t.Errorf("want touch begin(50, 60), got %s(%d,%d)", ty, x, y) + } + comm.Recv("touch", &ty, &x, &y) + if ty != "end" || x != 50 || y != 60 { + t.Errorf("want touch end(50, 60), got %s(%d,%d)", ty, x, y) + } + + // TODO: screenshot of gl.Clear to test painting + // TODO: lifecycle testing (NOTE: adb shell input keyevent 4 is the back button) + // TODO: orientation testing +} + +func tap(t *testing.T, x, y int) { + run(t, "adb", "shell", "input", "tap", fmt.Sprintf("%d", x), fmt.Sprintf("%d", y)) +} + +func run(t *testing.T, cmdName string, arg ...string) { + cmd := exec.Command(cmdName, arg...) + t.Log(strings.Join(cmd.Args, " ")) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("%s %v: %s", strings.Join(cmd.Args, " "), err, out) + } +} diff --git a/src/golang.org/x/mobile/app/darwin_amd64.go b/src/golang.org/x/mobile/app/darwin_amd64.go new file mode 100644 index 0000000000..48df495892 --- /dev/null +++ b/src/golang.org/x/mobile/app/darwin_amd64.go @@ -0,0 +1,480 @@ +// Copyright 2014 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 darwin + +package app + +// Simple on-screen app debugging for OS X. Not an officially supported +// development target for apps, as screens with mice are very different +// than screens with touch panels. + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Cocoa -framework OpenGL -framework QuartzCore +#import // for HIToolbox/Events.h +#import +#include + +void runApp(void); +void stopApp(void); +void makeCurrentContext(GLintptr); +uint64 threadID(); +*/ +import "C" +import ( + "log" + "runtime" + "sync" + + "golang.org/x/mobile/event/config" + "golang.org/x/mobile/event/key" + "golang.org/x/mobile/event/lifecycle" + "golang.org/x/mobile/event/paint" + "golang.org/x/mobile/event/touch" + "golang.org/x/mobile/geom" + "golang.org/x/mobile/gl" +) + +var initThreadID uint64 + +func init() { + // Lock the goroutine responsible for initialization to an OS thread. + // This means the goroutine running main (and calling runApp below) + // is locked to the OS thread that started the program. This is + // necessary for the correct delivery of Cocoa events to the process. + // + // A discussion on this topic: + // https://groups.google.com/forum/#!msg/golang-nuts/IiWZ2hUuLDA/SNKYYZBelsYJ + runtime.LockOSThread() + initThreadID = uint64(C.threadID()) +} + +func main(f func(App)) { + if tid := uint64(C.threadID()); tid != initThreadID { + log.Fatalf("app.Main called on thread %d, but app.init ran on %d", tid, initThreadID) + } + + go func() { + f(app{}) + C.stopApp() + // TODO(crawshaw): trigger runApp to return + }() + + C.runApp() +} + +// loop is the primary drawing loop. +// +// After Cocoa has captured the initial OS thread for processing Cocoa +// events in runApp, it starts loop on another goroutine. It is locked +// to an OS thread for its OpenGL context. +// +// Two Cocoa threads deliver draw signals to loop. The primary source of +// draw events is the CVDisplayLink timer, which is tied to the display +// vsync. Secondary draw events come from [NSView drawRect:] when the +// window is resized. +func loop(ctx C.GLintptr) { + runtime.LockOSThread() + C.makeCurrentContext(ctx) + + for range draw { + eventsIn <- paint.Event{} + loop1: + for { + select { + case <-gl.WorkAvailable: + gl.DoWork() + case <-endPaint: + C.CGLFlushDrawable(C.CGLGetCurrentContext()) + break loop1 + } + } + drawDone <- struct{}{} + } +} + +var ( + draw = make(chan struct{}) + drawDone = make(chan struct{}) +) + +//export drawgl +func drawgl() { + draw <- struct{}{} + <-drawDone +} + +//export startloop +func startloop(ctx C.GLintptr) { + go loop(ctx) +} + +var windowHeightPx float32 + +//export setGeom +func setGeom(ppp float32, widthPx, heightPx int) { + pixelsPerPt = ppp + windowHeightPx = float32(heightPx) + eventsIn <- config.Event{ + WidthPx: widthPx, + HeightPx: heightPx, + WidthPt: geom.Pt(float32(widthPx) / pixelsPerPt), + HeightPt: geom.Pt(float32(heightPx) / pixelsPerPt), + PixelsPerPt: pixelsPerPt, + } +} + +var touchEvents struct { + sync.Mutex + pending []touch.Event +} + +func sendTouch(t touch.Type, x, y float32) { + eventsIn <- touch.Event{ + X: x, + Y: windowHeightPx - y, + Sequence: 0, + Type: t, + } +} + +//export eventMouseDown +func eventMouseDown(x, y float32) { sendTouch(touch.TypeBegin, x, y) } + +//export eventMouseDragged +func eventMouseDragged(x, y float32) { sendTouch(touch.TypeMove, x, y) } + +//export eventMouseEnd +func eventMouseEnd(x, y float32) { sendTouch(touch.TypeEnd, x, y) } + +//export lifecycleDead +func lifecycleDead() { sendLifecycle(lifecycle.StageDead) } + +//export eventKey +func eventKey(runeVal int32, direction uint8, code uint16, flags uint32) { + var modifiers key.Modifiers + for _, mod := range mods { + if flags&mod.flags == mod.flags { + modifiers |= mod.mod + } + } + + eventsIn <- key.Event{ + Rune: convRune(rune(runeVal)), + Code: convVirtualKeyCode(code), + Modifiers: modifiers, + Direction: key.Direction(direction), + } +} + +//export eventFlags +func eventFlags(flags uint32) { + for _, mod := range mods { + if flags&mod.flags == mod.flags && lastFlags&mod.flags != mod.flags { + eventKey(-1, uint8(key.DirPress), mod.code, flags) + } + if lastFlags&mod.flags == mod.flags && flags&mod.flags != mod.flags { + eventKey(-1, uint8(key.DirRelease), mod.code, flags) + } + } + lastFlags = flags +} + +var lastFlags uint32 + +var mods = [...]struct { + flags uint32 + code uint16 + mod key.Modifiers +}{ + // Left and right variants of modifier keys have their own masks, + // but they are not documented. These were determined empirically. + {1<<17 | 0x102, C.kVK_Shift, key.ModShift}, + {1<<17 | 0x104, C.kVK_RightShift, key.ModShift}, + {1<<18 | 0x101, C.kVK_Control, key.ModControl}, + // TODO key.ControlRight + {1<<19 | 0x120, C.kVK_Option, key.ModAlt}, + {1<<19 | 0x140, C.kVK_RightOption, key.ModAlt}, + {1<<20 | 0x108, C.kVK_Command, key.ModMeta}, + {1<<20 | 0x110, C.kVK_Command, key.ModMeta}, // TODO: missing kVK_RightCommand +} + +//export lifecycleAlive +func lifecycleAlive() { sendLifecycle(lifecycle.StageAlive) } + +//export lifecycleVisible +func lifecycleVisible() { sendLifecycle(lifecycle.StageVisible) } + +//export lifecycleFocused +func lifecycleFocused() { sendLifecycle(lifecycle.StageFocused) } + +// convRune marks the Carbon/Cocoa private-range unicode rune representing +// a non-unicode key event to -1, used for Rune in the key package. +// +// http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT +func convRune(r rune) rune { + if '\uE000' <= r && r <= '\uF8FF' { + return -1 + } + return r +} + +// convVirtualKeyCode converts a Carbon/Cocoa virtual key code number +// into the standard keycodes used by the key package. +// +// To get a sense of the key map, see the diagram on +// http://boredzo.org/blog/archives/2007-05-22/virtual-key-codes +func convVirtualKeyCode(vkcode uint16) key.Code { + switch vkcode { + case C.kVK_ANSI_A: + return key.CodeA + case C.kVK_ANSI_B: + return key.CodeB + case C.kVK_ANSI_C: + return key.CodeC + case C.kVK_ANSI_D: + return key.CodeD + case C.kVK_ANSI_E: + return key.CodeE + case C.kVK_ANSI_F: + return key.CodeF + case C.kVK_ANSI_G: + return key.CodeG + case C.kVK_ANSI_H: + return key.CodeH + case C.kVK_ANSI_I: + return key.CodeI + case C.kVK_ANSI_J: + return key.CodeJ + case C.kVK_ANSI_K: + return key.CodeK + case C.kVK_ANSI_L: + return key.CodeL + case C.kVK_ANSI_M: + return key.CodeM + case C.kVK_ANSI_N: + return key.CodeN + case C.kVK_ANSI_O: + return key.CodeO + case C.kVK_ANSI_P: + return key.CodeP + case C.kVK_ANSI_Q: + return key.CodeQ + case C.kVK_ANSI_R: + return key.CodeR + case C.kVK_ANSI_S: + return key.CodeS + case C.kVK_ANSI_T: + return key.CodeT + case C.kVK_ANSI_U: + return key.CodeU + case C.kVK_ANSI_V: + return key.CodeV + case C.kVK_ANSI_W: + return key.CodeW + case C.kVK_ANSI_X: + return key.CodeX + case C.kVK_ANSI_Y: + return key.CodeY + case C.kVK_ANSI_Z: + return key.CodeZ + case C.kVK_ANSI_1: + return key.Code1 + case C.kVK_ANSI_2: + return key.Code2 + case C.kVK_ANSI_3: + return key.Code3 + case C.kVK_ANSI_4: + return key.Code4 + case C.kVK_ANSI_5: + return key.Code5 + case C.kVK_ANSI_6: + return key.Code6 + case C.kVK_ANSI_7: + return key.Code7 + case C.kVK_ANSI_8: + return key.Code8 + case C.kVK_ANSI_9: + return key.Code9 + case C.kVK_ANSI_0: + return key.Code0 + // TODO: move the rest of these codes to constants in key.go + // if we are happy with them. + case C.kVK_Return: + return key.CodeReturnEnter + case C.kVK_Escape: + return key.CodeEscape + case C.kVK_Delete: + return key.CodeDeleteBackspace + case C.kVK_Tab: + return key.CodeTab + case C.kVK_Space: + return key.CodeSpacebar + case C.kVK_ANSI_Minus: + return key.CodeHyphenMinus + case C.kVK_ANSI_Equal: + return key.CodeEqualSign + case C.kVK_ANSI_LeftBracket: + return key.CodeLeftSquareBracket + case C.kVK_ANSI_RightBracket: + return key.CodeRightSquareBracket + case C.kVK_ANSI_Backslash: + return key.CodeBackslash + // 50: Keyboard Non-US "#" and ~ + case C.kVK_ANSI_Semicolon: + return key.CodeSemicolon + case C.kVK_ANSI_Quote: + return key.CodeApostrophe + case C.kVK_ANSI_Grave: + return key.CodeGraveAccent + case C.kVK_ANSI_Comma: + return key.CodeComma + case C.kVK_ANSI_Period: + return key.CodeFullStop + case C.kVK_ANSI_Slash: + return key.CodeSlash + case C.kVK_CapsLock: + return key.CodeCapsLock + case C.kVK_F1: + return key.CodeF1 + case C.kVK_F2: + return key.CodeF2 + case C.kVK_F3: + return key.CodeF3 + case C.kVK_F4: + return key.CodeF4 + case C.kVK_F5: + return key.CodeF5 + case C.kVK_F6: + return key.CodeF6 + case C.kVK_F7: + return key.CodeF7 + case C.kVK_F8: + return key.CodeF8 + case C.kVK_F9: + return key.CodeF9 + case C.kVK_F10: + return key.CodeF10 + case C.kVK_F11: + return key.CodeF11 + case C.kVK_F12: + return key.CodeF12 + // 70: PrintScreen + // 71: Scroll Lock + // 72: Pause + // 73: Insert + case C.kVK_Home: + return key.CodeHome + case C.kVK_PageUp: + return key.CodePageUp + case C.kVK_ForwardDelete: + return key.CodeDeleteForward + case C.kVK_End: + return key.CodeEnd + case C.kVK_PageDown: + return key.CodePageDown + case C.kVK_RightArrow: + return key.CodeRightArrow + case C.kVK_LeftArrow: + return key.CodeLeftArrow + case C.kVK_DownArrow: + return key.CodeDownArrow + case C.kVK_UpArrow: + return key.CodeUpArrow + case C.kVK_ANSI_KeypadClear: + return key.CodeKeypadNumLock + case C.kVK_ANSI_KeypadDivide: + return key.CodeKeypadSlash + case C.kVK_ANSI_KeypadMultiply: + return key.CodeKeypadAsterisk + case C.kVK_ANSI_KeypadMinus: + return key.CodeKeypadHyphenMinus + case C.kVK_ANSI_KeypadPlus: + return key.CodeKeypadPlusSign + case C.kVK_ANSI_KeypadEnter: + return key.CodeKeypadEnter + case C.kVK_ANSI_Keypad1: + return key.CodeKeypad1 + case C.kVK_ANSI_Keypad2: + return key.CodeKeypad2 + case C.kVK_ANSI_Keypad3: + return key.CodeKeypad3 + case C.kVK_ANSI_Keypad4: + return key.CodeKeypad4 + case C.kVK_ANSI_Keypad5: + return key.CodeKeypad5 + case C.kVK_ANSI_Keypad6: + return key.CodeKeypad6 + case C.kVK_ANSI_Keypad7: + return key.CodeKeypad7 + case C.kVK_ANSI_Keypad8: + return key.CodeKeypad8 + case C.kVK_ANSI_Keypad9: + return key.CodeKeypad9 + case C.kVK_ANSI_Keypad0: + return key.CodeKeypad0 + case C.kVK_ANSI_KeypadDecimal: + return key.CodeKeypadFullStop + case C.kVK_ANSI_KeypadEquals: + return key.CodeKeypadEqualSign + case C.kVK_F13: + return key.CodeF13 + case C.kVK_F14: + return key.CodeF14 + case C.kVK_F15: + return key.CodeF15 + case C.kVK_F16: + return key.CodeF16 + case C.kVK_F17: + return key.CodeF17 + case C.kVK_F18: + return key.CodeF18 + case C.kVK_F19: + return key.CodeF19 + case C.kVK_F20: + return key.CodeF20 + // 116: Keyboard Execute + case C.kVK_Help: + return key.CodeHelp + // 118: Keyboard Menu + // 119: Keyboard Select + // 120: Keyboard Stop + // 121: Keyboard Again + // 122: Keyboard Undo + // 123: Keyboard Cut + // 124: Keyboard Copy + // 125: Keyboard Paste + // 126: Keyboard Find + case C.kVK_Mute: + return key.CodeMute + case C.kVK_VolumeUp: + return key.CodeVolumeUp + case C.kVK_VolumeDown: + return key.CodeVolumeDown + // 130: Keyboard Locking Caps Lock + // 131: Keyboard Locking Num Lock + // 132: Keyboard Locking Scroll Lock + // 133: Keyboard Comma + // 134: Keyboard Equal Sign + // ...: Bunch of stuff + case C.kVK_Control: + return key.CodeLeftControl + case C.kVK_Shift: + return key.CodeLeftShift + case C.kVK_Option: + return key.CodeLeftAlt + case C.kVK_Command: + return key.CodeLeftGUI + case C.kVK_RightControl: + return key.CodeRightControl + case C.kVK_RightShift: + return key.CodeRightShift + case C.kVK_RightOption: + return key.CodeRightAlt + // TODO key.CodeRightGUI + default: + return key.CodeUnknown + } +} diff --git a/src/golang.org/x/mobile/app/darwin_amd64.m b/src/golang.org/x/mobile/app/darwin_amd64.m new file mode 100644 index 0000000000..49ef8e2457 --- /dev/null +++ b/src/golang.org/x/mobile/app/darwin_amd64.m @@ -0,0 +1,269 @@ +// Copyright 2014 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 darwin + +#include "_cgo_export.h" +#include +#include + +#import +#import +#import +#import +#import + +static CVReturn displayLinkDraw(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext) +{ + drawgl(); + return kCVReturnSuccess; +} + +void makeCurrentContext(GLintptr context) { + NSOpenGLContext* ctx = (NSOpenGLContext*)context; + [ctx makeCurrentContext]; +} + +uint64 threadID() { + uint64 id; + if (pthread_threadid_np(pthread_self(), &id)) { + abort(); + } + return id; +} + + +@interface MobileGLView : NSOpenGLView +{ + CVDisplayLinkRef displayLink; +} +@end + +@implementation MobileGLView +- (void)prepareOpenGL { + [self setWantsBestResolutionOpenGLSurface:YES]; + GLint swapInt = 1; + [[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; + + CVDisplayLinkCreateWithActiveCGDisplays(&displayLink); + CVDisplayLinkSetOutputCallback(displayLink, &displayLinkDraw, self); + + CGLContextObj cglContext = [[self openGLContext] CGLContextObj]; + CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj]; + CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(displayLink, cglContext, cglPixelFormat); + + // Using attribute arrays in OpenGL 3.3 requires the use of a VBA. + // But VBAs don't exist in ES 2. So we bind a default one. + GLuint vba; + glGenVertexArrays(1, &vba); + glBindVertexArray(vba); + + startloop((GLintptr)[self openGLContext]); +} + +- (void)reshape { + [super reshape]; + + // Calculate screen PPI. + // + // Note that the backingScaleFactor converts from logical + // pixels to actual pixels, but both of these units vary + // independently from real world size. E.g. + // + // 13" Retina Macbook Pro, 2560x1600, 227ppi, backingScaleFactor=2, scale=3.15 + // 15" Retina Macbook Pro, 2880x1800, 220ppi, backingScaleFactor=2, scale=3.06 + // 27" iMac, 2560x1440, 109ppi, backingScaleFactor=1, scale=1.51 + // 27" Retina iMac, 5120x2880, 218ppi, backingScaleFactor=2, scale=3.03 + NSScreen *screen = [NSScreen mainScreen]; + double screenPixW = [screen frame].size.width * [screen backingScaleFactor]; + + CGDirectDisplayID display = (CGDirectDisplayID)[[[screen deviceDescription] valueForKey:@"NSScreenNumber"] intValue]; + CGSize screenSizeMM = CGDisplayScreenSize(display); // in millimeters + float ppi = 25.4 * screenPixW / screenSizeMM.width; + float pixelsPerPt = ppi/72.0; + + // The width and height reported to the geom package are the + // bounds of the OpenGL view. Several steps are necessary. + // First, [self bounds] gives us the number of logical pixels + // in the view. Multiplying this by the backingScaleFactor + // gives us the number of actual pixels. + NSRect r = [self bounds]; + int w = r.size.width * [screen backingScaleFactor]; + int h = r.size.height * [screen backingScaleFactor]; + + setGeom(pixelsPerPt, w, h); +} + +- (void)drawRect:(NSRect)theRect { + // Called during resize. Do an extra draw if we are visible. + // This gets rid of flicker when resizing. + if (CVDisplayLinkIsRunning(displayLink)) { + drawgl(); + } +} + +- (void)mouseDown:(NSEvent *)theEvent { + double scale = [[NSScreen mainScreen] backingScaleFactor]; + NSPoint p = [theEvent locationInWindow]; + eventMouseDown(p.x * scale, p.y * scale); +} + +- (void)mouseUp:(NSEvent *)theEvent { + double scale = [[NSScreen mainScreen] backingScaleFactor]; + NSPoint p = [theEvent locationInWindow]; + eventMouseEnd(p.x * scale, p.y * scale); +} + +- (void)mouseDragged:(NSEvent *)theEvent { + double scale = [[NSScreen mainScreen] backingScaleFactor]; + NSPoint p = [theEvent locationInWindow]; + eventMouseDragged(p.x * scale, p.y * scale); +} + +- (void)windowDidBecomeKey:(NSNotification *)notification { + lifecycleFocused(); +} + +- (void)windowDidResignKey:(NSNotification *)notification { + if (![NSApp isHidden]) { + lifecycleVisible(); + } +} + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { + lifecycleAlive(); + [[NSRunningApplication currentApplication] activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)]; + [self.window makeKeyAndOrderFront:self]; + lifecycleVisible(); + CVDisplayLinkStart(displayLink); +} + +- (void)applicationWillTerminate:(NSNotification *)aNotification { + lifecycleDead(); +} + +- (void)applicationDidHide:(NSNotification *)aNotification { + CVDisplayLinkStop(displayLink); + lifecycleAlive(); +} + +- (void)applicationWillUnhide:(NSNotification *)notification { + lifecycleVisible(); + CVDisplayLinkStart(displayLink); +} + +- (void)windowWillClose:(NSNotification *)notification { + CVDisplayLinkStop(displayLink); + lifecycleAlive(); +} +@end + +@interface MobileResponder : NSResponder +{ +} +@end + +@implementation MobileResponder +- (void)keyDown:(NSEvent *)theEvent { + [self key:theEvent]; +} +- (void)keyUp:(NSEvent *)theEvent { + [self key:theEvent]; +} +- (void)key:(NSEvent *)theEvent { + NSRange range = [theEvent.characters rangeOfComposedCharacterSequenceAtIndex:0]; + + uint8_t buf[4] = {0, 0, 0, 0}; + if (![theEvent.characters getBytes:buf + maxLength:4 + usedLength:nil + encoding:NSUTF32LittleEndianStringEncoding + options:NSStringEncodingConversionAllowLossy + range:range + remainingRange:nil]) { + NSLog(@"failed to read key event %@", theEvent); + return; + } + + uint32_t rune = (uint32_t)buf[0]<<0 | (uint32_t)buf[1]<<8 | (uint32_t)buf[2]<<16 | (uint32_t)buf[3]<<24; + + uint8_t direction; + if ([theEvent isARepeat]) { + direction = 0; + } else if (theEvent.type == NSKeyDown) { + direction = 1; + } else { + direction = 2; + } + eventKey((int32_t)rune, direction, theEvent.keyCode, theEvent.modifierFlags); +} + +- (void)flagsChanged:(NSEvent *)theEvent { + eventFlags(theEvent.modifierFlags); +} +@end + + +void +runApp(void) { + [NSAutoreleasePool new]; + [NSApplication sharedApplication]; + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + + id menuBar = [[NSMenu new] autorelease]; + id menuItem = [[NSMenuItem new] autorelease]; + [menuBar addItem:menuItem]; + [NSApp setMainMenu:menuBar]; + + id menu = [[NSMenu new] autorelease]; + id name = [[NSProcessInfo processInfo] processName]; + + id hideMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Hide" + action:@selector(hide:) keyEquivalent:@"h"] + autorelease]; + [menu addItem:hideMenuItem]; + + id quitMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Quit" + action:@selector(terminate:) keyEquivalent:@"q"] + autorelease]; + [menu addItem:quitMenuItem]; + [menuItem setSubmenu:menu]; + + NSRect rect = NSMakeRect(0, 0, 400, 400); + + NSWindow* window = [[[NSWindow alloc] initWithContentRect:rect + styleMask:NSTitledWindowMask + backing:NSBackingStoreBuffered + defer:NO] + autorelease]; + window.styleMask |= NSResizableWindowMask; + window.styleMask |= NSMiniaturizableWindowMask ; + window.styleMask |= NSClosableWindowMask; + window.title = name; + [window cascadeTopLeftFromPoint:NSMakePoint(20,20)]; + + NSOpenGLPixelFormatAttribute attr[] = { + NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, + NSOpenGLPFAColorSize, 24, + NSOpenGLPFAAlphaSize, 8, + NSOpenGLPFADepthSize, 16, + NSOpenGLPFAAccelerated, + NSOpenGLPFADoubleBuffer, + NSOpenGLPFAAllowOfflineRenderers, + 0 + }; + id pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr]; + MobileGLView* view = [[MobileGLView alloc] initWithFrame:rect pixelFormat:pixFormat]; + [window setContentView:view]; + [window setDelegate:view]; + [NSApp setDelegate:view]; + + window.nextResponder = [[[MobileResponder alloc] init] autorelease]; + + [NSApp run]; +} + +void stopApp(void) { + [NSApp terminate:nil]; +} diff --git a/src/golang.org/x/mobile/app/darwin_armx.go b/src/golang.org/x/mobile/app/darwin_armx.go new file mode 100644 index 0000000000..3639c7d749 --- /dev/null +++ b/src/golang.org/x/mobile/app/darwin_armx.go @@ -0,0 +1,180 @@ +// Copyright 2015 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 darwin +// +build arm arm64 + +package app + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation -framework UIKit -framework GLKit -framework OpenGLES -framework QuartzCore +#include +#include +#include + +extern struct utsname sysInfo; + +void runApp(void); +void setContext(void* context); +uint64_t threadID(); +*/ +import "C" +import ( + "log" + "runtime" + "strings" + "sync" + "unsafe" + + "golang.org/x/mobile/event/config" + "golang.org/x/mobile/event/lifecycle" + "golang.org/x/mobile/event/paint" + "golang.org/x/mobile/event/touch" + "golang.org/x/mobile/geom" + "golang.org/x/mobile/gl" +) + +var initThreadID uint64 + +func init() { + // Lock the goroutine responsible for initialization to an OS thread. + // This means the goroutine running main (and calling the run function + // below) is locked to the OS thread that started the program. This is + // necessary for the correct delivery of UIKit events to the process. + // + // A discussion on this topic: + // https://groups.google.com/forum/#!msg/golang-nuts/IiWZ2hUuLDA/SNKYYZBelsYJ + runtime.LockOSThread() + initThreadID = uint64(C.threadID()) +} + +func main(f func(App)) { + if tid := uint64(C.threadID()); tid != initThreadID { + log.Fatalf("app.Run called on thread %d, but app.init ran on %d", tid, initThreadID) + } + + go func() { + f(app{}) + // TODO(crawshaw): trigger runApp to return + }() + C.runApp() + panic("unexpected return from app.runApp") +} + +var screenScale int // [UIScreen mainScreen].scale, either 1, 2, or 3. + +//export setScreen +func setScreen(scale int) { + C.uname(&C.sysInfo) + name := C.GoString(&C.sysInfo.machine[0]) + + var v float32 + + switch { + case strings.HasPrefix(name, "iPhone"): + v = 163 + case strings.HasPrefix(name, "iPad"): + // TODO: is there a better way to distinguish the iPad Mini? + switch name { + case "iPad2,5", "iPad2,6", "iPad2,7", "iPad4,4", "iPad4,5", "iPad4,6", "iPad4,7": + v = 163 // iPad Mini + default: + v = 132 + } + default: + v = 163 // names like i386 and x86_64 are the simulator + } + + if v == 0 { + log.Printf("unknown machine: %s", name) + v = 163 // emergency fallback + } + + pixelsPerPt = v * float32(scale) / 72 + screenScale = scale +} + +//export updateConfig +func updateConfig(width, height int) { + widthPx := screenScale * width + heightPx := screenScale * height + eventsIn <- config.Event{ + WidthPx: widthPx, + HeightPx: heightPx, + WidthPt: geom.Pt(float32(widthPx) / pixelsPerPt), + HeightPt: geom.Pt(float32(heightPx) / pixelsPerPt), + PixelsPerPt: pixelsPerPt, + } +} + +var startedgl = false + +// touchIDs is the current active touches. The position in the array +// is the ID, the value is the UITouch* pointer value. +// +// It is widely reported that the iPhone can handle up to 5 simultaneous +// touch events, while the iPad can handle 11. +var touchIDs [11]uintptr + +var touchEvents struct { + sync.Mutex + pending []touch.Event +} + +//export sendTouch +func sendTouch(cTouch, cTouchType uintptr, x, y float32) { + id := -1 + for i, val := range touchIDs { + if val == cTouch { + id = i + break + } + } + if id == -1 { + for i, val := range touchIDs { + if val == 0 { + touchIDs[i] = cTouch + id = i + break + } + } + if id == -1 { + panic("out of touchIDs") + } + } + + t := touch.Type(cTouchType) + if t == touch.TypeEnd { + touchIDs[id] = 0 + } + + eventsIn <- touch.Event{ + X: x, + Y: y, + Sequence: touch.Sequence(id), + Type: t, + } +} + +//export drawgl +func drawgl(ctx uintptr) { + if !startedgl { + startedgl = true + C.setContext(unsafe.Pointer(ctx)) + // TODO(crawshaw): not just on process start. + sendLifecycle(lifecycle.StageFocused) + } + + eventsIn <- paint.Event{} + + for { + select { + case <-gl.WorkAvailable: + gl.DoWork() + case <-endPaint: + return + } + } +} diff --git a/src/golang.org/x/mobile/app/darwin_armx.m b/src/golang.org/x/mobile/app/darwin_armx.m new file mode 100644 index 0000000000..c8ea451226 --- /dev/null +++ b/src/golang.org/x/mobile/app/darwin_armx.m @@ -0,0 +1,113 @@ +// Copyright 2015 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 darwin +// +build arm arm64 + +#include "_cgo_export.h" +#include +#include +#include + +#import +#import + +struct utsname sysInfo; + +@interface GoAppAppController : GLKViewController +@end + +@interface GoAppAppDelegate : UIResponder +@property (strong, nonatomic) UIWindow *window; +@property (strong, nonatomic) GoAppAppController *controller; +@end + +@implementation GoAppAppDelegate +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.controller = [[GoAppAppController alloc] initWithNibName:nil bundle:nil]; + self.window.rootViewController = self.controller; + [self.window makeKeyAndVisible]; + return YES; +} +@end + +@interface GoAppAppController () +@property (strong, nonatomic) EAGLContext *context; +@end + +@implementation GoAppAppController +- (void)viewDidLoad { + [super viewDidLoad]; + self.preferredFramesPerSecond = 60; + self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; + GLKView *view = (GLKView *)self.view; + view.context = self.context; + view.drawableDepthFormat = GLKViewDrawableDepthFormat24; + view.multipleTouchEnabled = true; // TODO expose setting to user. + + int scale = 1; + if ([[UIScreen mainScreen] respondsToSelector:@selector(displayLinkWithTarget:selector:)]) { + scale = (int)[UIScreen mainScreen].scale; // either 1.0, 2.0, or 3.0. + } + setScreen(scale); + + CGSize size = [UIScreen mainScreen].bounds.size; + updateConfig((int)size.width, (int)size.height); +} + +- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { + updateConfig((int)size.width, (int)size.height); +} + +- (void)update { + drawgl((GoUintptr)self.context); +} + +#define TOUCH_TYPE_BEGIN 0 // touch.TypeBegin +#define TOUCH_TYPE_MOVE 1 // touch.TypeMove +#define TOUCH_TYPE_END 2 // touch.TypeEnd + +static void sendTouches(int change, NSSet* touches) { + CGFloat scale = [UIScreen mainScreen].scale; + for (UITouch* touch in touches) { + CGPoint p = [touch locationInView:touch.view]; + sendTouch((GoUintptr)touch, (GoUintptr)change, p.x*scale, p.y*scale); + } +} + +- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { + sendTouches(TOUCH_TYPE_BEGIN, touches); +} + +- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event { + sendTouches(TOUCH_TYPE_MOVE, touches); +} + +- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { + sendTouches(TOUCH_TYPE_END, touches); +} +@end + +void runApp(void) { + @autoreleasepool { + UIApplicationMain(0, nil, nil, NSStringFromClass([GoAppAppDelegate class])); + } +} + +void setContext(void* context) { + EAGLContext* ctx = (EAGLContext*)context; + if (![EAGLContext setCurrentContext:ctx]) { + // TODO(crawshaw): determine how terrible this is. Exit? + NSLog(@"failed to set current context"); + } +} + +uint64_t threadID() { + uint64_t id; + if (pthread_threadid_np(pthread_self(), &id)) { + abort(); + } + return id; +} diff --git a/src/golang.org/x/mobile/app/doc.go b/src/golang.org/x/mobile/app/doc.go new file mode 100644 index 0000000000..9bea9086d1 --- /dev/null +++ b/src/golang.org/x/mobile/app/doc.go @@ -0,0 +1,82 @@ +// Copyright 2014 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. + +/* +Package app lets you write portable all-Go apps for Android and iOS. + +There are typically two ways to use Go on Android and iOS. The first +is to write a Go library and use `gomobile bind` to generate language +bindings for Java and Objective-C. Building a library does not +require the app package. The `gomobile bind` command produces output +that you can include in an Android Studio or Xcode project. For more +on language bindings, see https://golang.org/x/mobile/cmd/gobind. + +The second way is to write an app entirely in Go. The APIs are limited +to those that are portable between both Android and iOS, in particular +OpenGL, audio, and other Android NDK-like APIs. An all-Go app should +use this app package to initialze the app, manage its lifecycle, and +receive events. + +Building apps + +Apps written entirely in Go have a main function, and can be built +with `gomobile build`, which directly produces runnable output for +Android and iOS. + +The gomobile tool can get installed with go get. For details, see +https://golang.org/x/mobile/cmd/gomobile. + +Event processing in Native Apps + +The Go runtime is initialized on Android when NativeActivity onCreate is +called, and on iOS when the process starts. In both cases, Go init functions +run before the app lifecycle has started. + +An app is expected to call the Main function in main.main. When the function +exits, the app exits. Inside the func passed to Main, call Filter on every +event received, and then switch on its type. Registered filters run when the +event is received, not when it is sent, so that filters run in the same +goroutine as other code that calls OpenGL. + + package main + + import ( + "log" + + "golang.org/x/mobile/app" + "golang.org/x/mobile/event/lifecycle" + "golang.org/x/mobile/event/paint" + ) + + func main() { + app.Main(func(a app.App) { + for e := range a.Events() { + switch e := app.Filter(e).(type) { + case lifecycle.Event: + // ... + case paint.Event: + log.Print("Call OpenGL here.") + a.EndPaint(e) + } + } + }) + } + +An event is represented by the empty interface type interface{}. Any value can +be an event. Commonly used types include Event types defined by the following +packages: + - golang.org/x/mobile/event/config + - golang.org/x/mobile/event/lifecycle + - golang.org/x/mobile/event/mouse + - golang.org/x/mobile/event/paint + - golang.org/x/mobile/event/touch +For example, touch.Event is the type that represents touch events. Other +packages may define their own events, and send them on an app's event channel. + +Other packages can also register event filters, e.g. to manage resources in +response to lifecycle events. Such packages should call: + app.RegisterFilter(etc) +in an init function inside that package. +*/ +package app // import "golang.org/x/mobile/app" diff --git a/src/golang.org/x/mobile/app/internal/apptest/apptest.go b/src/golang.org/x/mobile/app/internal/apptest/apptest.go new file mode 100644 index 0000000000..480677367b --- /dev/null +++ b/src/golang.org/x/mobile/app/internal/apptest/apptest.go @@ -0,0 +1,67 @@ +// Copyright 2015 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. + +// Package apptest provides utilities for testing an app. +// +// It is extremely incomplete, hence it being internal. +// For starters, it should support iOS. +package apptest + +import ( + "bufio" + "bytes" + "fmt" + "net" +) + +// Port is the TCP port used to communicate with the test app. +// +// TODO(crawshaw): find a way to make this configurable. adb am extras? +const Port = "12533" + +// Comm is a simple text-based communication protocol. +// +// Assumes all sides are friendly and cooperative and that the +// communication is over at the first sign of trouble. +type Comm struct { + Conn net.Conn + Fatalf func(format string, args ...interface{}) + Printf func(format string, args ...interface{}) + + scanner *bufio.Scanner +} + +func (c *Comm) Send(cmd string, args ...interface{}) { + buf := new(bytes.Buffer) + buf.WriteString(cmd) + for _, arg := range args { + buf.WriteRune(' ') + fmt.Fprintf(buf, "%v", arg) + } + buf.WriteRune('\n') + b := buf.Bytes() + c.Printf("comm.send: %s\n", b) + if _, err := c.Conn.Write(b); err != nil { + c.Fatalf("failed to send %s: %v", b, err) + } +} + +func (c *Comm) Recv(cmd string, a ...interface{}) { + if c.scanner == nil { + c.scanner = bufio.NewScanner(c.Conn) + } + if !c.scanner.Scan() { + c.Fatalf("failed to recv %q: %v", cmd, c.scanner.Err()) + } + text := c.scanner.Text() + c.Printf("comm.recv: %s\n", text) + var recvCmd string + args := append([]interface{}{&recvCmd}, a...) + if _, err := fmt.Sscan(text, args...); err != nil { + c.Fatalf("cannot scan recv command %s: %q: %v", cmd, text, err) + } + if cmd != recvCmd { + c.Fatalf("expecting recv %q, got %v", cmd, text) + } +} diff --git a/src/golang.org/x/mobile/app/internal/callfn/callfn.go b/src/golang.org/x/mobile/app/internal/callfn/callfn.go new file mode 100644 index 0000000000..ed4cac347d --- /dev/null +++ b/src/golang.org/x/mobile/app/internal/callfn/callfn.go @@ -0,0 +1,16 @@ +// Copyright 2015 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 android,arm + +// Package callfn provides an android entry point. +// +// It is a separate package from app because it contains Go assembly, +// which does not compile in a package using cgo. +package callfn + +// CallFn calls a zero-argument function by its program counter. +// It is only intended for calling main.main. Using it for +// anything else will not end well. +func CallFn(fn uintptr) diff --git a/src/golang.org/x/mobile/app/internal/callfn/callfn_arm.s b/src/golang.org/x/mobile/app/internal/callfn/callfn_arm.s new file mode 100644 index 0000000000..d71f748d9f --- /dev/null +++ b/src/golang.org/x/mobile/app/internal/callfn/callfn_arm.s @@ -0,0 +1,11 @@ +// Copyright 2015 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. + +#include "textflag.h" +#include "funcdata.h" + +TEXT ·CallFn(SB),$0-4 + MOVW fn+0(FP), R0 + BL (R0) + RET diff --git a/src/golang.org/x/mobile/app/internal/testapp/AndroidManifest.xml b/src/golang.org/x/mobile/app/internal/testapp/AndroidManifest.xml new file mode 100644 index 0000000000..3d8213e3d7 --- /dev/null +++ b/src/golang.org/x/mobile/app/internal/testapp/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + diff --git a/src/golang.org/x/mobile/app/internal/testapp/testapp.go b/src/golang.org/x/mobile/app/internal/testapp/testapp.go new file mode 100644 index 0000000000..561a379ffc --- /dev/null +++ b/src/golang.org/x/mobile/app/internal/testapp/testapp.go @@ -0,0 +1,68 @@ +// Copyright 2015 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 darwin linux + +// Small test app used by app/app_test.go. +package main + +import ( + "log" + "net" + + "golang.org/x/mobile/app" + "golang.org/x/mobile/app/internal/apptest" + "golang.org/x/mobile/event/config" + "golang.org/x/mobile/event/lifecycle" + "golang.org/x/mobile/event/paint" + "golang.org/x/mobile/event/touch" +) + +func main() { + app.Main(func(a app.App) { + addr := "127.0.0.1:" + apptest.Port + log.Printf("addr: %s", addr) + + conn, err := net.Dial("tcp", addr) + if err != nil { + log.Fatal(err) + } + defer conn.Close() + log.Printf("dialled") + comm := &apptest.Comm{ + Conn: conn, + Fatalf: log.Panicf, + Printf: log.Printf, + } + + comm.Send("hello_from_testapp") + comm.Recv("hello_from_host") + + sendPainting := false + var c config.Event + for e := range a.Events() { + switch e := app.Filter(e).(type) { + case lifecycle.Event: + switch e.Crosses(lifecycle.StageVisible) { + case lifecycle.CrossOn: + comm.Send("lifecycle_visible") + sendPainting = true + case lifecycle.CrossOff: + comm.Send("lifecycle_not_visible") + } + case config.Event: + c = e + comm.Send("config", c.PixelsPerPt) + case paint.Event: + if sendPainting { + comm.Send("paint") + sendPainting = false + } + a.EndPaint(e) + case touch.Event: + comm.Send("touch", e.Type, e.X, e.Y) + } + } + }) +} diff --git a/src/golang.org/x/mobile/app/x11.c b/src/golang.org/x/mobile/app/x11.c new file mode 100644 index 0000000000..eaafa0dc7d --- /dev/null +++ b/src/golang.org/x/mobile/app/x11.c @@ -0,0 +1,173 @@ +// Copyright 2014 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,!android + +#include "_cgo_export.h" +#include +#include +#include +#include +#include + +static Atom wm_delete_window; + +static Window +new_window(Display *x_dpy, EGLDisplay e_dpy, int w, int h, EGLContext *ctx, EGLSurface *surf) { + static const EGLint attribs[] = { + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_BLUE_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_RED_SIZE, 8, + EGL_DEPTH_SIZE, 16, + EGL_CONFIG_CAVEAT, EGL_NONE, + EGL_NONE + }; + EGLConfig config; + EGLint num_configs; + if (!eglChooseConfig(e_dpy, attribs, &config, 1, &num_configs)) { + fprintf(stderr, "eglChooseConfig failed\n"); + exit(1); + } + EGLint vid; + if (!eglGetConfigAttrib(e_dpy, config, EGL_NATIVE_VISUAL_ID, &vid)) { + fprintf(stderr, "eglGetConfigAttrib failed\n"); + exit(1); + } + + XVisualInfo visTemplate; + visTemplate.visualid = vid; + int num_visuals; + XVisualInfo *visInfo = XGetVisualInfo(x_dpy, VisualIDMask, &visTemplate, &num_visuals); + if (!visInfo) { + fprintf(stderr, "XGetVisualInfo failed\n"); + exit(1); + } + + Window root = RootWindow(x_dpy, DefaultScreen(x_dpy)); + XSetWindowAttributes attr; + + attr.colormap = XCreateColormap(x_dpy, root, visInfo->visual, AllocNone); + if (!attr.colormap) { + fprintf(stderr, "XCreateColormap failed\n"); + exit(1); + } + + attr.event_mask = StructureNotifyMask | ExposureMask | + ButtonPressMask | ButtonReleaseMask | ButtonMotionMask; + Window win = XCreateWindow( + x_dpy, root, 0, 0, w, h, 0, visInfo->depth, InputOutput, + visInfo->visual, CWColormap | CWEventMask, &attr); + XFree(visInfo); + + XSizeHints sizehints; + sizehints.width = w; + sizehints.height = h; + sizehints.flags = USSize; + XSetNormalHints(x_dpy, win, &sizehints); + XSetStandardProperties(x_dpy, win, "App", "App", None, (char **)NULL, 0, &sizehints); + + static const EGLint ctx_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + *ctx = eglCreateContext(e_dpy, config, EGL_NO_CONTEXT, ctx_attribs); + if (!*ctx) { + fprintf(stderr, "eglCreateContext failed\n"); + exit(1); + } + *surf = eglCreateWindowSurface(e_dpy, config, win, NULL); + if (!*surf) { + fprintf(stderr, "eglCreateWindowSurface failed\n"); + exit(1); + } + return win; +} + +Display *x_dpy; +EGLDisplay e_dpy; +EGLContext e_ctx; +EGLSurface e_surf; +Window win; + +void +createWindow(void) { + x_dpy = XOpenDisplay(NULL); + if (!x_dpy) { + fprintf(stderr, "XOpenDisplay failed\n"); + exit(1); + } + e_dpy = eglGetDisplay(x_dpy); + if (!e_dpy) { + fprintf(stderr, "eglGetDisplay failed\n"); + exit(1); + } + EGLint e_major, e_minor; + if (!eglInitialize(e_dpy, &e_major, &e_minor)) { + fprintf(stderr, "eglInitialize failed\n"); + exit(1); + } + eglBindAPI(EGL_OPENGL_ES_API); + win = new_window(x_dpy, e_dpy, 400, 400, &e_ctx, &e_surf); + + wm_delete_window = XInternAtom(x_dpy, "WM_DELETE_WINDOW", True); + if (wm_delete_window != None) { + XSetWMProtocols(x_dpy, win, &wm_delete_window, 1); + } + + XMapWindow(x_dpy, win); + if (!eglMakeCurrent(e_dpy, e_surf, e_surf, e_ctx)) { + fprintf(stderr, "eglMakeCurrent failed\n"); + exit(1); + } + + // Window size and DPI should be initialized before starting app. + XEvent ev; + while (1) { + if (XCheckMaskEvent(x_dpy, StructureNotifyMask, &ev) == False) { + continue; + } + if (ev.type == ConfigureNotify) { + onResize(ev.xconfigure.width, ev.xconfigure.height); + break; + } + } +} + +void +processEvents(void) { + while (XPending(x_dpy)) { + XEvent ev; + XNextEvent(x_dpy, &ev); + switch (ev.type) { + case ButtonPress: + onTouchBegin((float)ev.xbutton.x, (float)ev.xbutton.y); + break; + case ButtonRelease: + onTouchEnd((float)ev.xbutton.x, (float)ev.xbutton.y); + break; + case MotionNotify: + onTouchMove((float)ev.xmotion.x, (float)ev.xmotion.y); + break; + case ConfigureNotify: + onResize(ev.xconfigure.width, ev.xconfigure.height); + break; + case ClientMessage: + if (wm_delete_window != None && (Atom)ev.xclient.data.l[0] == wm_delete_window) { + onStop(); + return; + } + break; + } + } +} + +void +swapBuffers(void) { + if (eglSwapBuffers(e_dpy, e_surf) == EGL_FALSE) { + fprintf(stderr, "eglSwapBuffer failed\n"); + exit(1); + } +} diff --git a/src/golang.org/x/mobile/app/x11.go b/src/golang.org/x/mobile/app/x11.go new file mode 100644 index 0000000000..cb03439b15 --- /dev/null +++ b/src/golang.org/x/mobile/app/x11.go @@ -0,0 +1,119 @@ +// Copyright 2014 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,!android + +package app + +/* +Simple on-screen app debugging for X11. Not an officially supported +development target for apps, as screens with mice are very different +than screens with touch panels. + +On Ubuntu 14.04 'Trusty', you may have to install these libraries: +sudo apt-get install libegl1-mesa-dev libgles2-mesa-dev libx11-dev +*/ + +/* +#cgo LDFLAGS: -lEGL -lGLESv2 -lX11 + +void createWindow(void); +void processEvents(void); +void swapBuffers(void); +*/ +import "C" +import ( + "runtime" + "time" + + "golang.org/x/mobile/event/config" + "golang.org/x/mobile/event/lifecycle" + "golang.org/x/mobile/event/paint" + "golang.org/x/mobile/event/touch" + "golang.org/x/mobile/geom" + "golang.org/x/mobile/gl" +) + +func init() { + registerGLViewportFilter() +} + +func main(f func(App)) { + runtime.LockOSThread() + C.createWindow() + + // TODO: send lifecycle events when e.g. the X11 window is iconified or moved off-screen. + sendLifecycle(lifecycle.StageFocused) + + donec := make(chan struct{}) + go func() { + f(app{}) + close(donec) + }() + + // TODO: can we get the actual vsync signal? + ticker := time.NewTicker(time.Second / 60) + defer ticker.Stop() + tc := ticker.C + + for { + select { + case <-donec: + return + case <-gl.WorkAvailable: + gl.DoWork() + case <-endPaint: + C.swapBuffers() + tc = ticker.C + case <-tc: + tc = nil + eventsIn <- paint.Event{} + } + C.processEvents() + } +} + +//export onResize +func onResize(w, h int) { + // TODO(nigeltao): don't assume 72 DPI. DisplayWidth and DisplayWidthMM + // is probably the best place to start looking. + pixelsPerPt = 1 + eventsIn <- config.Event{ + WidthPx: w, + HeightPx: h, + WidthPt: geom.Pt(w), + HeightPt: geom.Pt(h), + PixelsPerPt: pixelsPerPt, + } +} + +func sendTouch(t touch.Type, x, y float32) { + eventsIn <- touch.Event{ + X: x, + Y: y, + Sequence: 0, // TODO: button?? + Type: t, + } +} + +//export onTouchBegin +func onTouchBegin(x, y float32) { sendTouch(touch.TypeBegin, x, y) } + +//export onTouchMove +func onTouchMove(x, y float32) { sendTouch(touch.TypeMove, x, y) } + +//export onTouchEnd +func onTouchEnd(x, y float32) { sendTouch(touch.TypeEnd, x, y) } + +var stopped bool + +//export onStop +func onStop() { + if stopped { + return + } + stopped = true + sendLifecycle(lifecycle.StageDead) + eventsIn <- stopPumping{} +} diff --git a/src/golang.org/x/mobile/asset/asset.go b/src/golang.org/x/mobile/asset/asset.go new file mode 100644 index 0000000000..fd3f59188d --- /dev/null +++ b/src/golang.org/x/mobile/asset/asset.go @@ -0,0 +1,34 @@ +// Copyright 2015 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 darwin linux + +// Package asset provides access to application-bundled assets. +// +// On Android, assets are accessed via android.content.res.AssetManager. +// These files are stored in the assets/ directory of the app. Any raw asset +// can be accessed by its direct relative name. For example assets/img.png +// can be opened with Open("img.png"). +// +// On iOS an asset is a resource stored in the application bundle. +// Resources can be loaded using the same relative paths. +// +// For consistency when debugging on a desktop, assets are read from a +// directory named assets under the current working directory. +package asset + +import "io" + +// Open opens a named asset. +// +// Errors are of type *os.PathError. +func Open(name string) (File, error) { + return openAsset(name) +} + +// File is an open asset. +type File interface { + io.ReadSeeker + io.Closer +} diff --git a/src/golang.org/x/mobile/asset/asset_android.go b/src/golang.org/x/mobile/asset/asset_android.go new file mode 100644 index 0000000000..0476baffa1 --- /dev/null +++ b/src/golang.org/x/mobile/asset/asset_android.go @@ -0,0 +1,124 @@ +// Copyright 2015 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. + +package asset + +/* +#cgo LDFLAGS: -llog -landroid +#include +#include +#include +#include +#include + +#define LOG_FATAL(...) __android_log_print(ANDROID_LOG_FATAL, "Go/asset", __VA_ARGS__) + +// asset_manager is the asset manager of the app. +AAssetManager* asset_manager; + +void asset_manager_init(void* java_vm, void* ctx) { + JavaVM* vm = (JavaVM*)(java_vm); + JNIEnv* env; + int err; + int attached = 0; + + err = (*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6); + if (err != JNI_OK) { + if (err == JNI_EDETACHED) { + if ((*vm)->AttachCurrentThread(vm, &env, 0) != 0) { + LOG_FATAL("cannot attach JVM"); + } + attached = 1; + } else { + LOG_FATAL("GetEnv unexpected error: %d", err); + } + } + + // Equivalent to: + // assetManager = ctx.getResources().getAssets(); + jclass ctx_clazz = (*env)->FindClass(env, "android/content/Context"); + jmethodID getres_id = (*env)->GetMethodID(env, ctx_clazz, "getResources", "()Landroid/content/res/Resources;"); + jobject res = (*env)->CallObjectMethod(env, ctx, getres_id); + jclass res_clazz = (*env)->FindClass(env, "android/content/res/Resources"); + jmethodID getam_id = (*env)->GetMethodID(env, res_clazz, "getAssets", "()Landroid/content/res/AssetManager;"); + jobject am = (*env)->CallObjectMethod(env, res, getam_id); + + // Pin the AssetManager and load an AAssetManager from it. + am = (*env)->NewGlobalRef(env, am); + asset_manager = AAssetManager_fromJava(env, am); + + if (attached) { + (*vm)->DetachCurrentThread(vm); + } +} +*/ +import "C" +import ( + "fmt" + "io" + "os" + "sync" + "unsafe" + + "golang.org/x/mobile/internal/mobileinit" +) + +var assetOnce sync.Once + +func assetInit() { + ctx := mobileinit.Context{} + C.asset_manager_init(ctx.JavaVM(), ctx.AndroidContext()) +} + +func openAsset(name string) (File, error) { + assetOnce.Do(assetInit) + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + a := &asset{ + ptr: C.AAssetManager_open(C.asset_manager, cname, C.AASSET_MODE_UNKNOWN), + name: name, + } + if a.ptr == nil { + return nil, a.errorf("open", "bad asset") + } + return a, nil +} + +type asset struct { + ptr *C.AAsset + name string +} + +func (a *asset) errorf(op string, format string, v ...interface{}) error { + return &os.PathError{ + Op: op, + Path: a.name, + Err: fmt.Errorf(format, v...), + } +} + +func (a *asset) Read(p []byte) (n int, err error) { + n = int(C.AAsset_read(a.ptr, unsafe.Pointer(&p[0]), C.size_t(len(p)))) + if n == 0 && len(p) > 0 { + return 0, io.EOF + } + if n < 0 { + return 0, a.errorf("read", "negative bytes: %d", n) + } + return n, nil +} + +func (a *asset) Seek(offset int64, whence int) (int64, error) { + // TODO(crawshaw): use AAsset_seek64 if it is available. + off := C.AAsset_seek(a.ptr, C.off_t(offset), C.int(whence)) + if off == -1 { + return 0, a.errorf("seek", "bad result for offset=%d, whence=%d", offset, whence) + } + return int64(off), nil +} + +func (a *asset) Close() error { + C.AAsset_close(a.ptr) + return nil +} diff --git a/src/golang.org/x/mobile/asset/asset_darwin_armx.go b/src/golang.org/x/mobile/asset/asset_darwin_armx.go new file mode 100644 index 0000000000..6c9eaec3b4 --- /dev/null +++ b/src/golang.org/x/mobile/asset/asset_darwin_armx.go @@ -0,0 +1,24 @@ +// Copyright 2015 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 darwin +// +build arm arm64 + +package asset + +import ( + "os" + "path/filepath" +) + +func openAsset(name string) (File, error) { + if !filepath.IsAbs(name) { + name = filepath.Join("assets", name) + } + f, err := os.Open(name) + if err != nil { + return nil, err + } + return f, nil +} diff --git a/src/golang.org/x/mobile/asset/asset_desktop.go b/src/golang.org/x/mobile/asset/asset_desktop.go new file mode 100644 index 0000000000..b23a6a3e2d --- /dev/null +++ b/src/golang.org/x/mobile/asset/asset_desktop.go @@ -0,0 +1,23 @@ +// Copyright 2014 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,!android darwin,!arm,!arm64 + +package asset + +import ( + "os" + "path/filepath" +) + +func openAsset(name string) (File, error) { + if !filepath.IsAbs(name) { + name = filepath.Join("assets", name) + } + f, err := os.Open(name) + if err != nil { + return nil, err + } + return f, nil +} diff --git a/src/golang.org/x/mobile/bind/bind.go b/src/golang.org/x/mobile/bind/bind.go new file mode 100644 index 0000000000..46286a2674 --- /dev/null +++ b/src/golang.org/x/mobile/bind/bind.go @@ -0,0 +1,78 @@ +// Copyright 2014 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. + +// Package bind implements a code generator for gobind. +// +// See the documentation on the gobind command for usage details +// and the list of currently supported types. +// (http://godoc.org/golang.org/x/mobile/cmd/gobind) +package bind // import "golang.org/x/mobile/bind" + +// TODO(crawshaw): slice support +// TODO(crawshaw): channel support + +import ( + "bytes" + "go/format" + "go/token" + "go/types" + "io" +) + +// GenJava generates a Java API from a Go package. +func GenJava(w io.Writer, fset *token.FileSet, pkg *types.Package) error { + buf := new(bytes.Buffer) + g := &javaGen{ + printer: &printer{buf: buf, indentEach: []byte(" ")}, + fset: fset, + pkg: pkg, + } + if err := g.gen(); err != nil { + return err + } + _, err := io.Copy(w, buf) + return err +} + +// GenGo generates a Go stub to support foreign language APIs. +func GenGo(w io.Writer, fset *token.FileSet, pkg *types.Package) error { + buf := new(bytes.Buffer) + g := &goGen{ + printer: &printer{buf: buf, indentEach: []byte("\t")}, + fset: fset, + pkg: pkg, + } + if err := g.gen(); err != nil { + return err + } + src := buf.Bytes() + srcf, err := format.Source(src) + if err != nil { + w.Write(src) // for debugging + return err + } + _, err = w.Write(srcf) + return err +} + +// GenObjc generates the Objective-C API from a Go package. +func GenObjc(w io.Writer, fset *token.FileSet, pkg *types.Package, isHeader bool) error { + buf := new(bytes.Buffer) + g := &objcGen{ + printer: &printer{buf: buf, indentEach: []byte("\t")}, + fset: fset, + pkg: pkg, + } + var err error + if isHeader { + err = g.genH() + } else { + err = g.genM() + } + if err != nil { + return err + } + _, err = io.Copy(w, buf) + return err +} diff --git a/src/golang.org/x/mobile/bind/bind_test.go b/src/golang.org/x/mobile/bind/bind_test.go new file mode 100644 index 0000000000..b9a3dfd027 --- /dev/null +++ b/src/golang.org/x/mobile/bind/bind_test.go @@ -0,0 +1,165 @@ +package bind + +import ( + "bytes" + "flag" + "go/ast" + "go/parser" + "go/token" + "go/types" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" +) + +func init() { + log.SetFlags(log.Lshortfile) +} + +var updateFlag = flag.Bool("update", false, "Update the golden files.") + +var tests = []string{ + "testdata/basictypes.go", + "testdata/structs.go", + "testdata/interfaces.go", + "testdata/issue10788.go", +} + +var fset = token.NewFileSet() + +func typeCheck(t *testing.T, filename string) *types.Package { + f, err := parser.ParseFile(fset, filename, nil, parser.AllErrors) + if err != nil { + t.Fatalf("%s: %v", filename, err) + } + + pkgName := filepath.Base(filename) + pkgName = strings.TrimSuffix(pkgName, ".go") + + // typecheck and collect typechecker errors + var conf types.Config + conf.Error = func(err error) { + t.Error(err) + } + pkg, err := conf.Check(pkgName, fset, []*ast.File{f}, nil) + if err != nil { + t.Fatal(err) + } + return pkg +} + +// diff runs the command "diff a b" and returns its output +func diff(a, b string) string { + var buf bytes.Buffer + var cmd *exec.Cmd + switch runtime.GOOS { + case "plan9": + cmd = exec.Command("/bin/diff", "-c", a, b) + default: + cmd = exec.Command("/usr/bin/diff", "-u", a, b) + } + cmd.Stdout = &buf + cmd.Stderr = &buf + cmd.Run() + return buf.String() +} + +func writeTempFile(t *testing.T, name string, contents []byte) string { + f, err := ioutil.TempFile("", name) + if err != nil { + t.Fatal(err) + } + if _, err := f.Write(contents); err != nil { + t.Fatal(err) + } + if err := f.Close(); err != nil { + t.Fatal(err) + } + return f.Name() +} + +func TestGenObjc(t *testing.T) { + var suffixes = map[bool]string{ + true: ".objc.h.golden", + false: ".objc.m.golden", + } + + for _, filename := range tests { + pkg := typeCheck(t, filename) + + for isHeader, suffix := range suffixes { + var buf bytes.Buffer + if err := GenObjc(&buf, fset, pkg, isHeader); err != nil { + t.Errorf("%s: %v", filename, err) + continue + } + out := writeTempFile(t, "generated"+suffix, buf.Bytes()) + defer os.Remove(out) + golden := filename[:len(filename)-len(".go")] + suffix + if diffstr := diff(golden, out); diffstr != "" { + t.Errorf("%s: does not match Objective-C golden:\n%s", filename, diffstr) + if *updateFlag { + t.Logf("Updating %s...", golden) + err := exec.Command("/bin/cp", out, golden).Run() + if err != nil { + t.Errorf("Update failed: %s", err) + } + } + } + } + } +} + +func TestGenJava(t *testing.T) { + for _, filename := range tests { + var buf bytes.Buffer + pkg := typeCheck(t, filename) + if err := GenJava(&buf, fset, pkg); err != nil { + t.Errorf("%s: %v", filename, err) + continue + } + out := writeTempFile(t, "java", buf.Bytes()) + defer os.Remove(out) + golden := filename[:len(filename)-len(".go")] + ".java.golden" + if diffstr := diff(golden, out); diffstr != "" { + t.Errorf("%s: does not match Java golden:\n%s", filename, diffstr) + + if *updateFlag { + t.Logf("Updating %s...", golden) + if err := exec.Command("/bin/cp", out, golden).Run(); err != nil { + t.Errorf("Update failed: %s", err) + } + } + + } + } +} + +func TestGenGo(t *testing.T) { + for _, filename := range tests { + var buf bytes.Buffer + pkg := typeCheck(t, filename) + if err := GenGo(&buf, fset, pkg); err != nil { + t.Errorf("%s: %v", filename, err) + continue + } + out := writeTempFile(t, "go", buf.Bytes()) + defer os.Remove(out) + golden := filename + ".golden" + if diffstr := diff(golden, out); diffstr != "" { + t.Errorf("%s: does not match Java golden:\n%s", filename, diffstr) + + if *updateFlag { + t.Logf("Updating %s...", golden) + if err := exec.Command("/bin/cp", out, golden).Run(); err != nil { + t.Errorf("Update failed: %s", err) + } + } + } + } +} diff --git a/src/golang.org/x/mobile/bind/gengo.go b/src/golang.org/x/mobile/bind/gengo.go new file mode 100644 index 0000000000..f503dc1bcf --- /dev/null +++ b/src/golang.org/x/mobile/bind/gengo.go @@ -0,0 +1,448 @@ +// Copyright 2014 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. + +package bind + +import ( + "fmt" + "go/token" + "go/types" + "log" + "strings" +) + +type goGen struct { + *printer + fset *token.FileSet + pkg *types.Package + err ErrorList +} + +func (g *goGen) errorf(format string, args ...interface{}) { + g.err = append(g.err, fmt.Errorf(format, args...)) +} + +const goPreamble = `// Package go_%s is an autogenerated binder stub for package %s. +// gobind -lang=go %s +// +// File is generated by gobind. Do not edit. +package go_%s + +import ( + "golang.org/x/mobile/bind/seq" + %q +) + +` + +func (g *goGen) genPreamble() { + n := g.pkg.Name() + g.Printf(goPreamble, n, n, g.pkg.Path(), n, g.pkg.Path()) +} + +func (g *goGen) genFuncBody(o *types.Func, selectorLHS string) { + sig := o.Type().(*types.Signature) + params := sig.Params() + for i := 0; i < params.Len(); i++ { + p := params.At(i) + g.genRead("param_"+paramName(params, i), "in", p.Type()) + } + + res := sig.Results() + if res.Len() > 2 || res.Len() == 2 && !isErrorType(res.At(1).Type()) { + g.errorf("functions and methods must return either zero or one values, and optionally an error") + return + } + returnsValue := false + returnsError := false + if res.Len() == 1 { + if isErrorType(res.At(0).Type()) { + returnsError = true + g.Printf("err := ") + } else { + returnsValue = true + g.Printf("res := ") + } + } else if res.Len() == 2 { + returnsValue = true + returnsError = true + g.Printf("res, err := ") + } + + g.Printf("%s.%s(", selectorLHS, o.Name()) + for i := 0; i < params.Len(); i++ { + if i > 0 { + g.Printf(", ") + } + g.Printf("param_%s", paramName(params, i)) + } + g.Printf(")\n") + + if returnsValue { + g.genWrite("res", "out", res.At(0).Type()) + } + if returnsError { + g.genWrite("err", "out", res.At(res.Len()-1).Type()) + } +} + +func (g *goGen) genWrite(valName, seqName string, T types.Type) { + if isErrorType(T) { + g.Printf("if %s == nil {\n", valName) + g.Printf(" %s.WriteString(\"\");\n", seqName) + g.Printf("} else {\n") + g.Printf(" %s.WriteString(%s.Error());\n", seqName, valName) + g.Printf("}\n") + return + } + switch T := T.(type) { + case *types.Pointer: + // TODO(crawshaw): test *int + // TODO(crawshaw): test **Generator + switch T := T.Elem().(type) { + case *types.Named: + obj := T.Obj() + if obj.Pkg() != g.pkg { + g.errorf("type %s not defined in package %s", T, g.pkg) + return + } + g.Printf("%s.WriteGoRef(%s)\n", seqName, valName) + default: + g.errorf("unsupported type %s", T) + } + case *types.Named: + switch u := T.Underlying().(type) { + case *types.Interface, *types.Pointer: + g.Printf("%s.WriteGoRef(%s)\n", seqName, valName) + default: + g.errorf("unsupported, direct named type %s: %s", T, u) + } + default: + g.Printf("%s.Write%s(%s);\n", seqName, seqType(T), valName) + } +} + +func (g *goGen) genFunc(o *types.Func) { + g.Printf("func proxy_%s(out, in *seq.Buffer) {\n", o.Name()) + g.Indent() + g.genFuncBody(o, g.pkg.Name()) + g.Outdent() + g.Printf("}\n\n") +} + +func exportedMethodSet(T types.Type) []*types.Func { + var methods []*types.Func + methodset := types.NewMethodSet(T) + for i := 0; i < methodset.Len(); i++ { + obj := methodset.At(i).Obj() + if !obj.Exported() { + continue + } + switch obj := obj.(type) { + case *types.Func: + methods = append(methods, obj) + default: + log.Panicf("unexpected methodset obj: %s", obj) + } + } + return methods +} + +func exportedFields(T *types.Struct) []*types.Var { + var fields []*types.Var + for i := 0; i < T.NumFields(); i++ { + f := T.Field(i) + if !f.Exported() { + continue + } + fields = append(fields, f) + } + return fields +} + +func (g *goGen) genStruct(obj *types.TypeName, T *types.Struct) { + fields := exportedFields(T) + methods := exportedMethodSet(types.NewPointer(obj.Type())) + + g.Printf("const (\n") + g.Indent() + g.Printf("proxy%s_Descriptor = \"go.%s.%s\"\n", obj.Name(), g.pkg.Name(), obj.Name()) + for i, f := range fields { + g.Printf("proxy%s_%s_Get_Code = 0x%x0f\n", obj.Name(), f.Name(), i) + g.Printf("proxy%s_%s_Set_Code = 0x%x1f\n", obj.Name(), f.Name(), i) + } + for i, m := range methods { + g.Printf("proxy%s_%s_Code = 0x%x0c\n", obj.Name(), m.Name(), i) + } + g.Outdent() + g.Printf(")\n\n") + + g.Printf("type proxy%s seq.Ref\n\n", obj.Name()) + + for _, f := range fields { + seqTyp := seqType(f.Type()) + + g.Printf("func proxy%s_%s_Set(out, in *seq.Buffer) {\n", obj.Name(), f.Name()) + g.Indent() + g.Printf("ref := in.ReadRef()\n") + g.Printf("v := in.Read%s()\n", seqTyp) + if seqTyp == "Ref" { + g.Printf("ref.Get().(*%s.%s).%s = v.Get().(%s)\n", g.pkg.Name(), obj.Name(), f.Name(), g.typeString(f.Type())) + } else { + // TODO(crawshaw): other kinds of non-ptr types. + g.Printf("ref.Get().(*%s.%s).%s = v\n", g.pkg.Name(), obj.Name(), f.Name()) + } + g.Outdent() + g.Printf("}\n\n") + + g.Printf("func proxy%s_%s_Get(out, in *seq.Buffer) {\n", obj.Name(), f.Name()) + g.Indent() + g.Printf("ref := in.ReadRef()\n") + g.Printf("v := ref.Get().(*%s.%s).%s\n", g.pkg.Name(), obj.Name(), f.Name()) + if seqTyp == "Ref" { + g.Printf("out.WriteGoRef(v)\n") + } else { + g.Printf("out.Write%s(v)\n", seqTyp) + } + g.Outdent() + g.Printf("}\n\n") + } + + for _, m := range methods { + g.Printf("func proxy%s_%s(out, in *seq.Buffer) {\n", obj.Name(), m.Name()) + g.Indent() + g.Printf("ref := in.ReadRef()\n") + g.Printf("v := ref.Get().(*%s.%s)\n", g.pkg.Name(), obj.Name()) + g.genFuncBody(m, "v") + g.Outdent() + g.Printf("}\n\n") + } + + g.Printf("func init() {\n") + g.Indent() + for _, f := range fields { + n := f.Name() + g.Printf("seq.Register(proxy%s_Descriptor, proxy%s_%s_Set_Code, proxy%s_%s_Set)\n", obj.Name(), obj.Name(), n, obj.Name(), n) + g.Printf("seq.Register(proxy%s_Descriptor, proxy%s_%s_Get_Code, proxy%s_%s_Get)\n", obj.Name(), obj.Name(), n, obj.Name(), n) + } + for _, m := range methods { + n := m.Name() + g.Printf("seq.Register(proxy%s_Descriptor, proxy%s_%s_Code, proxy%s_%s)\n", obj.Name(), obj.Name(), n, obj.Name(), n) + } + g.Outdent() + g.Printf("}\n\n") +} + +func (g *goGen) genInterface(obj *types.TypeName) { + iface := obj.Type().(*types.Named).Underlying().(*types.Interface) + + ifaceDesc := fmt.Sprintf("go.%s.%s", g.pkg.Name(), obj.Name()) + + // Descriptor and code for interface methods. + g.Printf("const (\n") + g.Indent() + g.Printf("proxy%s_Descriptor = %q\n", obj.Name(), ifaceDesc) + for i := 0; i < iface.NumMethods(); i++ { + g.Printf("proxy%s_%s_Code = 0x%x0a\n", obj.Name(), iface.Method(i).Name(), i+1) + } + g.Outdent() + g.Printf(")\n\n") + + // Define the entry points. + for i := 0; i < iface.NumMethods(); i++ { + m := iface.Method(i) + g.Printf("func proxy%s_%s(out, in *seq.Buffer) {\n", obj.Name(), m.Name()) + g.Indent() + g.Printf("ref := in.ReadRef()\n") + g.Printf("v := ref.Get().(%s.%s)\n", g.pkg.Name(), obj.Name()) + g.genFuncBody(m, "v") + g.Outdent() + g.Printf("}\n\n") + } + + // Register the method entry points. + g.Printf("func init() {\n") + g.Indent() + for i := 0; i < iface.NumMethods(); i++ { + g.Printf("seq.Register(proxy%s_Descriptor, proxy%s_%s_Code, proxy%s_%s)\n", + obj.Name(), obj.Name(), iface.Method(i).Name(), obj.Name(), iface.Method(i).Name()) + } + g.Outdent() + g.Printf("}\n\n") + + // Define a proxy interface. + g.Printf("type proxy%s seq.Ref\n\n", obj.Name()) + + for i := 0; i < iface.NumMethods(); i++ { + m := iface.Method(i) + sig := m.Type().(*types.Signature) + params := sig.Params() + res := sig.Results() + + if res.Len() > 2 || + (res.Len() == 2 && !isErrorType(res.At(1).Type())) { + g.errorf("functions and methods must return either zero or one value, and optionally an error: %s.%s", obj.Name(), m.Name()) + continue + } + + g.Printf("func (p *proxy%s) %s(", obj.Name(), m.Name()) + for i := 0; i < params.Len(); i++ { + if i > 0 { + g.Printf(", ") + } + g.Printf("%s %s", paramName(params, i), g.typeString(params.At(i).Type())) + } + g.Printf(") ") + + if res.Len() == 1 { + g.Printf(g.typeString(res.At(0).Type())) + } else if res.Len() == 2 { + g.Printf("(%s, error)", g.typeString(res.At(0).Type())) + } + g.Printf(" {\n") + g.Indent() + + g.Printf("in := new(seq.Buffer)\n") + for i := 0; i < params.Len(); i++ { + g.genWrite(paramName(params, i), "in", params.At(i).Type()) + } + + if res.Len() == 0 { + g.Printf("seq.Transact((*seq.Ref)(p), %q, proxy%s_%s_Code, in)\n", ifaceDesc, obj.Name(), m.Name()) + } else { + g.Printf("out := seq.Transact((*seq.Ref)(p), %q, proxy%s_%s_Code, in)\n", ifaceDesc, obj.Name(), m.Name()) + var rvs []string + for i := 0; i < res.Len(); i++ { + rv := fmt.Sprintf("res_%d", i) + g.genRead(rv, "out", res.At(i).Type()) + rvs = append(rvs, rv) + } + g.Printf("return %s\n", strings.Join(rvs, ",")) + } + + g.Outdent() + g.Printf("}\n\n") + } +} + +func (g *goGen) genRead(valName, seqName string, typ types.Type) { + if isErrorType(typ) { + g.Printf("%s := %s.ReadError()\n", valName, seqName) + return + } + switch t := typ.(type) { + case *types.Pointer: + switch u := t.Elem().(type) { + case *types.Named: + o := u.Obj() + if o.Pkg() != g.pkg { + g.errorf("type %s not defined in package %s", u, g.pkg) + return + } + g.Printf("// Must be a Go object\n") + g.Printf("%s_ref := %s.ReadRef()\n", valName, seqName) + g.Printf("%s := %s_ref.Get().(*%s.%s)\n", valName, valName, g.pkg.Name(), o.Name()) + default: + g.errorf("unsupported type %s", t) + } + case *types.Named: + switch t.Underlying().(type) { + case *types.Interface, *types.Pointer: + o := t.Obj() + if o.Pkg() != g.pkg { + g.errorf("type %s not defined in package %s", t, g.pkg) + return + } + g.Printf("var %s %s\n", valName, g.typeString(t)) + g.Printf("%s_ref := %s.ReadRef()\n", valName, seqName) + g.Printf("if %s_ref.Num < 0 { // go object \n", valName) + g.Printf(" %s = %s_ref.Get().(%s.%s)\n", valName, valName, g.pkg.Name(), o.Name()) + g.Printf("} else { // foreign object \n") + g.Printf(" %s = (*proxy%s)(%s_ref)\n", valName, o.Name(), valName) + g.Printf("}\n") + } + default: + g.Printf("%s := %s.Read%s()\n", valName, seqName, seqType(t)) + } +} + +func (g *goGen) typeString(typ types.Type) string { + pkg := g.pkg + + switch t := typ.(type) { + case *types.Named: + obj := t.Obj() + if obj.Pkg() == nil { // e.g. error type is *types.Named. + return types.TypeString(typ, types.RelativeTo(pkg)) + } + if obj.Pkg() != g.pkg { + g.errorf("type %s not defined in package %s", t, g.pkg) + } + + switch t.Underlying().(type) { + case *types.Interface, *types.Struct: + return fmt.Sprintf("%s.%s", pkg.Name(), types.TypeString(typ, types.RelativeTo(pkg))) + default: + g.errorf("unsupported named type %s / %T", t, t) + } + case *types.Pointer: + switch t := t.Elem().(type) { + case *types.Named: + return fmt.Sprintf("*%s", g.typeString(t)) + default: + g.errorf("not yet supported, pointer type %s / %T", t, t) + } + default: + return types.TypeString(typ, types.RelativeTo(pkg)) + } + return "" +} + +func (g *goGen) gen() error { + g.genPreamble() + + var funcs []string + + scope := g.pkg.Scope() + names := scope.Names() + for _, name := range names { + obj := scope.Lookup(name) + if !obj.Exported() { + continue + } + + switch obj := obj.(type) { + // TODO(crawshaw): case *types.Const: + // TODO(crawshaw): case *types.Var: + case *types.Func: + g.genFunc(obj) + funcs = append(funcs, obj.Name()) + case *types.TypeName: + named := obj.Type().(*types.Named) + switch T := named.Underlying().(type) { + case *types.Struct: + g.genStruct(obj, T) + case *types.Interface: + g.genInterface(obj) + } + + default: + g.errorf("not yet supported, name for %v / %T", obj, obj) + continue + } + } + + g.Printf("func init() {\n") + g.Indent() + for i, name := range funcs { + g.Printf("seq.Register(%q, %d, proxy_%s)\n", g.pkg.Name(), i+1, name) + } + g.Outdent() + g.Printf("}\n") + + if len(g.err) > 0 { + return g.err + } + return nil +} diff --git a/src/golang.org/x/mobile/bind/genjava.go b/src/golang.org/x/mobile/bind/genjava.go new file mode 100644 index 0000000000..332b179696 --- /dev/null +++ b/src/golang.org/x/mobile/bind/genjava.go @@ -0,0 +1,601 @@ +// Copyright 2014 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. + +package bind + +import ( + "bytes" + "fmt" + "go/token" + "go/types" + "io" + "regexp" + "unicode" + "unicode/utf8" +) + +// TODO(crawshaw): disallow basic android java type names in exported symbols. +// TODO(crawshaw): generate all relevant "implements" relationships for interfaces. +// TODO(crawshaw): consider introducing Java functions for casting to and from interfaces at runtime. + +type ErrorList []error + +func (list ErrorList) Error() string { + buf := new(bytes.Buffer) + for i, err := range list { + if i > 0 { + buf.WriteRune('\n') + } + io.WriteString(buf, err.Error()) + } + return buf.String() +} + +type javaGen struct { + *printer + fset *token.FileSet + pkg *types.Package + err ErrorList +} + +func (g *javaGen) genStruct(obj *types.TypeName, T *types.Struct) { + fields := exportedFields(T) + methods := exportedMethodSet(types.NewPointer(obj.Type())) + + g.Printf("public static final class %s implements go.Seq.Object {\n", obj.Name()) + g.Indent() + g.Printf("private static final String DESCRIPTOR = \"go.%s.%s\";\n", g.pkg.Name(), obj.Name()) + for i, f := range fields { + g.Printf("private static final int FIELD_%s_GET = 0x%x0f;\n", f.Name(), i) + g.Printf("private static final int FIELD_%s_SET = 0x%x1f;\n", f.Name(), i) + } + for i, m := range methods { + g.Printf("private static final int CALL_%s = 0x%x0c;\n", m.Name(), i) + } + g.Printf("\n") + + g.Printf("private go.Seq.Ref ref;\n\n") + + n := obj.Name() + g.Printf("private %s(go.Seq.Ref ref) { this.ref = ref; }\n\n", n) + g.Printf(`public go.Seq.Ref ref() { return ref; } + +public void call(int code, go.Seq in, go.Seq out) { + throw new RuntimeException("internal error: cycle: cannot call concrete proxy"); +} + +`) + + for _, f := range fields { + g.Printf("public %s get%s() {\n", g.javaType(f.Type()), f.Name()) + g.Indent() + g.Printf("Seq in = new Seq();\n") + g.Printf("Seq out = new Seq();\n") + g.Printf("in.writeRef(ref);\n") + g.Printf("Seq.send(DESCRIPTOR, FIELD_%s_GET, in, out);\n", f.Name()) + if seqType(f.Type()) == "Ref" { + g.Printf("return new %s(out.read%s);\n", g.javaType(f.Type()), seqRead(f.Type())) + } else { + g.Printf("return out.read%s;\n", seqRead(f.Type())) + } + g.Outdent() + g.Printf("}\n\n") + + g.Printf("public void set%s(%s v) {\n", f.Name(), g.javaType(f.Type())) + g.Indent() + g.Printf("Seq in = new Seq();\n") + g.Printf("Seq out = new Seq();\n") + g.Printf("in.writeRef(ref);\n") + g.Printf("in.write%s;\n", seqWrite(f.Type(), "v")) + g.Printf("Seq.send(DESCRIPTOR, FIELD_%s_SET, in, out);\n", f.Name()) + g.Outdent() + g.Printf("}\n\n") + } + + for _, m := range methods { + g.genFunc(m, true) + } + + g.Printf("@Override public boolean equals(Object o) {\n") + g.Indent() + g.Printf("if (o == null || !(o instanceof %s)) {\n return false;\n}\n", n) + g.Printf("%s that = (%s)o;\n", n, n) + for _, f := range fields { + nf := f.Name() + g.Printf("%s this%s = get%s();\n", g.javaType(f.Type()), nf, nf) + g.Printf("%s that%s = that.get%s();\n", g.javaType(f.Type()), nf, nf) + if isJavaPrimitive(f.Type()) { + g.Printf("if (this%s != that%s) {\n return false;\n}\n", nf, nf) + } else { + g.Printf("if (this%s == null) {\n", nf) + g.Indent() + g.Printf("if (that%s != null) {\n return false;\n}\n", nf) + g.Outdent() + g.Printf("} else if (!this%s.equals(that%s)) {\n return false;\n}\n", nf, nf) + } + } + g.Printf("return true;\n") + g.Outdent() + g.Printf("}\n\n") + + g.Printf("@Override public int hashCode() {\n") + g.Printf(" return java.util.Arrays.hashCode(new Object[] {") + for i, f := range fields { + if i > 0 { + g.Printf(", ") + } + g.Printf("get%s()", f.Name()) + } + g.Printf("});\n") + g.Printf("}\n\n") + + // TODO(crawshaw): use String() string if it is defined. + g.Printf("@Override public String toString() {\n") + g.Indent() + g.Printf("StringBuilder b = new StringBuilder();\n") + g.Printf(`b.append("%s").append("{");`, obj.Name()) + g.Printf("\n") + for _, f := range fields { + n := f.Name() + g.Printf(`b.append("%s:").append(get%s()).append(",");`, n, n) + g.Printf("\n") + } + g.Printf(`return b.append("}").toString();`) + g.Printf("\n") + g.Outdent() + g.Printf("}\n\n") + + g.Outdent() + g.Printf("}\n\n") +} + +func (g *javaGen) genInterfaceStub(o *types.TypeName, m *types.Interface) { + g.Printf("public static abstract class Stub implements %s {\n", o.Name()) + g.Indent() + + g.Printf("static final String DESCRIPTOR = \"go.%s.%s\";\n\n", g.pkg.Name(), o.Name()) + g.Printf("private final go.Seq.Ref ref;\n") + g.Printf("public Stub() {\n ref = go.Seq.createRef(this);\n}\n\n") + g.Printf("public go.Seq.Ref ref() { return ref; }\n\n") + + g.Printf("public void call(int code, go.Seq in, go.Seq out) {\n") + g.Indent() + g.Printf("switch (code) {\n") + + for i := 0; i < m.NumMethods(); i++ { + f := m.Method(i) + g.Printf("case Proxy.CALL_%s: {\n", f.Name()) + g.Indent() + + sig := f.Type().(*types.Signature) + params := sig.Params() + for i := 0; i < params.Len(); i++ { + p := sig.Params().At(i) + jt := g.javaType(p.Type()) + g.Printf("%s param_%s;\n", jt, paramName(params, i)) + g.genRead("param_"+paramName(params, i), "in", p.Type()) + } + + res := sig.Results() + var returnsError bool + var numRes = res.Len() + if (res.Len() == 1 && isErrorType(res.At(0).Type())) || + (res.Len() == 2 && isErrorType(res.At(1).Type())) { + numRes -= 1 + returnsError = true + } + + if returnsError { + g.Printf("try {\n") + g.Indent() + } + + if numRes > 0 { + g.Printf("%s result = ", g.javaType(res.At(0).Type())) + } + + g.Printf("this.%s(", f.Name()) + for i := 0; i < params.Len(); i++ { + if i > 0 { + g.Printf(", ") + } + g.Printf("param_%s", paramName(params, i)) + } + g.Printf(");\n") + + if numRes > 0 { + g.Printf("out.write%s;\n", seqWrite(res.At(0).Type(), "result")) + } + if returnsError { + g.Printf("out.writeString(null);\n") + g.Outdent() + g.Printf("} catch (Exception e) {\n") + g.Indent() + if numRes > 0 { + resTyp := res.At(0).Type() + g.Printf("%s result = %s;\n", g.javaType(resTyp), g.javaTypeDefault(resTyp)) + g.Printf("out.write%s;\n", seqWrite(resTyp, "result")) + } + g.Printf("out.writeString(e.getMessage());\n") + g.Outdent() + g.Printf("}\n") + } + g.Printf("return;\n") + g.Outdent() + g.Printf("}\n") + } + + g.Printf("default:\n throw new RuntimeException(\"unknown code: \"+ code);\n") + g.Printf("}\n") + g.Outdent() + g.Printf("}\n") + + g.Outdent() + g.Printf("}\n\n") +} + +const javaProxyPreamble = `static final class Proxy implements %s { + static final String DESCRIPTOR = Stub.DESCRIPTOR; + + private go.Seq.Ref ref; + + Proxy(go.Seq.Ref ref) { this.ref = ref; } + + public go.Seq.Ref ref() { return ref; } + + public void call(int code, go.Seq in, go.Seq out) { + throw new RuntimeException("cycle: cannot call proxy"); + } + +` + +func (g *javaGen) genInterface(o *types.TypeName) { + iface := o.Type().(*types.Named).Underlying().(*types.Interface) + + g.Printf("public interface %s extends go.Seq.Object {\n", o.Name()) + g.Indent() + + methodSigErr := false + for i := 0; i < iface.NumMethods(); i++ { + if err := g.funcSignature(iface.Method(i), false); err != nil { + methodSigErr = true + g.errorf("%v", err) + } + g.Printf(";\n\n") + } + if methodSigErr { + return // skip stub generation, more of the same errors + } + + g.genInterfaceStub(o, iface) + + g.Printf(javaProxyPreamble, o.Name()) + g.Indent() + + for i := 0; i < iface.NumMethods(); i++ { + g.genFunc(iface.Method(i), true) + } + for i := 0; i < iface.NumMethods(); i++ { + g.Printf("static final int CALL_%s = 0x%x0a;\n", iface.Method(i).Name(), i+1) + } + + g.Outdent() + g.Printf("}\n") + + g.Outdent() + g.Printf("}\n\n") +} + +func isErrorType(T types.Type) bool { + return T == types.Universe.Lookup("error").Type() +} + +func isJavaPrimitive(T types.Type) bool { + b, ok := T.(*types.Basic) + if !ok { + return false + } + switch b.Kind() { + case types.Bool, types.Uint8, types.Float32, types.Float64, + types.Int, types.Int8, types.Int16, types.Int32, types.Int64: + return true + } + return false +} + +// javaType returns a string that can be used as a Java type. +func (g *javaGen) javaType(T types.Type) string { + switch T := T.(type) { + case *types.Basic: + switch T.Kind() { + case types.Bool: + return "boolean" + case types.Int: + return "long" + case types.Int8: + return "byte" + case types.Int16: + return "short" + case types.Int32: + return "int" + case types.Int64: + return "long" + case types.Uint8: + // TODO(crawshaw): Java bytes are signed, so this is + // questionable, but vital. + return "byte" + // TODO(crawshaw): case types.Uint, types.Uint16, types.Uint32, types.Uint64: + case types.Float32: + return "float" + case types.Float64: + return "double" + case types.String: + return "String" + default: + g.errorf("unsupported return type: %s", T) + return "TODO" + } + case *types.Slice: + elem := g.javaType(T.Elem()) + return elem + "[]" + + case *types.Pointer: + if _, ok := T.Elem().(*types.Named); ok { + return g.javaType(T.Elem()) + } + panic(fmt.Sprintf("unsupporter pointer to type: %s", T)) + case *types.Named: + n := T.Obj() + if n.Pkg() != g.pkg { + panic(fmt.Sprintf("type %s is in package %s, must be defined in package %s", n.Name(), n.Pkg().Name(), g.pkg.Name())) + } + // TODO(crawshaw): more checking here + return n.Name() + default: + g.errorf("unsupported javaType: %#+v, %s\n", T, T) + return "TODO" + } +} + +// javaTypeDefault returns a string that represents the default value of the mapped java type. +// TODO(hyangah): Combine javaType and javaTypeDefault? +func (g *javaGen) javaTypeDefault(T types.Type) string { + switch T := T.(type) { + case *types.Basic: + switch T.Kind() { + case types.Bool: + return "false" + case types.Int, types.Int8, types.Int16, types.Int32, + types.Int64, types.Uint8, types.Float32, types.Float64: + return "0" + case types.String: + return "null" + default: + g.errorf("unsupported return type: %s", T) + return "TODO" + } + case *types.Slice, *types.Pointer, *types.Named: + return "null" + + default: + g.errorf("unsupported javaType: %#+v, %s\n", T, T) + return "TODO" + } +} + +var paramRE = regexp.MustCompile(`^p[0-9]*$`) + +// paramName replaces incompatible name with a p0-pN name. +// Missing names, or existing names of the form p[0-9] are incompatible. +// TODO(crawshaw): Replace invalid unicode names. +func paramName(params *types.Tuple, pos int) string { + name := params.At(pos).Name() + if name == "" || name == "_" || paramRE.MatchString(name) { + name = fmt.Sprintf("p%d", pos) + } + return name +} + +func (g *javaGen) funcSignature(o *types.Func, static bool) error { + sig := o.Type().(*types.Signature) + res := sig.Results() + + var returnsError bool + var ret string + switch res.Len() { + case 2: + if !isErrorType(res.At(1).Type()) { + return fmt.Errorf("second result value must be of type error: %s", o) + } + returnsError = true + ret = g.javaType(res.At(0).Type()) + case 1: + if isErrorType(res.At(0).Type()) { + returnsError = true + ret = "void" + } else { + ret = g.javaType(res.At(0).Type()) + } + case 0: + ret = "void" + default: + return fmt.Errorf("too many result values: %s", o) + } + + g.Printf("public ") + if static { + g.Printf("static ") + } + g.Printf("%s %s(", ret, o.Name()) + params := sig.Params() + for i := 0; i < params.Len(); i++ { + if i > 0 { + g.Printf(", ") + } + v := sig.Params().At(i) + name := paramName(params, i) + jt := g.javaType(v.Type()) + g.Printf("%s %s", jt, name) + } + g.Printf(")") + if returnsError { + g.Printf(" throws Exception") + } + return nil +} + +func (g *javaGen) genFunc(o *types.Func, method bool) { + if err := g.funcSignature(o, !method); err != nil { + g.errorf("%v", err) + return + } + sig := o.Type().(*types.Signature) + res := sig.Results() + + g.Printf(" {\n") + g.Indent() + g.Printf("go.Seq _in = new go.Seq();\n") + g.Printf("go.Seq _out = new go.Seq();\n") + + returnsError := false + var resultType types.Type + if res.Len() > 0 { + if !isErrorType(res.At(0).Type()) { + resultType = res.At(0).Type() + } + if res.Len() > 1 || isErrorType(res.At(0).Type()) { + returnsError = true + } + } + if resultType != nil { + t := g.javaType(resultType) + g.Printf("%s _result;\n", t) + } + + if method { + g.Printf("_in.writeRef(ref);\n") + } + params := sig.Params() + for i := 0; i < params.Len(); i++ { + p := params.At(i) + g.Printf("_in.write%s;\n", seqWrite(p.Type(), paramName(params, i))) + } + g.Printf("Seq.send(DESCRIPTOR, CALL_%s, _in, _out);\n", o.Name()) + if resultType != nil { + g.genRead("_result", "_out", resultType) + } + if returnsError { + g.Printf(`String _err = _out.readString(); +if (_err != null) { + throw new Exception(_err); +} +`) + } + if resultType != nil { + g.Printf("return _result;\n") + } + g.Outdent() + g.Printf("}\n\n") +} + +func (g *javaGen) genRead(resName, seqName string, T types.Type) { + switch T := T.(type) { + case *types.Pointer: + // TODO(crawshaw): test *int + // TODO(crawshaw): test **Generator + switch T := T.Elem().(type) { + case *types.Named: + o := T.Obj() + if o.Pkg() != g.pkg { + g.errorf("type %s not defined in package %s", T, g.pkg) + return + } + g.Printf("%s = new %s(%s.readRef());\n", resName, o.Name(), seqName) + default: + g.errorf("unsupported type %s", T) + } + case *types.Named: + switch T.Underlying().(type) { + case *types.Interface, *types.Pointer: + o := T.Obj() + if o.Pkg() != g.pkg { + g.errorf("type %s not defined in package %s", T, g.pkg) + return + } + g.Printf("%s = new %s.Proxy(%s.readRef());\n", resName, o.Name(), seqName) + default: + g.errorf("unsupported, direct named type %s", T) + } + default: + g.Printf("%s = %s.read%s();\n", resName, seqName, seqType(T)) + } +} + +func (g *javaGen) errorf(format string, args ...interface{}) { + g.err = append(g.err, fmt.Errorf(format, args...)) +} + +const javaPreamble = `// Java Package %s is a proxy for talking to a Go program. +// gobind -lang=java %s +// +// File is generated by gobind. Do not edit. +package go.%s; + +import go.Seq; + +` + +func (g *javaGen) gen() error { + g.Printf(javaPreamble, g.pkg.Name(), g.pkg.Path(), g.pkg.Name()) + + firstRune, size := utf8.DecodeRuneInString(g.pkg.Name()) + className := string(unicode.ToUpper(firstRune)) + g.pkg.Name()[size:] + + g.Printf("public abstract class %s {\n", className) + g.Indent() + g.Printf("private %s() {} // uninstantiable\n\n", className) + scope := g.pkg.Scope() + names := scope.Names() + var funcs []string + for _, name := range names { + obj := scope.Lookup(name) + if !obj.Exported() { + continue + } + + switch o := obj.(type) { + // TODO(crawshaw): case *types.Const: + // TODO(crawshaw): case *types.Var: + case *types.Func: + g.genFunc(o, false) + funcs = append(funcs, o.Name()) + case *types.TypeName: + named := o.Type().(*types.Named) + switch t := named.Underlying().(type) { + case *types.Struct: + g.genStruct(o, t) + case *types.Interface: + g.genInterface(o) + default: + g.errorf("%s: cannot generate binding for %s: %T", g.fset.Position(o.Pos()), o.Name(), t) + continue + } + default: + g.errorf("unsupported exported type: ", obj) + } + } + + for i, name := range funcs { + g.Printf("private static final int CALL_%s = %d;\n", name, i+1) + } + + g.Printf("private static final String DESCRIPTOR = %q;\n", g.pkg.Name()) + g.Outdent() + g.Printf("}\n") + + if len(g.err) > 0 { + return g.err + } + return nil +} diff --git a/src/golang.org/x/mobile/bind/genobjc.go b/src/golang.org/x/mobile/bind/genobjc.go new file mode 100644 index 0000000000..2926807ea4 --- /dev/null +++ b/src/golang.org/x/mobile/bind/genobjc.go @@ -0,0 +1,769 @@ +// Copyright 2015 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. + +package bind + +import ( + "fmt" + "go/token" + "go/types" + "strings" + "unicode" + "unicode/utf8" +) + +type objcGen struct { + *printer + fset *token.FileSet + pkg *types.Package + err ErrorList + + // fields set by init. + pkgName string + namePrefix string + funcs []*types.Func + names []*types.TypeName +} + +func capitalize(n string) string { + firstRune, size := utf8.DecodeRuneInString(n) + return string(unicode.ToUpper(firstRune)) + n[size:] +} + +func (g *objcGen) init() { + g.pkgName = g.pkg.Name() + g.namePrefix = "Go" + capitalize(g.pkgName) + g.funcs = nil + g.names = nil + + scope := g.pkg.Scope() + for _, name := range scope.Names() { + obj := scope.Lookup(name) + if !obj.Exported() { + continue + } + switch obj := obj.(type) { + case *types.Func: + g.funcs = append(g.funcs, obj) + case *types.TypeName: + g.names = append(g.names, obj) + // TODO(hyangah): *types.Const, *types.Var + } + } +} + +const objcPreamble = `// Objective-C API for talking to %s Go package. +// gobind -lang=objc %s +// +// File is generated by gobind. Do not edit. + +` + +func (g *objcGen) genH() error { + g.init() + + g.Printf(objcPreamble, g.pkg.Path(), g.pkg.Path()) + g.Printf("#ifndef __Go%s_H__\n", capitalize(g.pkgName)) + g.Printf("#define __Go%s_H__\n", capitalize(g.pkgName)) + g.Printf("\n") + g.Printf("#include ") + g.Printf("\n\n") + + // @class names + for _, obj := range g.names { + named := obj.Type().(*types.Named) + switch named.Underlying().(type) { + case *types.Struct, *types.Interface: + g.Printf("@class %s%s;\n", g.namePrefix, obj.Name()) + } + g.Printf("\n") + } + + // @interfaces + for _, obj := range g.names { + named := obj.Type().(*types.Named) + switch t := named.Underlying().(type) { + case *types.Struct: + g.genStructH(obj, t) + case *types.Interface: + g.genInterfaceH(obj, t) + } + g.Printf("\n") + } + + // static functions. + for _, obj := range g.funcs { + g.genFuncH(obj) + g.Printf("\n") + } + + // declare all named types first. + g.Printf("#endif\n") + + if len(g.err) > 0 { + return g.err + } + return nil +} + +func (g *objcGen) genM() error { + g.init() + + g.Printf(objcPreamble, g.pkg.Path(), g.pkg.Path()) + g.Printf("#include %q\n", g.namePrefix+".h") + g.Printf("#include \n") + g.Printf("#include \"seq.h\"\n") + g.Printf("\n") + g.Printf("static NSString* errDomain = @\"go.%s\";\n", g.pkg.Path()) + g.Printf("\n") + + g.Printf("@protocol goSeqRefInterface\n") + g.Printf("-(GoSeqRef*) ref;\n") + g.Printf("@end\n") + g.Printf("\n") + + g.Printf("#define _DESCRIPTOR_ %q\n\n", g.pkgName) + for i, obj := range g.funcs { + g.Printf("#define _CALL_%s_ %d\n", obj.Name(), i+1) + } + g.Printf("\n") + + // struct, interface. + var interfaces []*types.TypeName + for _, obj := range g.names { + named := obj.Type().(*types.Named) + switch t := named.Underlying().(type) { + case *types.Struct: + g.genStructM(obj, t) + case *types.Interface: + interfaces = append(interfaces, obj) + g.genInterfaceM(obj, t) + } + g.Printf("\n") + } + + // global functions. + for _, obj := range g.funcs { + g.genFuncM(obj) + g.Printf("\n") + } + + // register proxy functions. + if len(interfaces) > 0 { + g.Printf("__attribute__((constructor)) static void init() {\n") + g.Indent() + for _, obj := range interfaces { + g.Printf("go_seq_register_proxy(\"go.%s.%s\", proxy%s%s);\n", g.pkgName, obj.Name(), g.namePrefix, obj.Name()) + } + g.Outdent() + g.Printf("}\n") + } + + if len(g.err) > 0 { + return g.err + } + + return nil +} + +type funcSummary struct { + name string + ret string + params, retParams []paramInfo +} + +type paramInfo struct { + typ types.Type + name string +} + +func (g *objcGen) funcSummary(obj *types.Func) *funcSummary { + s := &funcSummary{name: obj.Name()} + + sig := obj.Type().(*types.Signature) + params := sig.Params() + for i := 0; i < params.Len(); i++ { + p := params.At(i) + v := paramInfo{ + typ: p.Type(), + name: paramName(params, i), + } + s.params = append(s.params, v) + } + + res := sig.Results() + switch res.Len() { + case 0: + s.ret = "void" + case 1: + p := res.At(0) + if isErrorType(p.Type()) { + s.retParams = append(s.retParams, paramInfo{ + typ: p.Type(), + name: "error", + }) + s.ret = "BOOL" + } else { + name := p.Name() + if name == "" || paramRE.MatchString(name) { + name = "ret0_" + } + typ := p.Type() + s.retParams = append(s.retParams, paramInfo{typ: typ, name: name}) + s.ret = g.objcType(typ) + } + case 2: + name := res.At(0).Name() + if name == "" || paramRE.MatchString(name) { + name = "ret0_" + } + s.retParams = append(s.retParams, paramInfo{ + typ: res.At(0).Type(), + name: name, + }) + + if !isErrorType(res.At(1).Type()) { + g.errorf("second result value must be of type error: %s", obj) + return nil + } + s.retParams = append(s.retParams, paramInfo{ + typ: res.At(1).Type(), + name: "error", // TODO(hyangah): name collision check. + }) + s.ret = "BOOL" + default: + // TODO(hyangah): relax the constraint on multiple return params. + g.errorf("too many result values: %s", obj) + return nil + } + + return s +} + +func (s *funcSummary) asFunc(g *objcGen) string { + var params []string + for _, p := range s.params { + params = append(params, g.objcType(p.typ)+" "+p.name) + } + if !s.returnsVal() { + for _, p := range s.retParams { + params = append(params, g.objcType(p.typ)+"* "+p.name) + } + } + return fmt.Sprintf("%s %s%s(%s)", s.ret, g.namePrefix, s.name, strings.Join(params, ", ")) +} + +func (s *funcSummary) asMethod(g *objcGen) string { + var params []string + for i, p := range s.params { + var key string + if i != 0 { + key = p.name + } + params = append(params, fmt.Sprintf("%s:(%s)%s", key, g.objcType(p.typ), p.name)) + } + if !s.returnsVal() { + for _, p := range s.retParams { + var key string + if len(params) > 0 { + key = p.name + } + params = append(params, fmt.Sprintf("%s:(%s)%s", key, g.objcType(p.typ)+"*", p.name)) + } + } + return fmt.Sprintf("(%s)%s%s", s.ret, s.name, strings.Join(params, " ")) +} + +func (s *funcSummary) callMethod(g *objcGen) string { + var params []string + for i, p := range s.params { + var key string + if i != 0 { + key = p.name + } + params = append(params, fmt.Sprintf("%s:%s", key, p.name)) + } + if !s.returnsVal() { + for _, p := range s.retParams { + var key string + if len(params) > 0 { + key = p.name + } + params = append(params, fmt.Sprintf("%s:&%s", key, p.name)) + } + } + return fmt.Sprintf("%s%s", s.name, strings.Join(params, " ")) +} + +func (s *funcSummary) returnsVal() bool { + return len(s.retParams) == 1 && !isErrorType(s.retParams[0].typ) +} + +func (g *objcGen) genFuncH(obj *types.Func) { + if s := g.funcSummary(obj); s != nil { + g.Printf("FOUNDATION_EXPORT %s;\n", s.asFunc(g)) + } +} + +func (g *objcGen) seqType(typ types.Type) string { + s := seqType(typ) + if s == "String" { + // TODO(hyangah): non utf-8 strings. + s = "UTF8" + } + return s +} + +func (g *objcGen) genFuncM(obj *types.Func) { + s := g.funcSummary(obj) + if s == nil { + return + } + g.Printf("%s {\n", s.asFunc(g)) + g.Indent() + g.genFunc("_DESCRIPTOR_", fmt.Sprintf("_CALL_%s_", s.name), s, false) + g.Outdent() + g.Printf("}\n") +} + +func (g *objcGen) genFunc(pkgDesc, callDesc string, s *funcSummary, isMethod bool) { + g.Printf("GoSeq in_ = {};\n") + g.Printf("GoSeq out_ = {};\n") + if isMethod { + g.Printf("go_seq_writeRef(&in_, self.ref);\n") + } + for _, p := range s.params { + st := g.seqType(p.typ) + if st == "Ref" { + g.Printf("if ([(id)(%s) isKindOfClass:[%s class]]) {\n", p.name, g.refTypeBase(p.typ)) + g.Indent() + g.Printf("id %[1]s_proxy = (id)(%[1]s);\n", p.name) + g.Printf("go_seq_writeRef(&in_, %s_proxy.ref);\n", p.name) + g.Outdent() + g.Printf("} else {\n") + g.Indent() + g.Printf("go_seq_writeObjcRef(&in_, %s);\n", p.name) + g.Outdent() + g.Printf("}\n") + } else { + g.Printf("go_seq_write%s(&in_, %s);\n", st, p.name) + } + } + g.Printf("go_seq_send(%s, %s, &in_, &out_);\n", pkgDesc, callDesc) + + if s.returnsVal() { + p := s.retParams[0] + if seqTyp := g.seqType(p.typ); seqTyp != "Ref" { + g.Printf("%s %s = go_seq_read%s(&out_);\n", g.objcType(p.typ), p.name, g.seqType(p.typ)) + } else { + ptype := g.objcType(p.typ) + g.Printf("GoSeqRef* %s_ref = go_seq_readRef(&out_);\n", p.name) + g.Printf("%s %s = %s_ref.obj;\n", ptype, p.name, p.name) + g.Printf("if (%s == NULL) {\n", p.name) + g.Indent() + g.Printf("%s = [[%s alloc] initWithRef:%s_ref];\n", p.name, g.refTypeBase(p.typ), p.name) + g.Outdent() + g.Printf("}\n") + } + } else { + for _, p := range s.retParams { + if isErrorType(p.typ) { + g.Printf("NSString* _%s = go_seq_readUTF8(&out_);\n", p.name) + g.Printf("if ([_%s length] != 0 && %s != nil) {\n", p.name, p.name) + g.Indent() + g.Printf("NSMutableDictionary* details = [NSMutableDictionary dictionary];\n") + g.Printf("[details setValue:_%s forKey:NSLocalizedDescriptionKey];\n", p.name) + g.Printf("*%s = [NSError errorWithDomain:errDomain code:1 userInfo:details];\n", p.name) + g.Outdent() + g.Printf("}\n") + } else if seqTyp := g.seqType(p.typ); seqTyp != "Ref" { + g.Printf("%s %s_val = go_seq_read%s(&out_);\n", g.objcType(p.typ), p.name, g.seqType(p.typ)) + g.Printf("if (%s != NULL) {\n", p.name) + g.Indent() + g.Printf("*%s = %s_val;\n", p.name, p.name) + g.Outdent() + g.Printf("}\n") + } else { + g.Printf("GoSeqRef* %s_ref = go_seq_readRef(&out_);\n", p.name) + g.Printf("if (%s != NULL) {\n", p.name) + g.Indent() + g.Printf("*%s = %s_ref.obj;\n", p.name, p.name) + g.Printf("if (*%s == NULL) {\n", p.name) + g.Indent() + g.Printf("*%s = [[%s alloc] initWithRef:%s_ref];\n", p.name, g.refTypeBase(p.typ), p.name) + g.Outdent() + g.Printf("}\n") + g.Outdent() + g.Printf("}\n") + } + } + } + + g.Printf("go_seq_free(&in_);\n") + g.Printf("go_seq_free(&out_);\n") + if n := len(s.retParams); n > 0 { + p := s.retParams[n-1] + if isErrorType(p.typ) { + g.Printf("return ([_%s length] == 0);\n", p.name) + } else { + g.Printf("return %s;\n", p.name) + } + } +} + +func (g *objcGen) genInterfaceH(obj *types.TypeName, t *types.Interface) { + g.Printf("@protocol %s%s\n", g.namePrefix, obj.Name()) + for _, m := range exportedMethodSet(obj.Type()) { + s := g.funcSummary(m) + g.Printf("- %s;\n", s.asMethod(g)) + } + g.Printf("@end\n") +} + +func (g *objcGen) genInterfaceM(obj *types.TypeName, t *types.Interface) { + methods := exportedMethodSet(obj.Type()) + + desc := fmt.Sprintf("_GO_%s_%s", g.pkgName, obj.Name()) + g.Printf("#define %s_DESCRIPTOR_ \"go.%s.%s\"\n", desc, g.pkgName, obj.Name()) + for i, m := range methods { + g.Printf("#define %s_%s_ (0x%x0a)\n", desc, m.Name(), i+1) + } + g.Printf("\n") + + // @interface Interface -- similar to what genStructH does. + g.Printf("@interface %[1]s%[2]s : NSObject <%[1]s%[2]s> {\n", g.namePrefix, obj.Name()) + g.Printf("}\n") + g.Printf("@property(strong, readonly) id ref;\n") + g.Printf("\n") + g.Printf("- (id)initWithRef:(id)ref;\n") + for _, m := range methods { + s := g.funcSummary(m) + g.Printf("- %s;\n", s.asMethod(g)) + } + g.Printf("@end\n") + g.Printf("\n") + + // @implementation Interface -- similar to what genStructM does. + g.Printf("@implementation %s%s {\n", g.namePrefix, obj.Name()) + g.Printf("}\n") + g.Printf("\n") + g.Printf("- (id)initWithRef:(id)ref {\n") + g.Indent() + g.Printf("self = [super init];\n") + g.Printf("if (self) { _ref = ref; }\n") + g.Printf("return self;\n") + g.Outdent() + g.Printf("}\n") + g.Printf("\n") + + for _, m := range methods { + s := g.funcSummary(m) + g.Printf("- %s {\n", s.asMethod(g)) + g.Indent() + g.genFunc(desc+"_DESCRIPTOR_", desc+"_"+m.Name()+"_", s, true) + g.Outdent() + g.Printf("}\n\n") + } + g.Printf("@end\n") + g.Printf("\n") + + // proxy function. + g.Printf("static void proxy%s%s(id obj, int code, GoSeq* in, GoSeq* out) {\n", g.namePrefix, obj.Name()) + g.Indent() + g.Printf("switch (code) {\n") + for _, m := range methods { + g.Printf("case %s_%s_: {\n", desc, m.Name()) + g.Indent() + g.genInterfaceMethodProxy(obj, g.funcSummary(m)) + g.Outdent() + g.Printf("} break;\n") + } + g.Printf("default:\n") + g.Indent() + g.Printf("NSLog(@\"unknown code %%x for %s_DESCRIPTOR_\", code);\n", desc) + g.Outdent() + g.Printf("}\n") + g.Outdent() + g.Printf("}\n") +} + +func (g *objcGen) genInterfaceMethodProxy(obj *types.TypeName, s *funcSummary) { + g.Printf("id<%[1]s%[2]s> o = (id<%[1]s%[2]s>)(obj);\n", g.namePrefix, obj.Name()) + // read params from GoSeq* inseq + for _, p := range s.params { + stype := g.seqType(p.typ) + ptype := g.objcType(p.typ) + if stype == "Ref" { + g.Printf("GoSeqRef* %s_ref = go_seq_readRef(in);\n", p.name) + g.Printf("%s %s = %s_ref.obj;\n", ptype, p.name, p.name) + g.Printf("if (%s == NULL) {\n", p.name) + g.Indent() + g.Printf("%s = [[%s alloc] initWithRef:%s_ref];\n", p.name, g.refTypeBase(p.typ), p.name) + g.Outdent() + g.Printf("}\n") + } else { + g.Printf("%s %s = go_seq_read%s(in);\n", ptype, p.name, stype) + } + } + + // call method + if !s.returnsVal() { + for _, p := range s.retParams { + if isErrorType(p.typ) { + g.Printf("NSError* %s = NULL;\n", p.name) + } else { + g.Printf("%s %s;\n", g.objcType(p.typ), p.name) + } + } + } + + if s.ret == "void" { + g.Printf("[o %s];\n", s.callMethod(g)) + } else { + g.Printf("%s returnVal = [o %s];\n", s.ret, s.callMethod(g)) + } + + // write result to GoSeq* outseq + if len(s.retParams) == 0 { + return + } + if s.returnsVal() { // len(s.retParams) == 1 && s.retParams[0] != error + p := s.retParams[0] + if stype := g.seqType(p.typ); stype == "Ref" { + g.Printf("if [(id)(returnVal) isKindOfClass:[%s class]]) {\n", g.refTypeBase(p.typ)) + g.Indent() + g.Printf("idretVal_proxy = (id)(returnVal);\n") + g.Printf("go_seq_writeRef(out, retVal_proxy.ref);\n") + g.Outdent() + g.Printf("} else {\n") + g.Indent() + g.Printf("go_seq_writeRef(out, returnVal);\n") + g.Outdent() + g.Printf("}\n") + } else { + g.Printf("go_seq_write%s(out, returnVal);\n", stype) + } + return + } + for _, p := range s.retParams { + if isErrorType(p.typ) { + g.Printf("if (%s == NULL) {\n", p.name) + g.Indent() + g.Printf("go_seq_writeUTF8(out, NULL);\n") + g.Outdent() + g.Printf("} else {\n") + g.Indent() + g.Printf("NSString* %sDesc = [%s localizedDescription];\n", p.name, p.name) + g.Printf("if (%sDesc == NULL || %sDesc.length == 0) {\n", p.name, p.name) + g.Indent() + g.Printf("%sDesc = NSString(@\"%@\", %s);\n", p.name, p.name) + g.Outdent() + g.Printf("}\n") + g.Outdent() + g.Printf("go_seq_writeUTF8(out, %sDesc);\n") + g.Printf("}\n") + } else if seqTyp := g.seqType(p.typ); seqTyp != "Ref" { + // TODO(hyangah): NULL. + g.Printf("if [(id)(%s) isKindOfClass:[%s class]]) {\n", p.name, g.refTypeBase(p.typ)) + g.Indent() + g.Printf("id%[1]s_proxy = (id)(%[1]s);\n", p.name) + g.Printf("go_seq_writeRef(out, %s_proxy.ref);\n", p.name) + g.Outdent() + g.Printf("} else {\n") + g.Indent() + g.Printf("go_seq_writeObjcRef(out, %s);\n", p.name) + g.Outdent() + g.Printf("}\n") + } else { + g.Printf("go_seq_write%s(out, %s);\n", seqTyp, p.name) + } + } +} + +func (g *objcGen) genStructH(obj *types.TypeName, t *types.Struct) { + g.Printf("@interface %s%s : NSObject {\n", g.namePrefix, obj.Name()) + g.Printf("}\n") + g.Printf("@property(strong, readonly) id ref;\n") + g.Printf("\n") + g.Printf("- (id)initWithRef:(id)ref;\n") + + // accessors to exported fields. + for _, f := range exportedFields(t) { + // TODO(hyangah): error type field? + name, typ := f.Name(), g.objcType(f.Type()) + g.Printf("- (%s)%s;\n", typ, name) + g.Printf("- (void)set%s:(%s)v;\n", name, typ) + } + + // exported methods + for _, m := range exportedMethodSet(types.NewPointer(obj.Type())) { + s := g.funcSummary(m) + g.Printf("- %s;\n", s.asMethod(g)) + } + g.Printf("@end\n") +} + +func (g *objcGen) genStructM(obj *types.TypeName, t *types.Struct) { + fields := exportedFields(t) + methods := exportedMethodSet(types.NewPointer(obj.Type())) + + desc := fmt.Sprintf("_GO_%s_%s", g.pkgName, obj.Name()) + g.Printf("#define %s_DESCRIPTOR_ \"go.%s.%s\"\n", desc, g.pkgName, obj.Name()) + for i, f := range fields { + g.Printf("#define %s_FIELD_%s_GET_ (0x%x0f)\n", desc, f.Name(), i) + g.Printf("#define %s_FIELD_%s_SET_ (0x%x1f)\n", desc, f.Name(), i) + } + for i, m := range methods { + g.Printf("#define %s_%s_ (0x%x0c)\n", desc, m.Name(), i) + } + + g.Printf("\n") + g.Printf("@implementation %s%s {\n", g.namePrefix, obj.Name()) + g.Printf("}\n\n") + g.Printf("- (id)initWithRef:(id)ref {\n") + g.Indent() + g.Printf("self = [super init];\n") + g.Printf("if (self) { _ref = ref; }\n") + g.Printf("return self;\n") + g.Outdent() + g.Printf("}\n\n") + + for _, f := range fields { + // getter + // TODO(hyangah): support error type fields? + s := &funcSummary{ + name: f.Name(), + ret: g.objcType(f.Type()), + } + s.retParams = append(s.retParams, paramInfo{typ: f.Type(), name: "ret_"}) + + g.Printf("- %s {\n", s.asMethod(g)) + g.Indent() + g.genFunc(desc+"_DESCRIPTOR_", desc+"_FIELD_"+f.Name()+"_GET_", s, true) + g.Outdent() + g.Printf("}\n\n") + + // setter + s = &funcSummary{ + name: "set" + f.Name(), + ret: "void", + } + s.params = append(s.params, paramInfo{typ: f.Type(), name: "v"}) + + g.Printf("- %s {\n", s.asMethod(g)) + g.Indent() + g.genFunc(desc+"_DESCRIPTOR_", desc+"_FIELD_"+f.Name()+"_SET_", s, true) + g.Outdent() + g.Printf("}\n\n") + } + + for _, m := range methods { + s := g.funcSummary(m) + g.Printf("- %s {\n", s.asMethod(g)) + g.Indent() + g.genFunc(desc+"_DESCRIPTOR_", desc+"_"+m.Name()+"_", s, true) + g.Outdent() + g.Printf("}\n\n") + } + g.Printf("@end\n") +} + +func (g *objcGen) errorf(format string, args ...interface{}) { + g.err = append(g.err, fmt.Errorf(format, args...)) +} + +func (g *objcGen) refTypeBase(typ types.Type) string { + switch typ := typ.(type) { + case *types.Pointer: + if _, ok := typ.Elem().(*types.Named); ok { + return g.objcType(typ.Elem()) + } + case *types.Named: + n := typ.Obj() + if n.Pkg() == g.pkg { + switch typ.Underlying().(type) { + case *types.Interface, *types.Struct: + return g.namePrefix + n.Name() + } + } + } + + // fallback to whatever objcType returns. This must not happen. + return g.objcType(typ) +} + +func (g *objcGen) objcType(typ types.Type) string { + if isErrorType(typ) { + return "NSError*" + } + + switch typ := typ.(type) { + case *types.Basic: + switch typ.Kind() { + case types.Bool: + return "BOOL" + case types.Int: + return "int" + case types.Int8: + return "int8_t" + case types.Int16: + return "int16_t" + case types.Int32: + return "int32_t" + case types.Int64: + return "int64_t" + case types.Uint8: + // byte is an alias of uint8, and the alias is lost. + return "byte" + case types.Uint16: + return "uint16_t" + case types.Uint32: + return "uint32_t" + case types.Uint64: + return "uint64_t" + case types.Float32: + return "float" + case types.Float64: + return "double" + case types.String: + return "NSString*" + default: + g.errorf("unsupported type: %s", typ) + return "TODO" + } + case *types.Slice: + elem := g.objcType(typ.Elem()) + // Special case: NSData seems to be a better option for byte slice. + if elem == "byte" { + return "NSData*" + } + // TODO(hyangah): support other slice types: NSArray or CFArrayRef. + // Investigate the performance implication. + g.errorf("unsupported type: %s", typ) + return "TODO" + case *types.Pointer: + if _, ok := typ.Elem().(*types.Named); ok { + return g.objcType(typ.Elem()) + "*" + } + g.errorf("unsupported pointer to type: %s", typ) + return "TODO" + case *types.Named: + n := typ.Obj() + if n.Pkg() != g.pkg { + g.errorf("type %s is in package %s; only types defined in package %s is supported", n.Name(), n.Pkg().Name(), g.pkg.Name()) + return "TODO" + } + switch typ.Underlying().(type) { + case *types.Interface: + return "id<" + g.namePrefix + n.Name() + ">" + case *types.Struct: + return g.namePrefix + n.Name() + } + g.errorf("unsupported, named type %s", typ) + return "TODO" + default: + g.errorf("unsupported type: %#+v, %s", typ, typ) + return "TODO" + } +} diff --git a/src/golang.org/x/mobile/bind/java/Seq.java b/src/golang.org/x/mobile/bind/java/Seq.java new file mode 100644 index 0000000000..e9550c8c0f --- /dev/null +++ b/src/golang.org/x/mobile/bind/java/Seq.java @@ -0,0 +1,305 @@ +// Copyright 2014 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. + +package go; + +import android.app.Application; +import android.content.Context; +import android.util.Log; +import android.util.SparseArray; +import android.util.SparseIntArray; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +// Seq is a sequence of machine-dependent encoded values. +// Used by automatically generated language bindings to talk to Go. +public class Seq { + static { + // Look for the shim class auto-generated by gomobile bind. + // Its only purpose is to call System.loadLibrary. + try { + Class.forName("go.LoadJNI"); + } catch (ClassNotFoundException e) { + // Ignore, assume the user will load JNI for it. + Log.w("GoSeq", "LoadJNI class not found"); + } + + try { + // TODO(hyangah): check proguard rule. + Application appl = (Application)Class.forName("android.app.AppGlobals").getMethod("getInitialApplication").invoke(null); + Context ctx = appl.getApplicationContext(); + setContext(ctx); + + } catch (Exception e) { + Log.w("GoSeq", "Global context not found:" + e); + } + + initSeq(); + new Thread("GoSeq") { + public void run() { Seq.receive(); } + }.start(); + } + + @SuppressWarnings("UnusedDeclaration") + private long memptr; // holds C-allocated pointer + + public Seq() { + ensure(64); + } + + static native void setContext(Context ctx); + + // Ensure that at least size bytes can be written to the Seq. + // Any existing data in the buffer is preserved. + public native void ensure(int size); + + // Moves the internal buffer offset back to zero. + // Length and contents are maintained. Data can be read after a reset. + public native void resetOffset(); + + public native void log(String label); + + public native boolean readBool(); + public native byte readInt8(); + public native short readInt16(); + public native int readInt32(); + public native long readInt64(); + public long readInt() { return readInt64(); } + + public native float readFloat32(); + public native double readFloat64(); + public native String readUTF16(); + public String readString() { return readUTF16(); } + public native byte[] readByteArray(); + + public native void writeBool(boolean v); + public native void writeInt8(byte v); + public native void writeInt16(short v); + public native void writeInt32(int v); + public native void writeInt64(long v); + public void writeInt(long v) { writeInt64(v); } + + public native void writeFloat32(float v); + public native void writeFloat64(double v); + public native void writeUTF16(String v); + public void writeString(String v) { writeUTF16(v); } + public native void writeByteArray(byte[] v); + + public void writeRef(Ref ref) { + tracker.inc(ref); + writeInt32(ref.refnum); + } + + public Ref readRef() { + int refnum = readInt32(); + return tracker.get(refnum); + } + + static native void initSeq(); + + // Informs the Go ref tracker that Java is done with this ref. + static native void destroyRef(int refnum); + + // createRef creates a Ref to a Java object. + public static Ref createRef(Seq.Object o) { + return tracker.createRef(o); + } + + // sends a function invocation request to Go. + // + // Blocks until the function completes. + // If the request is for a method, the first element in src is + // a Ref to the receiver. + public static native void send(String descriptor, int code, Seq src, Seq dst); + + // recv returns the next request from Go for a Java call. + static native void recv(Seq in, Receive params); + + // recvRes sends the result of a Java call back to Go. + static native void recvRes(int handle, Seq out); + + static final class Receive { + int refnum; + int code; + int handle; + } + + protected void finalize() throws Throwable { + super.finalize(); + free(); + } + private native void free(); + + private static final ExecutorService receivePool = Executors.newCachedThreadPool(); + + // receive listens for callback requests from Go, invokes them on a thread + // pool and sends the responses. + public static void receive() { + Seq.Receive params = new Seq.Receive(); + while (true) { + final Seq in = new Seq(); + Seq.recv(in, params); + + final int code = params.code; + final int handle = params.handle; + final int refnum = params.refnum; + + if (code == -1) { + // Special signal from seq.FinalizeRef. + tracker.dec(refnum); + Seq out = new Seq(); + Seq.recvRes(handle, out); + continue; + } + + receivePool.execute(new Runnable() { + public void run() { + Ref r = tracker.get(refnum); + Seq out = new Seq(); + r.obj.call(code, in, out); + Seq.recvRes(handle, out); + } + }); + } + } + + // An Object is a Java object that matches a Go object. + // The implementation of the object may be in either Java or Go, + // with a proxy instance in the other language passing calls + // through to the other language. + // + // Don't implement an Object directly. Instead, look for the + // generated abstract Stub. + public interface Object { + public Ref ref(); + public void call(int code, Seq in, Seq out); + } + + // A Ref is an object tagged with an integer for passing back and + // forth across the language boundary. + // + // A Ref may represent either an instance of a Java Object subclass, + // or an instance of a Go object. The explicit allocation of a Ref + // is used to pin Go object instances when they are passed to Java. + // The Go Seq library maintains a reference to the instance in a map + // keyed by the Ref number. When the JVM calls finalize, we ask Go + // to clear the entry in the map. + public static final class Ref { + // refnum < 0: Go object tracked by Java + // refnum > 0: Java object tracked by Go + int refnum; + + int refcnt; // for Java obj: track how many times sent to Go. + + public Seq.Object obj; // for Java obj: pointers to the Java obj. + + private Ref(int refnum, Seq.Object o) { + this.refnum = refnum; + this.refcnt = 0; + this.obj = o; + } + + @Override + protected void finalize() throws Throwable { + if (refnum < 0) { + // Go object: signal Go to decrement the reference count. + Seq.destroyRef(refnum); + } + super.finalize(); + } + } + + static final RefTracker tracker = new RefTracker(); + + static final class RefTracker { + // Next Java object reference number. + // + // Reference numbers are positive for Java objects, + // and start, arbitrarily at a different offset to Go + // to make debugging by reading Seq hex a little easier. + private int next = 42; // next Java object ref + + // Java objects that have been passed to Go. refnum -> Ref + // The Ref obj field is non-null. + // This map pins Java objects so they don't get GCed while the + // only reference to them is held by Go code. + private SparseArray javaObjs = new SparseArray(); + + // inc increments the reference count of a Java object when it + // is sent to Go. + synchronized void inc(Ref ref) { + int refnum = ref.refnum; + if (refnum <= 0) { + // We don't keep track of the Go object. + return; + } + // Count Java objects passed to Go. + if (ref.refcnt == Integer.MAX_VALUE) { + throw new RuntimeException("refnum " + refnum + " overflow"); + } + ref.refcnt++; + Ref obj = javaObjs.get(refnum); + if (obj == null) { + javaObjs.put(refnum, ref); + } + } + + // dec decrements the reference count of a Java object when + // Go signals a corresponding proxy object is finalized. + // If the count reaches zero, the Java object is removed + // from the javaObjs map. + synchronized void dec(int refnum) { + if (refnum <= 0) { + // We don't keep track of the Go object. + // This must not happen. + Log.wtf("GoSeq", "dec request for Go object "+ refnum); + return; + } + // Java objects are removed on request of Go. + Ref obj = javaObjs.get(refnum); + if (obj == null) { + throw new RuntimeException("referenced Java object is not found: refnum="+refnum); + } + obj.refcnt--; + if (obj.refcnt <= 0) { + javaObjs.remove(refnum); + } + } + + synchronized Ref createRef(Seq.Object o) { + // TODO(crawshaw): use single Ref for null. + if (next == Integer.MAX_VALUE) { + throw new RuntimeException("createRef overflow for " + o); + } + int refnum = next++; + Ref ref = new Ref(refnum, o); + javaObjs.put(refnum, ref); + return ref; + } + + // get returns an existing Ref to either a Java or Go object. + // It may be the first time we have seen the Go object. + // + // TODO(crawshaw): We could cut down allocations for frequently + // sent Go objects by maintaining a map to weak references. This + // however, would require allocating two objects per reference + // instead of one. It also introduces weak references, the bane + // of any Java debugging session. + // + // When we have real code, examine the tradeoffs. + synchronized Ref get(int refnum) { + if (refnum > 0) { + Ref ref = javaObjs.get(refnum); + if (ref == null) { + throw new RuntimeException("unknown java Ref: "+refnum); + } + return ref; + } else { + // Go object. + return new Ref(refnum, null); + } + } + } +} diff --git a/src/golang.org/x/mobile/bind/java/SeqTest.java b/src/golang.org/x/mobile/bind/java/SeqTest.java new file mode 100644 index 0000000000..786a03794f --- /dev/null +++ b/src/golang.org/x/mobile/bind/java/SeqTest.java @@ -0,0 +1,289 @@ +// Copyright 2014 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. + +package go; + +import android.util.Log; +import android.test.suitebuilder.annotation.Suppress; +import android.test.AndroidTestCase; +import android.test.MoreAsserts; +import java.util.Arrays; +import java.util.Random; + +import go.testpkg.Testpkg; + +public class SeqTest extends AndroidTestCase { + public SeqTest() { + } + + public void testAssets() { + String want = "Hello, Assets.\n"; + String got = Testpkg.ReadAsset(); + assertEquals("Asset read", want, got); + } + + public void testAdd() { + long res = Testpkg.Add(3, 4); + assertEquals("Unexpected arithmetic failure", 7, res); + } + + public void testBool() { + assertTrue(Testpkg.Negate(false)); + assertFalse(Testpkg.Negate(true)); + } + + public void testShortString() { + String want = "a short string"; + String got = Testpkg.StrDup(want); + assertEquals("Strings should match", want, got); + } + + public void testLongString() { + StringBuilder b = new StringBuilder(); + for (int i = 0; i < 128*1024; i++) { + b.append("0123456789"); + } + String want = b.toString(); + String got = Testpkg.StrDup(want); + assertEquals("Strings should match", want, got); + } + + public void testUnicode() { + String want = "Hello, 世界"; + String got = Testpkg.StrDup(want); + assertEquals("Strings should match", want, got); + } + + public void testNilErr() throws Exception { + Testpkg.Err(null); // returns nil, no exception + } + + public void testErr() { + String msg = "Go errors are dropped into the confusing space of exceptions"; + try { + Testpkg.Err(msg); + fail("expected non-nil error to be turned into an exception"); + } catch (Exception e) { + assertEquals("messages should match", msg, e.getMessage()); + } + } + + public void testByteArray() { + for (int i = 0; i < 2048; i++) { + if (i == 0) { + byte[] got = Testpkg.BytesAppend(null, null); + assertEquals("Bytes(null+null) should match", (byte[])null, got); + got = Testpkg.BytesAppend(new byte[0], new byte[0]); + assertEquals("Bytes(empty+empty) should match", (byte[])null, got); + continue; + } + + byte[] want = new byte[i]; + new Random().nextBytes(want); + + byte[] s1 = null; + byte[] s2 = null; + if (i > 0) { + s1 = Arrays.copyOfRange(want, 0, 1); + } + if (i > 1) { + s2 = Arrays.copyOfRange(want, 1, i); + } + byte[] got = Testpkg.BytesAppend(s1, s2); + MoreAsserts.assertEquals("Bytes(len="+i+") should match", want, got); + } + } + + // Test for golang.org/issue/9486. + public void testByteArrayAfterString() { + byte[] bytes = new byte[1024]; + for (int i=0; i < bytes.length; i++) { + bytes[i] = 8; + } + + String stuff = "stuff"; + byte[] got = Testpkg.AppendToString(stuff, bytes); + + try { + byte[] s = stuff.getBytes("UTF-8"); + byte[] want = new byte[s.length + bytes.length]; + System.arraycopy(s, 0, want, 0, s.length); + System.arraycopy(bytes, 0, want, s.length, bytes.length); + MoreAsserts.assertEquals("Bytes should match", want, got); + } catch (Exception e) { + fail("Cannot perform the test: " + e.toString()); + } + } + + public void testGoRefGC() { + Testpkg.S s = Testpkg.New(); + runGC(); + long collected = Testpkg.NumSCollected(); + assertEquals("Only S should be pinned", 0, collected); + + s = null; + runGC(); + collected = Testpkg.NumSCollected(); + assertEquals("S should be collected", 1, collected); + } + + private class AnI extends Testpkg.I.Stub { + public void E() throws Exception { + throw new Exception("my exception from E"); + } + + boolean calledF; + public void F() { + calledF = true; + } + + public Testpkg.I I() { + return this; + } + + public Testpkg.S S() { + return Testpkg.New(); + } + + public String StoString(Testpkg.S s) { + return s.String(); + } + + public long V() { + return 1234; + } + + public long VE() throws Exception { + throw new Exception("my exception from VE"); + } + + public String name; + + public String String() { + return name; + } + + } + + // TODO(hyangah): add tests for methods that take parameters. + + public void testInterfaceMethodReturnsError() { + final AnI obj = new AnI(); + try { + Testpkg.CallE(obj); + fail("Expecting exception but none was thrown."); + } catch (Exception e) { + assertEquals("Error messages should match", "my exception from E", e.getMessage()); + } + } + + public void testInterfaceMethodVoid() { + final AnI obj = new AnI(); + Testpkg.CallF(obj); + assertTrue("Want AnI.F to be called", obj.calledF); + } + + public void testInterfaceMethodReturnsInterface() { + AnI obj = new AnI(); + obj.name = "testing AnI.I"; + Testpkg.I i = Testpkg.CallI(obj); + assertEquals("Want AnI.I to return itself", i.String(), obj.String()); + + runGC(); + + i = Testpkg.CallI(obj); + assertEquals("Want AnI.I to return itself", i.String(), obj.String()); + } + + public void testInterfaceMethodReturnsStructPointer() { + final AnI obj = new AnI(); + for (int i = 0; i < 5; i++) { + Testpkg.S s = Testpkg.CallS(obj); + runGC(); + } + } + + public void testInterfaceMethodTakesStructPointer() { + final AnI obj = new AnI(); + Testpkg.S s = Testpkg.CallS(obj); + String got = obj.StoString(s); + String want = s.String(); + assertEquals("Want AnI.StoString(s) to call s's String", want, got); + } + + public void testInterfaceMethodReturnsInt() { + final AnI obj = new AnI(); + assertEquals("Values must match", 1234, Testpkg.CallV(obj)); + } + + public void testInterfaceMethodReturnsIntOrError() { + final AnI obj = new AnI(); + try { + long v = Testpkg.CallVE(obj); + fail("Expecting exception but none was thrown and got value " + v); + } catch (Exception e) { + assertEquals("Error messages should match", "my exception from VE", e.getMessage()); + } + } + + boolean finalizedAnI; + + private class AnI_Traced extends AnI { + @Override + public void finalize() throws Throwable { + finalizedAnI = true; + super.finalize(); + } + } + + public void testJavaRefGC() { + finalizedAnI = false; + AnI obj = new AnI_Traced(); + Testpkg.CallF(obj); + assertTrue("want F to be called", obj.calledF); + Testpkg.CallF(obj); + obj = null; + runGC(); + assertTrue("want obj to be collected", finalizedAnI); + } + + public void testJavaRefKeep() { + finalizedAnI = false; + AnI obj = new AnI_Traced(); + Testpkg.CallF(obj); + Testpkg.CallF(obj); + obj = null; + runGC(); + assertTrue("want obj not to be kept by Go", finalizedAnI); + + finalizedAnI = false; + obj = new AnI_Traced(); + Testpkg.Keep(obj); + obj = null; + runGC(); + assertFalse("want obj to be kept live by Go", finalizedAnI); + } + + private void runGC() { + System.gc(); + System.runFinalization(); + Testpkg.GC(); + System.gc(); + System.runFinalization(); + } + + public void testUnnamedParams() { + final String msg = "1234567"; + assertEquals("want the length of \"1234567\" passed after unnamed params", + 7, Testpkg.UnnamedParams(10, 20, msg)); + } + + public void testPointerToStructAsField() { + Testpkg.Node a = Testpkg.NewNode("A"); + Testpkg.Node b = Testpkg.NewNode("B"); + a.setNext(b); + String got = a.String(); + assertEquals("want Node A points to Node B", "A:B:", got); + } +} diff --git a/src/golang.org/x/mobile/bind/java/doc.go b/src/golang.org/x/mobile/bind/java/doc.go new file mode 100644 index 0000000000..ee85203e89 --- /dev/null +++ b/src/golang.org/x/mobile/bind/java/doc.go @@ -0,0 +1,10 @@ +// Copyright 2015 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. + +// Package java implements the Java language bindings. +// +// See the design document (http://golang.org/s/gobind). +// +// Currently, this works only for android. +package java diff --git a/src/golang.org/x/mobile/bind/java/seq_android.c b/src/golang.org/x/mobile/bind/java/seq_android.c new file mode 100644 index 0000000000..dfb2988a28 --- /dev/null +++ b/src/golang.org/x/mobile/bind/java/seq_android.c @@ -0,0 +1,451 @@ +// Copyright 2014 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. + +#include +#include +#include +#include +#include +#include +#include "seq_android.h" +#include "_cgo_export.h" + +#define LOG_INFO(...) __android_log_print(ANDROID_LOG_INFO, "go/Seq", __VA_ARGS__) +#define LOG_FATAL(...) __android_log_print(ANDROID_LOG_FATAL, "go/Seq", __VA_ARGS__) + +static jfieldID memptr_id; +static jfieldID receive_refnum_id; +static jfieldID receive_code_id; +static jfieldID receive_handle_id; + +static jclass jbytearray_clazz; + +// pinned represents a pinned array to be released at the end of Send call. +typedef struct pinned { + jobject ref; + void* ptr; + struct pinned* next; +} pinned; + +// mem is a simple C equivalent of seq.Buffer. +// +// Many of the allocations around mem could be avoided to improve +// function call performance, but the goal is to start simple. +typedef struct mem { + uint8_t *buf; + uint32_t off; + uint32_t len; + uint32_t cap; + + // TODO(hyangah): have it as a separate field outside mem? + pinned* pinned; +} mem; + +// mem_ensure ensures that m has at least size bytes free. +// If m is NULL, it is created. +static mem *mem_ensure(mem *m, uint32_t size) { + if (m == NULL) { + m = (mem*)malloc(sizeof(mem)); + if (m == NULL) { + LOG_FATAL("mem_ensure malloc failed"); + } + m->cap = 0; + m->off = 0; + m->len = 0; + m->buf = NULL; + m->pinned = NULL; + } + uint32_t cap = m->cap; + if (m->cap > m->off+size) { + return m; + } + if (cap == 0) { + cap = 64; + } + // TODO(hyangah): consider less aggressive allocation such as + // cap += max(pow2round(size), 64) + while (cap < m->off+size) { + cap *= 2; + } + m->buf = (uint8_t*)realloc((void*)m->buf, cap); + if (m->buf == NULL) { + LOG_FATAL("mem_ensure realloc failed, off=%d, size=%d", m->off, size); + } + m->cap = cap; + return m; +} + +static mem *mem_get(JNIEnv *env, jobject obj) { + // Storage space for pointer is always 64-bits, even on 32-bit + // machines. Cast to uintptr_t to avoid -Wint-to-pointer-cast. + return (mem*)(uintptr_t)(*env)->GetLongField(env, obj, memptr_id); +} + +static uint32_t align(uint32_t offset, uint32_t alignment) { + uint32_t pad = offset % alignment; + if (pad > 0) { + pad = alignment-pad; + } + return pad+offset; +} + +static uint8_t *mem_read(JNIEnv *env, jobject obj, uint32_t size, uint32_t alignment) { + if (size == 0) { + return NULL; + } + mem *m = mem_get(env, obj); + if (m == NULL) { + LOG_FATAL("mem_read on NULL mem"); + } + uint32_t offset = align(m->off, alignment); + + if (m->len-offset < size) { + LOG_FATAL("short read"); + } + uint8_t *res = m->buf+offset; + m->off = offset+size; + return res; +} + +uint8_t *mem_write(JNIEnv *env, jobject obj, uint32_t size, uint32_t alignment) { + mem *m = mem_get(env, obj); + if (m == NULL) { + LOG_FATAL("mem_write on NULL mem"); + } + if (m->off != m->len) { + LOG_FATAL("write can only append to seq, size: (off=%d, len=%d, size=%d", m->off, m->len, size); + } + uint32_t offset = align(m->off, alignment); + m = mem_ensure(m, offset - m->off + size); + uint8_t *res = m->buf+offset; + m->off = offset+size; + m->len = offset+size; + return res; +} + +static void *pin_array(JNIEnv *env, jobject obj, jobject arr) { + mem *m = mem_get(env, obj); + if (m == NULL) { + m = mem_ensure(m, 64); + } + pinned *p = (pinned*) malloc(sizeof(pinned)); + if (p == NULL) { + LOG_FATAL("pin_array malloc failed"); + } + p->ref = (*env)->NewGlobalRef(env, arr); + + if ((*env)->IsInstanceOf(env, p->ref, jbytearray_clazz)) { + p->ptr = (*env)->GetByteArrayElements(env, p->ref, NULL); + } else { + LOG_FATAL("unsupported array type"); + } + + p->next = m->pinned; + m->pinned = p; + return p->ptr; +} + +static void unpin_arrays(JNIEnv *env, mem *m) { + pinned* p = m->pinned; + while (p != NULL) { + if ((*env)->IsInstanceOf(env, p->ref, jbytearray_clazz)) { + (*env)->ReleaseByteArrayElements(env, p->ref, (jbyte*)p->ptr, JNI_ABORT); + } else { + LOG_FATAL("invalid array type"); + } + + (*env)->DeleteGlobalRef(env, p->ref); + + pinned* o = p; + p = p->next; + free(o); + } + m->pinned = NULL; +} + +static void describe_exception(JNIEnv* env) { + jthrowable exc = (*env)->ExceptionOccurred(env); + if (exc) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + } +} + +static jfieldID find_field(JNIEnv *env, const char *class_name, const char *field_name, const char *field_type) { + jclass clazz = (*env)->FindClass(env, class_name); + if (clazz == NULL) { + describe_exception(env); + LOG_FATAL("cannot find %s", class_name); + return NULL; + } + jfieldID id = (*env)->GetFieldID(env, clazz, field_name , field_type); + if(id == NULL) { + describe_exception(env); + LOG_FATAL("no %s/%s field", field_name, field_type); + return NULL; + } + return id; +} + +static jclass find_class(JNIEnv *env, const char *class_name) { + jclass clazz = (*env)->FindClass(env, class_name); + if (clazz == NULL) { + describe_exception(env); + LOG_FATAL("cannot find %s", class_name); + return NULL; + } + return (*env)->NewGlobalRef(env, clazz); +} + +JNIEXPORT void JNICALL +Java_go_Seq_initSeq(JNIEnv *env, jclass clazz) { + memptr_id = find_field(env, "go/Seq", "memptr", "J"); + receive_refnum_id = find_field(env, "go/Seq$Receive", "refnum", "I"); + receive_handle_id = find_field(env, "go/Seq$Receive", "handle", "I"); + receive_code_id = find_field(env, "go/Seq$Receive", "code", "I"); + + jclass bclazz = find_class(env, "[B"); + jbytearray_clazz = (*env)->NewGlobalRef(env, bclazz); +} + +JNIEXPORT void JNICALL +Java_go_Seq_ensure(JNIEnv *env, jobject obj, jint size) { + mem *m = mem_get(env, obj); + if (m == NULL || m->off+size > m->cap) { + m = mem_ensure(m, size); + (*env)->SetLongField(env, obj, memptr_id, (jlong)(uintptr_t)m); + } +} + +JNIEXPORT void JNICALL +Java_go_Seq_free(JNIEnv *env, jobject obj) { + mem *m = mem_get(env, obj); + if (m != NULL) { + unpin_arrays(env, m); + free((void*)m->buf); + free((void*)m); + } +} + +#define MEM_READ(obj, ty) ((ty*)mem_read(env, obj, sizeof(ty), sizeof(ty))) + +JNIEXPORT jboolean JNICALL +Java_go_Seq_readBool(JNIEnv *env, jobject obj) { + int8_t *v = MEM_READ(obj, int8_t); + if (v == NULL) { + return 0; + } + return *v != 0 ? 1 : 0; +} + +JNIEXPORT jbyte JNICALL +Java_go_Seq_readInt8(JNIEnv *env, jobject obj) { + uint8_t *v = MEM_READ(obj, uint8_t); + if (v == NULL) { + return 0; + } + return *v; +} + +JNIEXPORT jshort JNICALL +Java_go_Seq_readInt16(JNIEnv *env, jobject obj) { + int16_t *v = MEM_READ(obj, int16_t); + return v == NULL ? 0 : *v; +} + +JNIEXPORT jint JNICALL +Java_go_Seq_readInt32(JNIEnv *env, jobject obj) { + int32_t *v = MEM_READ(obj, int32_t); + return v == NULL ? 0 : *v; +} + +JNIEXPORT jlong JNICALL +Java_go_Seq_readInt64(JNIEnv *env, jobject obj) { + int64_t *v = MEM_READ(obj, int64_t); + return v == NULL ? 0 : *v; +} + +JNIEXPORT jfloat JNICALL +Java_go_Seq_readFloat32(JNIEnv *env, jobject obj) { + float *v = MEM_READ(obj, float); + return v == NULL ? 0 : *v; +} + +JNIEXPORT jdouble JNICALL +Java_go_Seq_readFloat64(JNIEnv *env, jobject obj) { + double *v = MEM_READ(obj, double); + return v == NULL ? 0 : *v; +} + +JNIEXPORT jstring JNICALL +Java_go_Seq_readUTF16(JNIEnv *env, jobject obj) { + int32_t size = *MEM_READ(obj, int32_t); + if (size == 0) { + return NULL; + } + return (*env)->NewString(env, (jchar*)mem_read(env, obj, 2*size, 1), size); +} + +JNIEXPORT jbyteArray JNICALL +Java_go_Seq_readByteArray(JNIEnv *env, jobject obj) { + // Send the (array length, pointer) pair encoded as two int64. + // The pointer value is omitted if array length is 0. + jlong size = Java_go_Seq_readInt64(env, obj); + if (size == 0) { + return NULL; + } + jbyteArray res = (*env)->NewByteArray(env, size); + jlong ptr = Java_go_Seq_readInt64(env, obj); + (*env)->SetByteArrayRegion(env, res, 0, size, (jbyte*)(intptr_t)(ptr)); + return res; +} + +#define MEM_WRITE(ty) (*(ty*)mem_write(env, obj, sizeof(ty), sizeof(ty))) + +JNIEXPORT void JNICALL +Java_go_Seq_writeBool(JNIEnv *env, jobject obj, jboolean v) { + MEM_WRITE(int8_t) = v ? 1 : 0; +} + +JNIEXPORT void JNICALL +Java_go_Seq_writeInt8(JNIEnv *env, jobject obj, jbyte v) { + MEM_WRITE(int8_t) = v; +} + +JNIEXPORT void JNICALL +Java_go_Seq_writeInt16(JNIEnv *env, jobject obj, jshort v) { + MEM_WRITE(int16_t) = v; +} + +JNIEXPORT void JNICALL +Java_go_Seq_writeInt32(JNIEnv *env, jobject obj, jint v) { + MEM_WRITE(int32_t) = v; +} + +JNIEXPORT void JNICALL +Java_go_Seq_writeInt64(JNIEnv *env, jobject obj, jlong v) { + MEM_WRITE(int64_t) = v; +} + +JNIEXPORT void JNICALL +Java_go_Seq_writeFloat32(JNIEnv *env, jobject obj, jfloat v) { + MEM_WRITE(float) = v; +} + +JNIEXPORT void JNICALL +Java_go_Seq_writeFloat64(JNIEnv *env, jobject obj, jdouble v) { + MEM_WRITE(double) = v; +} + +JNIEXPORT void JNICALL +Java_go_Seq_writeUTF16(JNIEnv *env, jobject obj, jstring v) { + if (v == NULL) { + MEM_WRITE(int32_t) = 0; + return; + } + int32_t size = (*env)->GetStringLength(env, v); + MEM_WRITE(int32_t) = size; + (*env)->GetStringRegion(env, v, 0, size, (jchar*)mem_write(env, obj, 2*size, 1)); +} + +JNIEXPORT void JNICALL +Java_go_Seq_writeByteArray(JNIEnv *env, jobject obj, jbyteArray v) { + // For Byte array, we pass only the (array length, pointer) pair + // encoded as two int64 values. If the array length is 0, + // the pointer value is omitted. + if (v == NULL) { + MEM_WRITE(int64_t) = 0; + return; + } + + jsize len = (*env)->GetArrayLength(env, v); + MEM_WRITE(int64_t) = len; + if (len == 0) { + return; + } + + jbyte* b = pin_array(env, obj, v); + MEM_WRITE(int64_t) = (jlong)(uintptr_t)b; +} + +JNIEXPORT void JNICALL +Java_go_Seq_resetOffset(JNIEnv *env, jobject obj) { + mem *m = mem_get(env, obj); + if (m == NULL) { + LOG_FATAL("resetOffset on NULL mem"); + } + m->off = 0; +} + +JNIEXPORT void JNICALL +Java_go_Seq_log(JNIEnv *env, jobject obj, jstring v) { + mem *m = mem_get(env, obj); + const char *label = (*env)->GetStringUTFChars(env, v, NULL); + if (label == NULL) { + LOG_FATAL("log GetStringUTFChars failed"); + } + if (m == NULL) { + LOG_INFO("%s: mem=NULL", label); + } else { + LOG_INFO("%s: mem{off=%d, len=%d, cap=%d}", label, m->off, m->len, m->cap); + } + (*env)->ReleaseStringUTFChars(env, v, label); +} + +JNIEXPORT void JNICALL +Java_go_Seq_destroyRef(JNIEnv *env, jclass clazz, jint refnum) { + DestroyRef(refnum); +} + +JNIEXPORT void JNICALL +Java_go_Seq_send(JNIEnv *env, jclass clazz, jstring descriptor, jint code, jobject src_obj, jobject dst_obj) { + mem *src = mem_get(env, src_obj); + if (src == NULL) { + LOG_FATAL("send src is NULL"); + } + mem *dst = mem_get(env, dst_obj); + if (dst == NULL) { + LOG_FATAL("send dst is NULL"); + } + + GoString desc; + desc.p = (char*)(*env)->GetStringUTFChars(env, descriptor, NULL); + if (desc.p == NULL) { + LOG_FATAL("send GetStringUTFChars failed"); + } + desc.n = (*env)->GetStringUTFLength(env, descriptor); + Send(desc, (GoInt)code, src->buf, src->len, &dst->buf, &dst->len); + (*env)->ReleaseStringUTFChars(env, descriptor, desc.p); + unpin_arrays(env, src); // assume 'src' is no longer needed. +} + +JNIEXPORT void JNICALL +Java_go_Seq_recv(JNIEnv *env, jclass clazz, jobject in_obj, jobject receive) { + mem *in = mem_get(env, in_obj); + if (in == NULL) { + LOG_FATAL("recv in is NULL"); + } + struct Recv_return ret = Recv(&in->buf, &in->len); + (*env)->SetIntField(env, receive, receive_refnum_id, ret.r0); + (*env)->SetIntField(env, receive, receive_code_id, ret.r1); + (*env)->SetIntField(env, receive, receive_handle_id, ret.r2); +} + +JNIEXPORT void JNICALL +Java_go_Seq_recvRes(JNIEnv *env, jclass clazz, jint handle, jobject out_obj) { + mem *out = mem_get(env, out_obj); + if (out == NULL) { + LOG_FATAL("recvRes out is NULL"); + } + RecvRes((int32_t)handle, out->buf, out->len); +} + +JNIEXPORT void JNICALL +Java_go_Seq_setContext(JNIEnv* env, jclass clazz, jobject ctx) { + JavaVM* vm; + if ((*env)->GetJavaVM(env, &vm) != 0) { + LOG_FATAL("failed to get JavaVM"); + } + setContext(vm, (*env)->NewGlobalRef(env, ctx)); +} diff --git a/src/golang.org/x/mobile/bind/java/seq_android.go b/src/golang.org/x/mobile/bind/java/seq_android.go new file mode 100644 index 0000000000..a3c4a8c509 --- /dev/null +++ b/src/golang.org/x/mobile/bind/java/seq_android.go @@ -0,0 +1,187 @@ +// Copyright 2014 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. + +package java // import "golang.org/x/mobile/bind/java" + +//#cgo LDFLAGS: -llog +//#include +//#include +//#include +//#include +//#include "seq_android.h" +import "C" +import ( + "fmt" + "sync" + "unsafe" + + "golang.org/x/mobile/bind/seq" + "golang.org/x/mobile/internal/mobileinit" +) + +const maxSliceLen = 1<<31 - 1 + +const debug = false + +// Send is called by Java to send a request to run a Go function. +//export Send +func Send(descriptor string, code int, req *C.uint8_t, reqlen C.size_t, res **C.uint8_t, reslen *C.size_t) { + fn := seq.Registry[descriptor][code] + if fn == nil { + panic(fmt.Sprintf("invalid descriptor(%s) and code(0x%x)", descriptor, code)) + } + in := new(seq.Buffer) + if reqlen > 0 { + in.Data = (*[maxSliceLen]byte)(unsafe.Pointer(req))[:reqlen] + } + out := new(seq.Buffer) + fn(out, in) + // BUG(hyangah): the function returning a go byte slice (so fn writes a pointer into 'out') is unsafe. + // After fn is complete here, Go runtime is free to collect or move the pointed byte slice + // contents. (Explicitly calling runtime.GC here will surface the problem?) + // Without pinning support from Go side, it will be hard to fix it without extra copying. + + seqToBuf(res, reslen, out) +} + +// DestroyRef is called by Java to inform Go it is done with a reference. +//export DestroyRef +func DestroyRef(refnum C.int32_t) { + seq.Delete(int32(refnum)) +} + +type request struct { + ref *seq.Ref + handle int32 + code int + in *seq.Buffer +} + +var recv struct { + sync.Mutex + cond sync.Cond // signals req is not empty + req []request + next int32 // next handle value +} + +var res struct { + sync.Mutex + cond sync.Cond // signals a response is filled in + out map[int32]*seq.Buffer // handle -> output +} + +func init() { + recv.cond.L = &recv.Mutex + recv.next = 411 // arbitrary starting point distinct from Go and Java obj ref nums + + res.cond.L = &res.Mutex + res.out = make(map[int32]*seq.Buffer) +} + +func seqToBuf(bufptr **C.uint8_t, lenptr *C.size_t, buf *seq.Buffer) { + if debug { + fmt.Printf("seqToBuf tag 1, len(buf.Data)=%d, *lenptr=%d\n", len(buf.Data), *lenptr) + } + if len(buf.Data) == 0 { + *lenptr = 0 + return + } + if len(buf.Data) > int(*lenptr) { + // TODO(crawshaw): realloc + C.free(unsafe.Pointer(*bufptr)) + m := C.malloc(C.size_t(len(buf.Data))) + if uintptr(m) == 0 { + panic(fmt.Sprintf("malloc failed, size=%d", len(buf.Data))) + } + *bufptr = (*C.uint8_t)(m) + *lenptr = C.size_t(len(buf.Data)) + } + C.memcpy(unsafe.Pointer(*bufptr), unsafe.Pointer(&buf.Data[0]), C.size_t(len(buf.Data))) +} + +// Recv is called by Java in a loop and blocks until Go requests a callback +// be executed by the JVM. Then a request object is returned, along with a +// handle for the host to respond via RecvRes. +//export Recv +func Recv(in **C.uint8_t, inlen *C.size_t) (ref, code, handle C.int32_t) { + recv.Lock() + for len(recv.req) == 0 { + recv.cond.Wait() + } + req := recv.req[0] + recv.req = recv.req[1:] + seqToBuf(in, inlen, req.in) + recv.Unlock() + + return C.int32_t(req.ref.Num), C.int32_t(req.code), C.int32_t(req.handle) +} + +// RecvRes is called by JNI to return the result of a requested callback. +//export RecvRes +func RecvRes(handle C.int32_t, out *C.uint8_t, outlen C.size_t) { + outBuf := &seq.Buffer{ + Data: make([]byte, outlen), + } + copy(outBuf.Data, (*[maxSliceLen]byte)(unsafe.Pointer(out))[:outlen]) + + res.Lock() + res.out[int32(handle)] = outBuf + res.Unlock() + res.cond.Broadcast() +} + +// transact calls a method on a Java object instance. +// It blocks until the call is complete. +func transact(ref *seq.Ref, _ string, code int, in *seq.Buffer) *seq.Buffer { + recv.Lock() + if recv.next == 1<<31-1 { + panic("recv handle overflow") + } + handle := recv.next + recv.next++ + recv.req = append(recv.req, request{ + ref: ref, + code: code, + in: in, + handle: handle, + }) + recv.Unlock() + recv.cond.Signal() + + res.Lock() + for res.out[handle] == nil { + res.cond.Wait() + } + out := res.out[handle] + delete(res.out, handle) + res.Unlock() + + return out +} + +func encodeString(out *seq.Buffer, v string) { + out.WriteUTF16(v) +} + +func decodeString(in *seq.Buffer) string { + return in.ReadUTF16() +} + +func init() { + seq.FinalizeRef = func(ref *seq.Ref) { + if ref.Num < 0 { + panic(fmt.Sprintf("not a Java ref: %d", ref.Num)) + } + transact(ref, "", -1, new(seq.Buffer)) + } + + seq.Transact = transact + seq.EncString = encodeString + seq.DecString = decodeString +} + +//export setContext +func setContext(vm *C.JavaVM, ctx C.jobject) { + mobileinit.SetCurrentContext(unsafe.Pointer(vm), unsafe.Pointer(ctx)) +} diff --git a/src/golang.org/x/mobile/bind/java/seq_android.h b/src/golang.org/x/mobile/bind/java/seq_android.h new file mode 100644 index 0000000000..5ae09c5289 --- /dev/null +++ b/src/golang.org/x/mobile/bind/java/seq_android.h @@ -0,0 +1,5 @@ +// Copyright 2014 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. + +void init_seq(void* vm, void* classfinder); diff --git a/src/golang.org/x/mobile/bind/java/seq_test.go b/src/golang.org/x/mobile/bind/java/seq_test.go new file mode 100644 index 0000000000..66576090e5 --- /dev/null +++ b/src/golang.org/x/mobile/bind/java/seq_test.go @@ -0,0 +1,154 @@ +// Copyright 2015 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. + +package java + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" +) + +// TestJavaSeqTest runs java test SeqTest.java. +// This requires the gradle command in PATH and +// the Android SDK whose path is available through ANDROID_HOME environment variable. +func TestJavaSeqTest(t *testing.T) { + if _, err := run("which gradle"); err != nil { + t.Skip("command gradle not found, skipping") + } + if sdk := os.Getenv("ANDROID_HOME"); sdk == "" { + t.Skip("ANDROID_HOME environment var not set, skipping") + } + if _, err := run("which gomobile"); err != nil { + _, err := run("go install golang.org/x/mobile/cmd/gomobile") + if err != nil { + t.Skip("gomobile not available, skipping") + } + } + + // TODO(hyangah): gomobile init if necessary. + + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("failed pwd: %v", err) + } + tmpdir, err := ioutil.TempDir("", "bind-java-seq-test-") + if err != nil { + t.Fatalf("failed to prepare temp dir: %v", err) + } + defer os.RemoveAll(tmpdir) + t.Logf("tmpdir = %s", tmpdir) + + if err := os.Chdir(tmpdir); err != nil { + t.Fatalf("failed chdir: %v", err) + } + defer os.Chdir(cwd) + + for _, d := range []string{"src/main", "src/androidTest/java/go", "libs"} { + err = os.MkdirAll(filepath.Join(tmpdir, d), 0700) + if err != nil { + t.Fatal(err) + } + } + + buf, err := run("gomobile bind golang.org/x/mobile/bind/java/testpkg") + if err != nil { + t.Logf("%s", buf) + t.Fatalf("failed to run gomobile bind: %v", err) + } + + fname := filepath.Join(tmpdir, "libs", "testpkg.aar") + err = cp(fname, filepath.Join(tmpdir, "testpkg.aar")) + if err != nil { + t.Fatalf("failed to copy testpkg.aar: %v", err) + } + + fname = filepath.Join(tmpdir, "src/androidTest/java/go/SeqTest.java") + err = cp(fname, filepath.Join(cwd, "SeqTest.java")) + if err != nil { + t.Fatalf("failed to copy SeqTest.java: %v", err) + } + + fname = filepath.Join(tmpdir, "src/main/AndroidManifest.xml") + err = ioutil.WriteFile(fname, []byte(androidmanifest), 0700) + if err != nil { + t.Fatalf("failed to write android manifest file: %v", err) + } + + fname = filepath.Join(tmpdir, "build.gradle") + err = ioutil.WriteFile(fname, []byte(buildgradle), 0700) + if err != nil { + t.Fatalf("failed to write build.gradle file: %v", err) + } + + if buf, err := run("gradle connectedAndroidTest"); err != nil { + t.Logf("%s", buf) + t.Errorf("failed to run gradle test: %v", err) + } +} + +func run(cmd string) ([]byte, error) { + c := strings.Split(cmd, " ") + return exec.Command(c[0], c[1:]...).CombinedOutput() +} + +func cp(dst, src string) error { + r, err := os.Open(src) + if err != nil { + return fmt.Errorf("failed to read source: %v", err) + } + defer r.Close() + w, err := os.Create(dst) + if err != nil { + return fmt.Errorf("failed to open destination: $v", err) + } + _, err = io.Copy(w, r) + cerr := w.Close() + if err != nil { + return err + } + return cerr +} + +const androidmanifest = ` + +` + +const buildgradle = `buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.1.3' + } +} + +allprojects { + repositories { jcenter() } +} + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 'android-19' + buildToolsVersion '21.1.2' + defaultConfig { minSdkVersion 14 } +} + +repositories { + flatDir { dirs 'libs' } +} +dependencies { + compile 'com.android.support:appcompat-v7:19.0.0' + compile(name: "testpkg", ext: "aar") +} +` diff --git a/src/golang.org/x/mobile/bind/java/testpkg/assets/hello.txt b/src/golang.org/x/mobile/bind/java/testpkg/assets/hello.txt new file mode 100644 index 0000000000..0c44083080 --- /dev/null +++ b/src/golang.org/x/mobile/bind/java/testpkg/assets/hello.txt @@ -0,0 +1 @@ +Hello, Assets. diff --git a/src/golang.org/x/mobile/bind/java/testpkg/testpkg.go b/src/golang.org/x/mobile/bind/java/testpkg/testpkg.go new file mode 100644 index 0000000000..fd795e4b46 --- /dev/null +++ b/src/golang.org/x/mobile/bind/java/testpkg/testpkg.go @@ -0,0 +1,176 @@ +// Copyright 2014 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. + +// Package testpkg contains bound functions for testing the cgo-JNI interface. +// This is used in tests of golang.org/x/mobile/bind/java. +package testpkg + +//go:generate gobind -lang=go -outdir=go_testpkg . +//go:generate gobind -lang=java -outdir=. . +import ( + "errors" + "fmt" + "io/ioutil" + "log" + "runtime" + "time" + + "golang.org/x/mobile/asset" +) + +type I interface { + F() + + E() error + V() int + VE() (int, error) + I() I + S() *S + StoString(*S) string + + String() string +} + +func CallF(i I) { + i.F() +} + +func CallE(i I) error { + return i.E() +} + +func CallV(i I) int { + return i.V() +} + +func CallVE(i I) (int, error) { + return i.VE() +} + +func CallI(i I) I { + return i +} + +func CallS(i I) *S { + return &S{} +} + +var keep []I + +func Keep(i I) { + keep = append(keep, i) +} + +var numSCollected int + +type S struct { + // *S already has a finalizer, so we need another object + // to count successful collections. + innerObj *int + + name string +} + +func (s *S) F() { + fmt.Printf("called F on *S{%s}\n", s.name) +} + +func (s *S) String() string { + return s.name +} + +func finalizeInner(a *int) { + numSCollected++ +} + +func New() *S { + s := &S{innerObj: new(int), name: "new"} + runtime.SetFinalizer(s.innerObj, finalizeInner) + return s +} + +func GC() { + runtime.GC() + time.Sleep(10 * time.Millisecond) + runtime.GC() +} + +func Add(x, y int) int { + return x + y +} + +func NumSCollected() int { + return numSCollected +} + +func StrDup(s string) string { + return s +} + +func Negate(x bool) bool { + return !x +} + +func Err(s string) error { + if s != "" { + return errors.New(s) + } + return nil +} + +func BytesAppend(a []byte, b []byte) []byte { + return append(a, b...) +} + +func AppendToString(str string, someBytes []byte) []byte { + a := []byte(str) + fmt.Printf("str=%q (len=%d), someBytes=%v (len=%d)\n", str, len(str), someBytes, len(someBytes)) + return append(a, someBytes...) +} + +func UnnamedParams(_, _ int, p0 string) int { + return len(p0) +} + +type Node struct { + V string + Next *Node +} + +func NewNode(name string) *Node { + return &Node{V: name} +} + +func (a *Node) String() string { + if a == nil { + return "" + } + return a.V + ":" + a.Next.String() +} + +type Receiver interface { + Hello(message string) +} + +func Hello(r Receiver, name string) { + r.Hello(fmt.Sprintf("Hello, %s!\n", name)) +} + +func GarbageCollect() { + runtime.GC() +} + +func ReadAsset() string { + rc, err := asset.Open("hello.txt") + if err != nil { + log.Fatal(err) + } + defer rc.Close() + + b, err := ioutil.ReadAll(rc) + if err != nil { + log.Fatal(err) + } + return string(b) +} diff --git a/src/golang.org/x/mobile/bind/objc/SeqTest.m b/src/golang.org/x/mobile/bind/objc/SeqTest.m new file mode 100644 index 0000000000..581674ac49 --- /dev/null +++ b/src/golang.org/x/mobile/bind/objc/SeqTest.m @@ -0,0 +1,203 @@ +// Copyright 2015 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 ignore + +#import +#import "GoTestpkg.h" + +#define ERROR(...) \ + do { \ + NSLog(__VA_ARGS__); \ + err = 1; \ + } while (0); + +static int err = 0; + +void testHello(NSString *input) { + NSString *got = GoTestpkgHello(input); + NSString *want = [NSString stringWithFormat:@"Hello, %@!", input]; + if (!got) { + ERROR(@"GoTestpkgHello(%@)= NULL, want %@", input, want); + return; + } + if (![got isEqualToString:want]) { + ERROR(@"want %@\nGoTestpkgHello(%@)= %@", want, input, got); + } +} + +void testBytesAppend(NSString *a, NSString *b) { + NSData *data_a = [a dataUsingEncoding:NSUTF8StringEncoding]; + NSData *data_b = [b dataUsingEncoding:NSUTF8StringEncoding]; + NSData *gotData = GoTestpkgBytesAppend(data_a, data_b); + NSString *got = + [[NSString alloc] initWithData:gotData encoding:NSUTF8StringEncoding]; + NSString *want = [a stringByAppendingString:b]; + if (![got isEqualToString:want]) { + ERROR(@"want %@\nGoTestpkgBytesAppend(%@, %@) = %@", want, a, b, got); + } +} + +void testReturnsError() { + NSString *value; + NSError *error; + GoTestpkgReturnsError(TRUE, &value, &error); + NSString *got = [error.userInfo valueForKey:NSLocalizedDescriptionKey]; + NSString *want = @"Error"; + if (![got isEqualToString:want]) { + ERROR(@"want %@\nGoTestpkgReturnsError(TRUE) = (%@, %@)", want, value, got); + } +} + +void testStruct() { + GoTestpkgS *s = GoTestpkgNewS(10.0, 100.0); + if (!s) { + ERROR(@"GoTestpkgNewS returned NULL"); + } + + double x = [s X]; + double y = [s Y]; + double sum = [s Sum]; + if (x != 10.0 || y != 100.0 || sum != 110.0) { + ERROR(@"GoTestpkgS(10.0, 100.0).X=%f Y=%f SUM=%f; want 10, 100, 110", x, y, + sum); + } + + double sum2 = GoTestpkgCallSSum(s); + if (sum != sum2) { + ERROR(@"GoTestpkgCallSSum(s)=%f; want %f as returned by s.Sum", sum2, sum); + } + + [s setX:7]; + [s setY:70]; + x = [s X]; + y = [s Y]; + sum = [s Sum]; + if (x != 7 || y != 70 || sum != 77) { + ERROR(@"GoTestpkgS(7, 70).X=%f Y=%f SUM=%f; want 7, 70, 77", x, y, sum); + } + + NSString *first = @"trytwotested"; + NSString *second = @"test"; + NSString *got = [s TryTwoStrings:first second:second]; + NSString *want = [first stringByAppendingString:second]; + if (![got isEqualToString:want]) { + ERROR(@"GoTestpkgS_TryTwoStrings(%@, %@)= %@; want %@", first, second, got, + want); + } + + GoTestpkgGC(); +} + +// Objective-C implementation of testpkg.I. +@interface Number : NSObject { +} +@property int32_t value; + +- (int64_t)Times:(int32_t)v; +@end + +// numI is incremented when the first numI objective-C implementation is +// deallocated. +static int numI = 0; + +@implementation Number { +} +@synthesize value; + +- (int64_t)Times:(int32_t)v { + return v * value; +} +- (void)dealloc { + if (self.value == 0) { + numI++; + } +} +@end + +void testInterface() { + // Test Go object implementing testpkg.I is handled correctly. + id goObj = GoTestpkgNewI(); + int64_t got = [goObj Times:10]; + if (got != 100) { + ERROR(@"GoTestpkgNewI().Times(10) = %lld; want %d", got, 100); + } + int32_t key = -1; + GoTestpkgRegisterI(key, goObj); + int64_t got2 = GoTestpkgMultiply(key, 10); + if (got != got2) { + ERROR(@"GoTestpkgMultiply(10 * 10) = %lld; want %lld", got2, got); + } + GoTestpkgUnregisterI(key); + + // Test Objective-C objects implementing testpkg.I is handled correctly. + @autoreleasepool { + for (int32_t i = 0; i < 10; i++) { + Number *num = [[Number alloc] init]; + num.value = i; + GoTestpkgRegisterI(i, num); + } + GoTestpkgGC(); + } + + // Registered Objective-C objects are pinned on Go side which must + // prevent deallocation from Objective-C. + for (int32_t i = 0; i < 10; i++) { + int64_t got = GoTestpkgMultiply(i, 2); + if (got != i * 2) { + ERROR(@"GoTestpkgMultiply(%d, 2) = %lld; want %d", i, got, i * 2); + return; + } + GoTestpkgUnregisterI(i); + GoTestpkgGC(); + } + // Unregistered all Objective-C objects. +} + +// Invokes functions and object methods defined in Testpkg.h. +// +// TODO(hyangah): apply testing framework (e.g. XCTestCase) +// and test through xcodebuild. +int main(void) { + @autoreleasepool { + GoTestpkgHi(); + + GoTestpkgInt(42); + + int64_t sum = GoTestpkgSum(31, 21); + if (sum != 52) { + ERROR(@"GoTestpkgSum(31, 21) = %lld, want 52\n", sum); + } + + testHello(@"세계"); // korean, utf-8, world. + + unichar t[] = { + 0xD83D, 0xDCA9, + }; // utf-16, pile of poo. + testHello([NSString stringWithCharacters:t length:2]); + + testBytesAppend(@"Foo", @"Bar"); + + testStruct(); + int numS = GoTestpkgCollectS( + 1, 10); // within 10 seconds, collect the S used in testStruct. + if (numS != 1) { + ERROR(@"%d S objects were collected; S used in testStruct is supposed to " + @"be collected.", + numS); + } + + @autoreleasepool { + testInterface(); + } + if (numI != 1) { + ERROR(@"%d I objects were collected; I used in testInterface is supposed " + @"to be collected.", + numI); + } + } + + fprintf(stderr, "%s\n", err ? "FAIL" : "PASS"); + return err; +} diff --git a/src/golang.org/x/mobile/bind/objc/doc.go b/src/golang.org/x/mobile/bind/objc/doc.go new file mode 100644 index 0000000000..2370311dc1 --- /dev/null +++ b/src/golang.org/x/mobile/bind/objc/doc.go @@ -0,0 +1,6 @@ +// Copyright 2015 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. + +// Package objc implements the Objective-C language bindings. +package objc diff --git a/src/golang.org/x/mobile/bind/objc/seq.h b/src/golang.org/x/mobile/bind/objc/seq.h new file mode 100644 index 0000000000..53d55ae30f --- /dev/null +++ b/src/golang.org/x/mobile/bind/objc/seq.h @@ -0,0 +1,83 @@ +// Copyright 2015 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. + +#ifndef __GO_SEQ_HDR__ +#define __GO_SEQ_HDR__ + +#include + +// GoSeq is a sequence of machine-dependent encoded values, which +// is a simple C equivalent of seq.Buffer. +// Used by automatically generated language bindings to talk to Go. +typedef struct GoSeq { + uint8_t *buf; + size_t off; + size_t len; + size_t cap; +} GoSeq; + +// GoSeqRef is an object tagged with an integer for passing back and +// forth across the language boundary. A GoSeqRef may represent either +// an instance of a Go object, or an Objective-C object passed to Go. +// The explicit allocation of a GoSeqRef is used to pin a Go object +// when it is passed to Objective-C. The Go seq package maintains a +// reference to the Go object in a map keyed by the refnum. When the +// GoSeqRef is deallocated, the Go seq package will clear the +// corresponding entry in the map. +// TODO(hyangah): update the doc as golang.org/issue/10933 is fixed. +@interface GoSeqRef : NSObject { +} +@property int32_t refnum; +@property(strong) id obj; // NULL when representing a Go object. + +// new GoSeqRef object to proxy a Go object. The refnum must be +// provided from Go side. +- (instancetype)initWithRefnum:(int32_t)refnum obj:(id)obj; + +@end + +// go_seq_free releases resources of the GoSeq. +extern void go_seq_free(GoSeq *seq); + +extern BOOL go_seq_readBool(GoSeq *seq); +extern int go_seq_readInt(GoSeq *seq); +extern int8_t go_seq_readInt8(GoSeq *seq); +extern int16_t go_seq_readInt16(GoSeq *seq); +extern int32_t go_seq_readInt32(GoSeq *seq); +extern int64_t go_seq_readInt64(GoSeq *seq); +extern float go_seq_readFloat32(GoSeq *seq); +extern double go_seq_readFloat64(GoSeq *seq); +extern GoSeqRef *go_seq_readRef(GoSeq *seq); +extern NSString *go_seq_readUTF8(GoSeq *seq); +extern NSData *go_seq_readByteArray(GoSeq *seq); + +extern void go_seq_writeBool(GoSeq *seq, BOOL v); +extern void go_seq_writeInt(GoSeq *seq, int v); +extern void go_seq_writeInt8(GoSeq *seq, int8_t v); +extern void go_seq_writeInt16(GoSeq *seq, int16_t v); +extern void go_seq_writeInt32(GoSeq *seq, int32_t v); +extern void go_seq_writeInt64(GoSeq *seq, int64_t v); +extern void go_seq_writeFloat32(GoSeq *seq, float v); +extern void go_seq_writeFloat64(GoSeq *seq, double v); +extern void go_seq_writeRef(GoSeq *seq, GoSeqRef *ref); +extern void go_seq_writeUTF8(GoSeq *seq, NSString *v); + +// go_seq_writeByteArray writes the data bytes to the seq. Note that the +// data should be valid until the the subsequent go_seq_send call completes. +extern void go_seq_writeByteArray(GoSeq *seq, NSData *data); + +// go_seq_writeObjcRef is a special case of go_seq_writeRef for +// Objective-C objects that implement Go interface. +extern void go_seq_writeObjcRef(GoSeq *seq, id obj); + +// go_seq_send sends a function invocation request to Go. +// It blocks until the function completes. +// If the request is for a method, the first element in req is +// a Ref to the receiver. +extern void go_seq_send(char *descriptor, int code, GoSeq *req, GoSeq *res); + +extern void go_seq_register_proxy(const char *descriptor, + void(*fn)(id, int, GoSeq *, GoSeq *)); + +#endif // __GO_SEQ_HDR__ diff --git a/src/golang.org/x/mobile/bind/objc/seq_darwin.go b/src/golang.org/x/mobile/bind/objc/seq_darwin.go new file mode 100644 index 0000000000..bb34850fba --- /dev/null +++ b/src/golang.org/x/mobile/bind/objc/seq_darwin.go @@ -0,0 +1,185 @@ +// Copyright 2015 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. + +package objc + +/* +#cgo CFLAGS: -x objective-c -fobjc-arc +#cgo LDFLAGS: -framework Foundation + +#include +#include +#include + +void init_seq(); +void go_seq_recv(int32_t, const char*, int, uint8_t*, size_t, uint8_t**, size_t*); +*/ +import "C" + +import ( + "fmt" + "sync" + "unsafe" + + "golang.org/x/mobile/bind/seq" +) + +const debug = false + +const maxSliceLen = 1<<31 - 1 + +// Send is called by Objective-C to send a request to run a Go function. +//export Send +func Send(descriptor string, code int, req *C.uint8_t, reqlen C.size_t, res **C.uint8_t, reslen *C.size_t) { + fn := seq.Registry[descriptor][code] + if fn == nil { + panic(fmt.Sprintf("invalid descriptor(%s) and code(0x%x)", descriptor, code)) + } + var in, out *seq.Buffer + if reqlen > 0 { + in = &seq.Buffer{Data: (*[maxSliceLen]byte)(unsafe.Pointer(req))[:reqlen]} + } + if reslen != nil { + out = new(seq.Buffer) + } + + fn(out, in) + if out != nil { + // sender expects results. + seqToBuf(res, reslen, out) + } +} + +// DestroyRef is called by Objective-C to inform Go it is done with a reference. +//export DestroyRef +func DestroyRef(refnum C.int32_t) { + seq.Delete(int32(refnum)) +} + +type request struct { + ref *seq.Ref + handle int32 + code int + in *seq.Buffer +} + +var recv struct { + sync.Mutex + cond sync.Cond // signals req is not empty + req []request + next int32 // next handle value +} + +var res struct { + sync.Mutex + cond sync.Cond // signals a response is filled in + out map[int32]*seq.Buffer // handle -> output +} + +func init() { + recv.cond.L = &recv.Mutex + recv.next = 411 // arbitrary starting point distrinct from Go and Objective-C object ref nums. + res.cond.L = &res.Mutex + res.out = make(map[int32]*seq.Buffer) +} + +func seqToBuf(bufptr **C.uint8_t, lenptr *C.size_t, buf *seq.Buffer) { + if debug { + fmt.Printf("seqToBuf tag 1, len(buf.Data)=%d, *lenptr=%d\n", len(buf.Data), *lenptr) + } + if len(buf.Data) == 0 { + *lenptr = 0 + return + } + if len(buf.Data) > int(*lenptr) { + // TODO(crawshaw): realloc + C.free(unsafe.Pointer(*bufptr)) + m := C.malloc(C.size_t(len(buf.Data))) + if uintptr(m) == 0 { + panic(fmt.Sprintf("malloc failed, size=%d", len(buf.Data))) + } + *bufptr = (*C.uint8_t)(m) + *lenptr = C.size_t(len(buf.Data)) + } + C.memcpy(unsafe.Pointer(*bufptr), unsafe.Pointer(&buf.Data[0]), C.size_t(len(buf.Data))) +} + +type cStringMap struct { + sync.Mutex + m map[string]*C.char +} + +var cstrings = &cStringMap{ + m: make(map[string]*C.char), +} + +func (s *cStringMap) get(k string) *C.char { + s.Lock() + c, ok := s.m[k] + if !ok { + c = C.CString(k) + s.m[k] = c + } + s.Unlock() + return c +} + +// transact calls a method on an Objective-C object instance. +// It blocks until the call is complete. +// +// Code (>0) is the method id assigned by gobind. +// Code -1 is used to instruct Objective-C to decrement the ref count of +// the Objective-Co object. +func transact(ref *seq.Ref, descriptor string, code int, in *seq.Buffer) *seq.Buffer { + var ( + res *C.uint8_t = nil + resLen C.size_t = 0 + req *C.uint8_t = nil + reqLen C.size_t = 0 + ) + + if len(in.Data) > 0 { + req = (*C.uint8_t)(unsafe.Pointer(&in.Data[0])) + reqLen = C.size_t(len(in.Data)) + } + + if debug { + fmt.Printf("transact: ref.Num = %d code = %d\n", ref.Num, code) + } + + desc := cstrings.get(descriptor) + C.go_seq_recv(C.int32_t(ref.Num), desc, C.int(code), req, reqLen, &res, &resLen) + + if resLen > 0 { + goSlice := (*[maxSliceLen]byte)(unsafe.Pointer(res))[:resLen] + out := new(seq.Buffer) + out.Data = make([]byte, int(resLen)) + copy(out.Data, goSlice) + C.free(unsafe.Pointer(res)) + // TODO: own or copy []bytes whose addresses were passed in. + return out + } + return nil +} + +// finalizeRef notifies Objective-C side of GC of a proxy object from Go side. +func finalizeRef(ref *seq.Ref) { + if ref.Num < 0 { + panic(fmt.Sprintf("not an Objective-C ref: %d", ref.Num)) + } + transact(ref, "", -1, new(seq.Buffer)) +} + +func init() { + seq.EncString = func(out *seq.Buffer, v string) { + out.WriteUTF8(v) + } + seq.DecString = func(in *seq.Buffer) string { + return in.ReadUTF8() + } + seq.Transact = transact + seq.FinalizeRef = finalizeRef + + C.init_seq() +} diff --git a/src/golang.org/x/mobile/bind/objc/seq_darwin.m b/src/golang.org/x/mobile/bind/objc/seq_darwin.m new file mode 100644 index 0000000000..92c0e45db6 --- /dev/null +++ b/src/golang.org/x/mobile/bind/objc/seq_darwin.m @@ -0,0 +1,522 @@ +// Copyright 2015 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. + +#include +#include +#include +#include +#include "seq.h" +#include "_cgo_export.h" + +#ifdef DEBUG +#define LOG_DEBUG(...) NSLog(__VA_ARGS__); +#else +#define LOG_DEBUG(...) ; +#endif + +#define LOG_INFO(...) NSLog(__VA_ARGS__); +#define LOG_FATAL(...) \ + { \ + NSLog(__VA_ARGS__); \ + @throw \ + [NSException exceptionWithName:NSInternalInconsistencyException \ + reason:[NSString stringWithFormat:__VA_ARGS__] \ + userInfo:NULL]; \ + } + +// * Objective-C implementation of a Go interface type +// +// For an interface testpkg.I, gobind defines a protocol GoSeqTestpkgI. +// Reference tracker (tracker) maintains two maps: +// 1) _refs: objective-C object pointer -> a refnum (starting from 42). +// 2) _objs: refnum -> RefCounter. +// +// Whenever a user's object conforming the protocol is sent to Go (through +// a function or method that takes I), _refs is consulted to find the refnum +// of the object. If not found, the refnum is assigned and stored. +// +// _objs is also updated so that the RefCounter is incremented and the +// user's object is pinned. +// +// When a Go side needs to call a method of the interface, the Go side +// notifies the Objective-C side of the object's refnum, and the method code +// as gobind assigned. Upon receiving the request, Objective-C side looks +// up the object from _objs map, and looks up the proxy global function +// registered in 'proxies'. The global function deserializes/serializes +// the parameters and sends the method to the object. +// +// The RefCount counts the references on objective-C objects from Go side, +// and pins the objective-C objects until there is no more reference from +// Go side. +// +// * Objective-C proxy of a Go object (struct or interface type) +// +// For Go type object, a objective-C proxy instance is created whenever +// the object reference is passed into objective-C. + +// A simple thread-safe mutable dictionary. +@interface goSeqDictionary : NSObject { +} +@property NSMutableDictionary *dict; +@end + +@implementation goSeqDictionary + +- (id)init { + if (self = [super init]) { + _dict = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (id)get:(id)key { + @synchronized(self) { + return [_dict objectForKey:key]; + } +} + +- (void)put:(id)obj withKey:(id)key { + @synchronized(self) { + [_dict setObject:obj forKey:key]; + } +} +@end + +// The proxies maps Go interface name (e.g. go.testpkg.I) to the proxy function +// gobind generates for interfaces defined in a module. The function is +// registered by calling go_seq_register_proxy from a global contructor funcion. +static goSeqDictionary *proxies = NULL; + +__attribute__((constructor)) static void go_seq_very_init() { + proxies = [[goSeqDictionary alloc] init]; +} + +void go_seq_register_proxy(const char *descriptor, + void (*fn)(id, int, GoSeq *, GoSeq *)) { + [proxies put:^(id obj, int code, GoSeq *in, GoSeq *out) { + fn(obj, code, in, out); + } withKey:[NSString stringWithUTF8String:descriptor]]; +} + +// RefTracker encapsulates a map of objective-C objects passed to Go and +// the reference number counter which is incremented whenever an objective-C +// object that implements a Go interface is created. +@interface RefTracker : NSObject { + int32_t _next; + NSMutableDictionary *_refs; // map: object ptr -> refnum + NSMutableDictionary *_objs; // map: refnum -> RefCounter* +} + +- (id)init; + +// decrements the counter of the objective-C object with the reference number. +// This is called whenever a Go proxy to this object is finalized. +// When the counter reaches 0, the object is removed from the map. +- (void)dec:(int32_t)refnum; + +// returns the object of the reference number. +- (id)get:(int32_t)refnum; + +// returns the reference number of the object and increments the ref count. +// This is called whenever an Objective-C object is sent to Go side. +- (int32_t)assignRefnumAndIncRefcount:(id)obj; +@end + +RefTracker *tracker = NULL; + +// mem_ensure ensures that m has at least size bytes free. +// If m is NULL, it is created. +static void mem_ensure(GoSeq *m, uint32_t size) { + size_t cap = m->cap; + if (cap > m->off + size) { + return; + } + if (cap == 0) { + cap = 64; + } + while (cap < m->off + size) { + cap *= 2; + } + m->buf = (uint8_t *)realloc((void *)m->buf, cap); + if (m->buf == NULL) { + LOG_FATAL(@"mem_ensure realloc failed, off=%zu, size=%u", m->off, size); + } + m->cap = cap; +} + +static uint32_t align(uint32_t offset, uint32_t alignment) { + uint32_t pad = offset % alignment; + if (pad > 0) { + pad = alignment - pad; + } + return pad + offset; +} + +static uint8_t *mem_read(GoSeq *m, uint32_t size, uint32_t alignment) { + if (size == 0) { + return NULL; + } + if (m == NULL) { + LOG_FATAL(@"mem_read on NULL GoSeq"); + } + uint32_t offset = align(m->off, alignment); + + if (m->len - offset < size) { + LOG_FATAL(@"short read"); + } + uint8_t *res = m->buf + offset; + m->off = offset + size; + return res; +} + +static uint8_t *mem_write(GoSeq *m, uint32_t size, uint32_t alignment) { + if (m->off != m->len) { + LOG_FATAL(@"write can only append to seq, size: (off=%zu len=%zu, size=%u)", + m->off, m->len, size); + } + uint32_t offset = align(m->off, alignment); + mem_ensure(m, offset - m->off + size); + uint8_t *res = m->buf + offset; + m->off = offset + size; + m->len = offset + size; + return res; +} + +// extern +void go_seq_free(GoSeq *m) { + if (m != NULL) { + free(m->buf); + } +} + +#define MEM_READ(seq, ty) ((ty *)mem_read(seq, sizeof(ty), sizeof(ty))) +#define MEM_WRITE(seq, ty) (*(ty *)mem_write(seq, sizeof(ty), sizeof(ty))) + +int go_seq_readInt(GoSeq *seq) { + int64_t v = go_seq_readInt64(seq); + return v; // Assume that Go-side used WriteInt to encode 'int' value. +} + +void go_seq_writeInt(GoSeq *seq, int v) { go_seq_writeInt64(seq, v); } + +BOOL go_seq_readBool(GoSeq *seq) { + int8_t v = go_seq_readInt8(seq); + return v ? YES : NO; +} + +void go_seq_writeBool(GoSeq *seq, BOOL v) { go_seq_writeInt8(seq, v ? 1 : 0); } + +int8_t go_seq_readInt8(GoSeq *seq) { + int8_t *v = MEM_READ(seq, int8_t); + return v == NULL ? 0 : *v; +} +void go_seq_writeInt8(GoSeq *seq, int8_t v) { MEM_WRITE(seq, int8_t) = v; } + +int16_t go_seq_readInt16(GoSeq *seq) { + int16_t *v = MEM_READ(seq, int16_t); + return v == NULL ? 0 : *v; +} +void go_seq_writeInt16(GoSeq *seq, int16_t v) { MEM_WRITE(seq, int16_t) = v; } + +int32_t go_seq_readInt32(GoSeq *seq) { + int32_t *v = MEM_READ(seq, int32_t); + return v == NULL ? 0 : *v; +} +void go_seq_writeInt32(GoSeq *seq, int32_t v) { MEM_WRITE(seq, int32_t) = v; } + +int64_t go_seq_readInt64(GoSeq *seq) { + int64_t *v = MEM_READ(seq, int64_t); + return v == NULL ? 0 : *v; +} +void go_seq_writeInt64(GoSeq *seq, int64_t v) { MEM_WRITE(seq, int64_t) = v; } + +float go_seq_readFloat32(GoSeq *seq) { + float *v = MEM_READ(seq, float); + return v == NULL ? 0 : *v; +} +void go_seq_writeFloat32(GoSeq *seq, float v) { MEM_WRITE(seq, float) = v; } + +double go_seq_readFloat64(GoSeq *seq) { + double *v = MEM_READ(seq, double); + return v == NULL ? 0 : *v; +} +void go_seq_writeFloat64(GoSeq *seq, double v) { MEM_WRITE(seq, double) = v; } + +NSString *go_seq_readUTF8(GoSeq *seq) { + int32_t len = *MEM_READ(seq, int32_t); + if (len == 0) { + return NULL; + } + const void *buf = (const void *)mem_read(seq, len, 1); + return [[NSString alloc] initWithBytes:buf + length:len + encoding:NSUTF8StringEncoding]; +} + +void go_seq_writeUTF8(GoSeq *seq, NSString *s) { + int32_t len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + MEM_WRITE(seq, int32_t) = len; + + if (len == 0 && s.length > 0) { + LOG_INFO(@"unable to incode an NSString into UTF-8"); + return; + } + + char *buf = (char *)mem_write(seq, len, 1); + NSUInteger used; + [s getBytes:buf + maxLength:len + usedLength:&used + encoding:NSUTF8StringEncoding + options:0 + range:NSMakeRange(0, [s length]) + remainingRange:NULL]; + if (used < len) { + buf[used] = '\0'; + } + return; +} + +NSData *go_seq_readByteArray(GoSeq *seq) { + int64_t sz = *MEM_READ(seq, int64_t); + if (sz == 0) { + return [NSData data]; + } + // BUG(hyangah): it is possible that *ptr is already GC'd by Go runtime. + void *ptr = (void *)(*MEM_READ(seq, int64_t)); + return [NSData dataWithBytes:ptr length:sz]; +} + +void go_seq_writeByteArray(GoSeq *seq, NSData *data) { + int64_t sz = data.length; + MEM_WRITE(seq, int64_t) = sz; + if (sz == 0) { + return; + } + + int64_t ptr = (int64_t)data.bytes; + MEM_WRITE(seq, int64_t) = ptr; + return; +} + +typedef void (^proxyFn)(id, int, GoSeq *, GoSeq *); + +// called from Go when Go tries to access an Objective-C object. +void go_seq_recv(int32_t refnum, const char *desc, int code, uint8_t *in_ptr, + size_t in_len, uint8_t **out_ptr, size_t *out_len) { + if (code == -1) { // special signal from seq.FinalizeRef in Go + [tracker dec:refnum]; + return; + } + GoSeq ins = {}; + ins.buf = in_ptr; // Memory allocated from Go + ins.off = 0; + ins.len = in_len; + ins.cap = in_len; + id obj = [tracker get:refnum]; + if (obj == NULL) { + LOG_FATAL(@"invalid object for ref %d", refnum); + return; + } + + NSString *k = [NSString stringWithUTF8String:desc]; + + proxyFn fn = [proxies get:k]; + if (fn == NULL) { + LOG_FATAL(@"cannot find a proxy function for %s", desc); + return; + } + GoSeq outs = {}; + fn(obj, code, &ins, &outs); + + if (out_ptr == NULL) { + free(outs.buf); + } else { + *out_ptr = outs.buf; // Let Go side free this memory + *out_len = outs.len; + } +} + +void go_seq_send(char *descriptor, int code, GoSeq *req, GoSeq *res) { + if (descriptor == NULL) { + LOG_FATAL(@"invalid NULL descriptor"); + } + uint8_t *req_buf = NULL; + size_t req_len = 0; + if (req != NULL) { + req_buf = req->buf; + req_len = req->len; + } + + uint8_t **res_buf = NULL; + size_t *res_len = NULL; + if (res != NULL) { + res_buf = &res->buf; + res_len = &res->len; + } + + GoString desc; + desc.p = descriptor; + desc.n = strlen(descriptor); + Send(desc, (GoInt)code, req_buf, req_len, res_buf, res_len); +} + +#define IS_FROM_GO(refnum) ((refnum) < 0) + +// init_seq is called when the Go side is initialized. +void init_seq() { tracker = [[RefTracker alloc] init]; } + +GoSeqRef *go_seq_readRef(GoSeq *seq) { + int32_t refnum = go_seq_readInt32(seq); + if (IS_FROM_GO(refnum)) { + return [[GoSeqRef alloc] initWithRefnum:refnum obj:NULL]; + } + return [[GoSeqRef alloc] initWithRefnum:refnum obj:[tracker get:refnum]]; +} + +// TODO(hyangah): make this go_seq_writeRef(GoSeq *seq, int32_t refnum, id obj) +// and get read of GoSeqRef. +void go_seq_writeRef(GoSeq *seq, GoSeqRef *v) { + int32_t refnum = v.refnum; + if (!IS_FROM_GO(refnum)) { + LOG_FATAL(@"go_seq_writeRef on objective-c objects is not permitted"); + } + go_seq_writeInt32(seq, refnum); + return; +} + +void go_seq_writeObjcRef(GoSeq *seq, id obj) { + int32_t refnum = [tracker assignRefnumAndIncRefcount:obj]; + go_seq_writeInt32(seq, refnum); +} + +@implementation GoSeqRef { +} + +- (id)init { + LOG_FATAL(@"GoSeqRef init is disallowed"); + return nil; +} + +// called when an object from Go is passed in. +- (instancetype)initWithRefnum:(int32_t)refnum obj:(id)obj { + self = [super init]; + if (self) { + _refnum = refnum; + _obj = obj; + } + return self; +} + +- (void)dealloc { + if (IS_FROM_GO(_refnum)) { + DestroyRef(_refnum); + } +} +@end + +// RefCounter is a pair of (GoSeqProxy, count). GoSeqProxy has a strong +// reference to an Objective-C object. The count corresponds to +// the number of Go proxy objects. +// +// RefTracker maintains a map of refnum to RefCounter, for every +// Objective-C objects passed to Go. This map allows the transact +// call to relay the method call to the right Objective-C object, and +// prevents the Objective-C objects from being deallocated +// while they are still referenced from Go side. +@interface RefCounter : NSObject { +} +@property(strong, readonly) id obj; +@property int cnt; + +- (id)initWithObject:(id)obj; +@end + +@implementation RefCounter { +} +- (id)initWithObject:(id)obj { + self = [super init]; + if (self) { + _obj = obj; + _cnt = 0; + } + return self; +} + +@end + +@implementation RefTracker { +} + +- (id)init { + self = [super init]; + if (self) { + _next = 42; + _objs = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (void)dec:(int32_t)refnum { // called whenever a go proxy object is finalized. + if (IS_FROM_GO(refnum)) { + LOG_FATAL(@"dec:invalid refnum for Objective-C objects"); + return; + } + @synchronized(self) { + id key = @(refnum); + RefCounter *counter = [_objs objectForKey:key]; + if (counter == NULL) { + LOG_FATAL(@"unknown refnum"); + return; + } + int n = counter.cnt; + if (n <= 0) { + LOG_FATAL(@"refcount underflow"); + } else if (n == 1) { + LOG_DEBUG(@"remove the reference %d", refnum); + NSValue *ptr = [NSValue valueWithPointer:(const void *)(counter.obj)]; + [_refs removeObjectForKey:ptr]; + [_objs removeObjectForKey:key]; + } else { + counter.cnt = n - 1; + } + } +} + +- (id)get:(int32_t)refnum { + if (IS_FROM_GO(refnum)) { + LOG_FATAL(@"get:invalid refnum for Objective-C objects"); + return NULL; + } + @synchronized(self) { + RefCounter *counter = _objs[@(refnum)]; + if (counter == NULL) { + LOG_FATAL(@"unidentified object refnum: %d", refnum); + return NULL; + } + return counter.obj; + } +} + +- (int32_t)assignRefnumAndIncRefcount:(id)obj { + @synchronized(self) { + NSValue *ptr = [NSValue valueWithPointer:(const void *)(obj)]; + NSNumber *refnum = [_refs objectForKey:ptr]; + if (refnum == NULL) { + refnum = @(_next++); + _refs[ptr] = refnum; + } + RefCounter *counter = [_objs objectForKey:refnum]; + if (counter == NULL) { + counter = [[RefCounter alloc] initWithObject:obj]; + counter.cnt = 1; + _objs[refnum] = counter; + } else { + counter.cnt++; + } + return (int32_t)([refnum intValue]); + } +} + +@end diff --git a/src/golang.org/x/mobile/bind/objc/test.bash b/src/golang.org/x/mobile/bind/objc/test.bash new file mode 100755 index 0000000000..2f114f9537 --- /dev/null +++ b/src/golang.org/x/mobile/bind/objc/test.bash @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +# Copyright 2015 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. + +# This is a script to compile and run SeqTest. + +set -e + +export GOARCH=amd64 +export GOOS=darwin # TODO: arm, arm64. +export CGO_ENABLED=1 + +WORK=`mktemp -d /tmp/objctest.XXXXX` +function cleanup() { + rm -rf ${WORK} +} +trap cleanup EXIT + +(cd testpkg; go generate) + +go build -x -v -buildmode=c-archive -o=${WORK}/libgo.a test_main.go +cp ./seq.h ${WORK}/ +cp testpkg/objc_testpkg/GoTestpkg.* ${WORK}/ +cp ./SeqTest.m ${WORK}/ + +ccargs="-Wl,-no_pie -framework Foundation -fobjc-arc" +$(go env CC) $(go env GOGCCFLAGS) $ccargs -o ${WORK}/a.out ${WORK}/libgo.a ${WORK}/GoTestpkg.m ${WORK}/SeqTest.m + +${WORK}/a.out diff --git a/src/golang.org/x/mobile/bind/objc/test_main.go b/src/golang.org/x/mobile/bind/objc/test_main.go new file mode 100644 index 0000000000..ccf4e981e8 --- /dev/null +++ b/src/golang.org/x/mobile/bind/objc/test_main.go @@ -0,0 +1,19 @@ +// Copyright 2015 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 ignore + +// Dummy main used by test.bash to build testpkg. +package main + +import "C" + +import ( + _ "golang.org/x/mobile/bind/objc" + _ "golang.org/x/mobile/bind/objc/testpkg/go_testpkg" +) + +func main() { + panic("not called in a c-archive") +} diff --git a/src/golang.org/x/mobile/bind/objc/testpkg/go_testpkg/go_testpkg.go b/src/golang.org/x/mobile/bind/objc/testpkg/go_testpkg/go_testpkg.go new file mode 100644 index 0000000000..e26928c5f0 --- /dev/null +++ b/src/golang.org/x/mobile/bind/objc/testpkg/go_testpkg/go_testpkg.go @@ -0,0 +1,210 @@ +// Package go_testpkg is an autogenerated binder stub for package testpkg. +// gobind -lang=go golang.org/x/mobile/bind/objc/testpkg +// +// File is generated by gobind. Do not edit. +package go_testpkg + +import ( + "golang.org/x/mobile/bind/objc/testpkg" + "golang.org/x/mobile/bind/seq" +) + +func proxy_BytesAppend(out, in *seq.Buffer) { + param_a := in.ReadByteArray() + param_b := in.ReadByteArray() + res := testpkg.BytesAppend(param_a, param_b) + out.WriteByteArray(res) +} + +func proxy_CallSSum(out, in *seq.Buffer) { + // Must be a Go object + param_s_ref := in.ReadRef() + param_s := param_s_ref.Get().(*testpkg.S) + res := testpkg.CallSSum(param_s) + out.WriteFloat64(res) +} + +func proxy_CollectS(out, in *seq.Buffer) { + param_want := in.ReadInt() + param_timeoutSec := in.ReadInt() + res := testpkg.CollectS(param_want, param_timeoutSec) + out.WriteInt(res) +} + +func proxy_GC(out, in *seq.Buffer) { + testpkg.GC() +} + +func proxy_Hello(out, in *seq.Buffer) { + param_s := in.ReadString() + res := testpkg.Hello(param_s) + out.WriteString(res) +} + +func proxy_Hi(out, in *seq.Buffer) { + testpkg.Hi() +} + +const ( + proxyI_Descriptor = "go.testpkg.I" + proxyI_Times_Code = 0x10a +) + +func proxyI_Times(out, in *seq.Buffer) { + ref := in.ReadRef() + v := ref.Get().(testpkg.I) + param_v := in.ReadInt32() + res := v.Times(param_v) + out.WriteInt64(res) +} + +func init() { + seq.Register(proxyI_Descriptor, proxyI_Times_Code, proxyI_Times) +} + +type proxyI seq.Ref + +func (p *proxyI) Times(v int32) int64 { + in := new(seq.Buffer) + in.WriteInt32(v) + out := seq.Transact((*seq.Ref)(p), "go.testpkg.I", proxyI_Times_Code, in) + res_0 := out.ReadInt64() + return res_0 +} + +func proxy_Int(out, in *seq.Buffer) { + param_x := in.ReadInt32() + testpkg.Int(param_x) +} + +func proxy_Multiply(out, in *seq.Buffer) { + param_idx := in.ReadInt32() + param_val := in.ReadInt32() + res := testpkg.Multiply(param_idx, param_val) + out.WriteInt64(res) +} + +func proxy_NewI(out, in *seq.Buffer) { + res := testpkg.NewI() + out.WriteGoRef(res) +} + +func proxy_NewS(out, in *seq.Buffer) { + param_x := in.ReadFloat64() + param_y := in.ReadFloat64() + res := testpkg.NewS(param_x, param_y) + out.WriteGoRef(res) +} + +func proxy_RegisterI(out, in *seq.Buffer) { + param_idx := in.ReadInt32() + var param_i testpkg.I + param_i_ref := in.ReadRef() + if param_i_ref.Num < 0 { // go object + param_i = param_i_ref.Get().(testpkg.I) + } else { // foreign object + param_i = (*proxyI)(param_i_ref) + } + testpkg.RegisterI(param_idx, param_i) +} + +func proxy_ReturnsError(out, in *seq.Buffer) { + param_b := in.ReadBool() + res, err := testpkg.ReturnsError(param_b) + out.WriteString(res) + if err == nil { + out.WriteString("") + } else { + out.WriteString(err.Error()) + } +} + +const ( + proxyS_Descriptor = "go.testpkg.S" + proxyS_X_Get_Code = 0x00f + proxyS_X_Set_Code = 0x01f + proxyS_Y_Get_Code = 0x10f + proxyS_Y_Set_Code = 0x11f + proxyS_Sum_Code = 0x00c + proxyS_TryTwoStrings_Code = 0x10c +) + +type proxyS seq.Ref + +func proxyS_X_Set(out, in *seq.Buffer) { + ref := in.ReadRef() + v := in.ReadFloat64() + ref.Get().(*testpkg.S).X = v +} + +func proxyS_X_Get(out, in *seq.Buffer) { + ref := in.ReadRef() + v := ref.Get().(*testpkg.S).X + out.WriteFloat64(v) +} + +func proxyS_Y_Set(out, in *seq.Buffer) { + ref := in.ReadRef() + v := in.ReadFloat64() + ref.Get().(*testpkg.S).Y = v +} + +func proxyS_Y_Get(out, in *seq.Buffer) { + ref := in.ReadRef() + v := ref.Get().(*testpkg.S).Y + out.WriteFloat64(v) +} + +func proxyS_Sum(out, in *seq.Buffer) { + ref := in.ReadRef() + v := ref.Get().(*testpkg.S) + res := v.Sum() + out.WriteFloat64(res) +} + +func proxyS_TryTwoStrings(out, in *seq.Buffer) { + ref := in.ReadRef() + v := ref.Get().(*testpkg.S) + param_first := in.ReadString() + param_second := in.ReadString() + res := v.TryTwoStrings(param_first, param_second) + out.WriteString(res) +} + +func init() { + seq.Register(proxyS_Descriptor, proxyS_X_Set_Code, proxyS_X_Set) + seq.Register(proxyS_Descriptor, proxyS_X_Get_Code, proxyS_X_Get) + seq.Register(proxyS_Descriptor, proxyS_Y_Set_Code, proxyS_Y_Set) + seq.Register(proxyS_Descriptor, proxyS_Y_Get_Code, proxyS_Y_Get) + seq.Register(proxyS_Descriptor, proxyS_Sum_Code, proxyS_Sum) + seq.Register(proxyS_Descriptor, proxyS_TryTwoStrings_Code, proxyS_TryTwoStrings) +} + +func proxy_Sum(out, in *seq.Buffer) { + param_x := in.ReadInt64() + param_y := in.ReadInt64() + res := testpkg.Sum(param_x, param_y) + out.WriteInt64(res) +} + +func proxy_UnregisterI(out, in *seq.Buffer) { + param_idx := in.ReadInt32() + testpkg.UnregisterI(param_idx) +} + +func init() { + seq.Register("testpkg", 1, proxy_BytesAppend) + seq.Register("testpkg", 2, proxy_CallSSum) + seq.Register("testpkg", 3, proxy_CollectS) + seq.Register("testpkg", 4, proxy_GC) + seq.Register("testpkg", 5, proxy_Hello) + seq.Register("testpkg", 6, proxy_Hi) + seq.Register("testpkg", 7, proxy_Int) + seq.Register("testpkg", 8, proxy_Multiply) + seq.Register("testpkg", 9, proxy_NewI) + seq.Register("testpkg", 10, proxy_NewS) + seq.Register("testpkg", 11, proxy_RegisterI) + seq.Register("testpkg", 12, proxy_ReturnsError) + seq.Register("testpkg", 13, proxy_Sum) + seq.Register("testpkg", 14, proxy_UnregisterI) +} diff --git a/src/golang.org/x/mobile/bind/objc/testpkg/objc_testpkg/GoTestpkg.h b/src/golang.org/x/mobile/bind/objc/testpkg/objc_testpkg/GoTestpkg.h new file mode 100644 index 0000000000..fdee3d4334 --- /dev/null +++ b/src/golang.org/x/mobile/bind/objc/testpkg/objc_testpkg/GoTestpkg.h @@ -0,0 +1,60 @@ +// Objective-C API for talking to golang.org/x/mobile/bind/objc/testpkg Go package. +// gobind -lang=objc golang.org/x/mobile/bind/objc/testpkg +// +// File is generated by gobind. Do not edit. + +#ifndef __GoTestpkg_H__ +#define __GoTestpkg_H__ + +#include + +@class GoTestpkgI; + +@class GoTestpkgS; + +@protocol GoTestpkgI +- (int64_t)Times:(int32_t)v; +@end + +@interface GoTestpkgS : NSObject { +} +@property(strong, readonly) id ref; + +- (id)initWithRef:(id)ref; +- (double)X; +- (void)setX:(double)v; +- (double)Y; +- (void)setY:(double)v; +- (double)Sum; +- (NSString*)TryTwoStrings:(NSString*)first second:(NSString*)second; +@end + +FOUNDATION_EXPORT NSData* GoTestpkgBytesAppend(NSData* a, NSData* b); + +FOUNDATION_EXPORT double GoTestpkgCallSSum(GoTestpkgS* s); + +FOUNDATION_EXPORT int GoTestpkgCollectS(int want, int timeoutSec); + +FOUNDATION_EXPORT void GoTestpkgGC(); + +FOUNDATION_EXPORT NSString* GoTestpkgHello(NSString* s); + +FOUNDATION_EXPORT void GoTestpkgHi(); + +FOUNDATION_EXPORT void GoTestpkgInt(int32_t x); + +FOUNDATION_EXPORT int64_t GoTestpkgMultiply(int32_t idx, int32_t val); + +FOUNDATION_EXPORT id GoTestpkgNewI(); + +FOUNDATION_EXPORT GoTestpkgS* GoTestpkgNewS(double x, double y); + +FOUNDATION_EXPORT void GoTestpkgRegisterI(int32_t idx, id i); + +FOUNDATION_EXPORT BOOL GoTestpkgReturnsError(BOOL b, NSString** ret0_, NSError** error); + +FOUNDATION_EXPORT int64_t GoTestpkgSum(int64_t x, int64_t y); + +FOUNDATION_EXPORT void GoTestpkgUnregisterI(int32_t idx); + +#endif diff --git a/src/golang.org/x/mobile/bind/objc/testpkg/objc_testpkg/GoTestpkg.m b/src/golang.org/x/mobile/bind/objc/testpkg/objc_testpkg/GoTestpkg.m new file mode 100644 index 0000000000..45c71c7ee9 --- /dev/null +++ b/src/golang.org/x/mobile/bind/objc/testpkg/objc_testpkg/GoTestpkg.m @@ -0,0 +1,341 @@ +// Objective-C API for talking to golang.org/x/mobile/bind/objc/testpkg Go package. +// gobind -lang=objc golang.org/x/mobile/bind/objc/testpkg +// +// File is generated by gobind. Do not edit. + +#include "GoTestpkg.h" +#include +#include "seq.h" + +static NSString* errDomain = @"go.golang.org/x/mobile/bind/objc/testpkg"; + +@protocol goSeqRefInterface +-(GoSeqRef*) ref; +@end + +#define _DESCRIPTOR_ "testpkg" + +#define _CALL_BytesAppend_ 1 +#define _CALL_CallSSum_ 2 +#define _CALL_CollectS_ 3 +#define _CALL_GC_ 4 +#define _CALL_Hello_ 5 +#define _CALL_Hi_ 6 +#define _CALL_Int_ 7 +#define _CALL_Multiply_ 8 +#define _CALL_NewI_ 9 +#define _CALL_NewS_ 10 +#define _CALL_RegisterI_ 11 +#define _CALL_ReturnsError_ 12 +#define _CALL_Sum_ 13 +#define _CALL_UnregisterI_ 14 + +#define _GO_testpkg_I_DESCRIPTOR_ "go.testpkg.I" +#define _GO_testpkg_I_Times_ (0x10a) + +@interface GoTestpkgI : NSObject { +} +@property(strong, readonly) id ref; + +- (id)initWithRef:(id)ref; +- (int64_t)Times:(int32_t)v; +@end + +@implementation GoTestpkgI { +} + +- (id)initWithRef:(id)ref { + self = [super init]; + if (self) { _ref = ref; } + return self; +} + +- (int64_t)Times:(int32_t)v { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, self.ref); + go_seq_writeInt32(&in_, v); + go_seq_send(_GO_testpkg_I_DESCRIPTOR_, _GO_testpkg_I_Times_, &in_, &out_); + int64_t ret0_ = go_seq_readInt64(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + +@end + +static void proxyGoTestpkgI(id obj, int code, GoSeq* in, GoSeq* out) { + switch (code) { + case _GO_testpkg_I_Times_: { + id o = (id)(obj); + int32_t v = go_seq_readInt32(in); + int64_t returnVal = [o Times:v]; + go_seq_writeInt64(out, returnVal); + } break; + default: + NSLog(@"unknown code %x for _GO_testpkg_I_DESCRIPTOR_", code); + } +} + +#define _GO_testpkg_S_DESCRIPTOR_ "go.testpkg.S" +#define _GO_testpkg_S_FIELD_X_GET_ (0x00f) +#define _GO_testpkg_S_FIELD_X_SET_ (0x01f) +#define _GO_testpkg_S_FIELD_Y_GET_ (0x10f) +#define _GO_testpkg_S_FIELD_Y_SET_ (0x11f) +#define _GO_testpkg_S_Sum_ (0x00c) +#define _GO_testpkg_S_TryTwoStrings_ (0x10c) + +@implementation GoTestpkgS { +} + +- (id)initWithRef:(id)ref { + self = [super init]; + if (self) { _ref = ref; } + return self; +} + +- (double)X { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, self.ref); + go_seq_send(_GO_testpkg_S_DESCRIPTOR_, _GO_testpkg_S_FIELD_X_GET_, &in_, &out_); + double ret_ = go_seq_readFloat64(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret_; +} + +- (void)setX:(double)v { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, self.ref); + go_seq_writeFloat64(&in_, v); + go_seq_send(_GO_testpkg_S_DESCRIPTOR_, _GO_testpkg_S_FIELD_X_SET_, &in_, &out_); + go_seq_free(&in_); + go_seq_free(&out_); +} + +- (double)Y { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, self.ref); + go_seq_send(_GO_testpkg_S_DESCRIPTOR_, _GO_testpkg_S_FIELD_Y_GET_, &in_, &out_); + double ret_ = go_seq_readFloat64(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret_; +} + +- (void)setY:(double)v { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, self.ref); + go_seq_writeFloat64(&in_, v); + go_seq_send(_GO_testpkg_S_DESCRIPTOR_, _GO_testpkg_S_FIELD_Y_SET_, &in_, &out_); + go_seq_free(&in_); + go_seq_free(&out_); +} + +- (double)Sum { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, self.ref); + go_seq_send(_GO_testpkg_S_DESCRIPTOR_, _GO_testpkg_S_Sum_, &in_, &out_); + double ret0_ = go_seq_readFloat64(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + +- (NSString*)TryTwoStrings:(NSString*)first second:(NSString*)second { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, self.ref); + go_seq_writeUTF8(&in_, first); + go_seq_writeUTF8(&in_, second); + go_seq_send(_GO_testpkg_S_DESCRIPTOR_, _GO_testpkg_S_TryTwoStrings_, &in_, &out_); + NSString* ret0_ = go_seq_readUTF8(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + +@end + +NSData* GoTestpkgBytesAppend(NSData* a, NSData* b) { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeByteArray(&in_, a); + go_seq_writeByteArray(&in_, b); + go_seq_send(_DESCRIPTOR_, _CALL_BytesAppend_, &in_, &out_); + NSData* ret0_ = go_seq_readByteArray(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + +double GoTestpkgCallSSum(GoTestpkgS* s) { + GoSeq in_ = {}; + GoSeq out_ = {}; + if ([(id)(s) isKindOfClass:[GoTestpkgS class]]) { + id s_proxy = (id)(s); + go_seq_writeRef(&in_, s_proxy.ref); + } else { + go_seq_writeObjcRef(&in_, s); + } + go_seq_send(_DESCRIPTOR_, _CALL_CallSSum_, &in_, &out_); + double ret0_ = go_seq_readFloat64(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + +int GoTestpkgCollectS(int want, int timeoutSec) { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeInt(&in_, want); + go_seq_writeInt(&in_, timeoutSec); + go_seq_send(_DESCRIPTOR_, _CALL_CollectS_, &in_, &out_); + int ret0_ = go_seq_readInt(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + +void GoTestpkgGC() { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_send(_DESCRIPTOR_, _CALL_GC_, &in_, &out_); + go_seq_free(&in_); + go_seq_free(&out_); +} + +NSString* GoTestpkgHello(NSString* s) { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeUTF8(&in_, s); + go_seq_send(_DESCRIPTOR_, _CALL_Hello_, &in_, &out_); + NSString* ret0_ = go_seq_readUTF8(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + +void GoTestpkgHi() { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_send(_DESCRIPTOR_, _CALL_Hi_, &in_, &out_); + go_seq_free(&in_); + go_seq_free(&out_); +} + +void GoTestpkgInt(int32_t x) { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeInt32(&in_, x); + go_seq_send(_DESCRIPTOR_, _CALL_Int_, &in_, &out_); + go_seq_free(&in_); + go_seq_free(&out_); +} + +int64_t GoTestpkgMultiply(int32_t idx, int32_t val) { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeInt32(&in_, idx); + go_seq_writeInt32(&in_, val); + go_seq_send(_DESCRIPTOR_, _CALL_Multiply_, &in_, &out_); + int64_t ret0_ = go_seq_readInt64(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + +id GoTestpkgNewI() { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_send(_DESCRIPTOR_, _CALL_NewI_, &in_, &out_); + GoSeqRef* ret0__ref = go_seq_readRef(&out_); + id ret0_ = ret0__ref.obj; + if (ret0_ == NULL) { + ret0_ = [[GoTestpkgI alloc] initWithRef:ret0__ref]; + } + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + +GoTestpkgS* GoTestpkgNewS(double x, double y) { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeFloat64(&in_, x); + go_seq_writeFloat64(&in_, y); + go_seq_send(_DESCRIPTOR_, _CALL_NewS_, &in_, &out_); + GoSeqRef* ret0__ref = go_seq_readRef(&out_); + GoTestpkgS* ret0_ = ret0__ref.obj; + if (ret0_ == NULL) { + ret0_ = [[GoTestpkgS alloc] initWithRef:ret0__ref]; + } + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + +void GoTestpkgRegisterI(int32_t idx, id i) { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeInt32(&in_, idx); + if ([(id)(i) isKindOfClass:[GoTestpkgI class]]) { + id i_proxy = (id)(i); + go_seq_writeRef(&in_, i_proxy.ref); + } else { + go_seq_writeObjcRef(&in_, i); + } + go_seq_send(_DESCRIPTOR_, _CALL_RegisterI_, &in_, &out_); + go_seq_free(&in_); + go_seq_free(&out_); +} + +BOOL GoTestpkgReturnsError(BOOL b, NSString** ret0_, NSError** error) { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeBool(&in_, b); + go_seq_send(_DESCRIPTOR_, _CALL_ReturnsError_, &in_, &out_); + NSString* ret0__val = go_seq_readUTF8(&out_); + if (ret0_ != NULL) { + *ret0_ = ret0__val; + } + NSString* _error = go_seq_readUTF8(&out_); + if ([_error length] != 0 && error != nil) { + NSMutableDictionary* details = [NSMutableDictionary dictionary]; + [details setValue:_error forKey:NSLocalizedDescriptionKey]; + *error = [NSError errorWithDomain:errDomain code:1 userInfo:details]; + } + go_seq_free(&in_); + go_seq_free(&out_); + return ([_error length] == 0); +} + +int64_t GoTestpkgSum(int64_t x, int64_t y) { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeInt64(&in_, x); + go_seq_writeInt64(&in_, y); + go_seq_send(_DESCRIPTOR_, _CALL_Sum_, &in_, &out_); + int64_t ret0_ = go_seq_readInt64(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + +void GoTestpkgUnregisterI(int32_t idx) { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeInt32(&in_, idx); + go_seq_send(_DESCRIPTOR_, _CALL_UnregisterI_, &in_, &out_); + go_seq_free(&in_); + go_seq_free(&out_); +} + +__attribute__((constructor)) static void init() { + go_seq_register_proxy("go.testpkg.I", proxyGoTestpkgI); +} diff --git a/src/golang.org/x/mobile/bind/objc/testpkg/objc_testpkg/reference/GoTestpkg.h b/src/golang.org/x/mobile/bind/objc/testpkg/objc_testpkg/reference/GoTestpkg.h new file mode 100644 index 0000000000..08227f4225 --- /dev/null +++ b/src/golang.org/x/mobile/bind/objc/testpkg/objc_testpkg/reference/GoTestpkg.h @@ -0,0 +1,57 @@ +// Objective-C API for talking to golang.org/x/mobile/bind/objc/testpkg Go package. +// gobind -lang=objc golang.org/x/mobile/bind/objc/testpkg +// +// File is generated by gobind. Do not edit. + +#ifndef __GoTestpkg_H__ +#define __GoTestpkg_H__ + +#include + +@protocol GoTestpkgI +- (void)Fn:(int32_t)v; +@end + +@interface GoTestpkgI : NSObject { +} +@property (strong, readonly) id ref; +@property (weak, readonly, nonatomic) id delegate; + +- (void)call:(int)code in:(void *)in out:(void *)out; +@end + +@class GoTestpkgS; + +@interface GoTestpkgS : NSObject { +} +@property(strong, readonly) id ref; + +- (id)initWithRef:(id)ref; +- (double)X; +- (void)setX:(double)v; +- (double)Y; +- (void)setY:(double)v; +- (double)Sum; +@end + +FOUNDATION_EXPORT NSData* GoTestpkgBytesAppend(NSData* a, NSData* b); + +FOUNDATION_EXPORT void GoTestpkgCallI(GoTestpkgI* i, int32_t v); + +FOUNDATION_EXPORT double GoTestpkgCallSSum(GoTestpkgS* s); + +FOUNDATION_EXPORT int GoTestpkgCollectS(int want, int timeoutSec); + +FOUNDATION_EXPORT NSString* GoTestpkgHello(NSString* s); + +FOUNDATION_EXPORT void GoTestpkgHi(); + +FOUNDATION_EXPORT void GoTestpkgInt(int32_t x); + +FOUNDATION_EXPORT GoTestpkgS* GoTestpkgNewS(double x, double y); + +FOUNDATION_EXPORT BOOL GoTestpkgReturnsError(BOOL b, NSString** ret0_, NSError** error); + +FOUNDATION_EXPORT int64_t GoTestpkgSum(int64_t x, int64_t y); + +#endif diff --git a/src/golang.org/x/mobile/bind/objc/testpkg/objc_testpkg/reference/GoTestpkg.m b/src/golang.org/x/mobile/bind/objc/testpkg/objc_testpkg/reference/GoTestpkg.m new file mode 100644 index 0000000000..dedf816bb0 --- /dev/null +++ b/src/golang.org/x/mobile/bind/objc/testpkg/objc_testpkg/reference/GoTestpkg.m @@ -0,0 +1,249 @@ +// Objective-C API for talking to golang.org/x/mobile/bind/objc/testpkg Go package. +// gobind -lang=objc golang.org/x/mobile/bind/objc/testpkg +// +// File is generated by gobind. Do not edit. + +#include "GoTestpkg.h" +#include +#include "seq.h" + +static NSString *errDomain = @"go.golang.org/x/mobile/bind/objc/testpkg"; + +#define _DESCRIPTOR_ "testpkg" + +#define _CALL_BytesAppend_ 1 +#define _CALL_CallI_ 2 +#define _CALL_CallSSum_ 3 +#define _CALL_CollectS_ 4 +#define _CALL_Hello_ 5 +#define _CALL_Hi_ 6 +#define _CALL_Int_ 7 +#define _CALL_NewS_ 8 +#define _CALL_ReturnsError_ 9 +#define _CALL_Sum_ 10 + +#define _GO_testpkg_S_DESCRIPTOR_ "go.testpkg.S" +#define _GO_testpkg_S_FIELD_X_GET_ (0x00f) +#define _GO_testpkg_S_FIELD_X_SET_ (0x01f) +#define _GO_testpkg_S_FIELD_Y_GET_ (0x10f) +#define _GO_testpkg_S_FIELD_Y_SET_ (0x11f) +#define _GO_testpkg_S_Sum_ (0x00c) + +#define _GO_testpkg_I_DESCRIPTOR_ "go.testpkg.I" +#define _GO_testpkg_I_Fn_ 0x10a + +@implementation GoTestpkgI { +} +@synthesize delegate = _delegate; +@synthesize ref = _ref; + +- (id)init { + self = [super init]; + if (self) { + _delegate = self; + _ref = [GoSeqRef newForObject:self]; + } + return self; +} + +- (void)call:(int)code in:(void *)in out:(void *)out { + GoSeq* inseq = (GoSeq*)in; + GoSeq* outseq = (GoSeq*)out; + switch (code) { + case _GO_testpkg_I_Fn_: + { + int32_t v = go_seq_readInt32(inseq); + [_delegate Fn:v]; + return; + } + default: + NSLog(@"unknown code %s:%d", _GO_testpkg_I_DESCRIPTOR_, code); + } +} + +@end + +@implementation GoTestpkgS { +} + +- (id)initWithRef:(id)ref { + self = [super init]; + if (self) { _ref = ref; } + return self; +} + +- (double)X { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, self.ref); + go_seq_send(_GO_testpkg_S_DESCRIPTOR_, _GO_testpkg_S_FIELD_X_GET_, &in_, &out_); + double ret_ = go_seq_readFloat64(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret_; +} + +- (void)setX:(double)v { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, self.ref); + go_seq_writeFloat64(&in_, v); + go_seq_send(_GO_testpkg_S_DESCRIPTOR_, _GO_testpkg_S_FIELD_X_SET_, &in_, &out_); + go_seq_free(&in_); + go_seq_free(&out_); +} + +- (double)Y { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, self.ref); + go_seq_send(_GO_testpkg_S_DESCRIPTOR_, _GO_testpkg_S_FIELD_Y_GET_, &in_, &out_); + double ret_ = go_seq_readFloat64(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret_; +} + +- (void)setY:(double)v { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, self.ref); + go_seq_writeFloat64(&in_, v); + go_seq_send(_GO_testpkg_S_DESCRIPTOR_, _GO_testpkg_S_FIELD_Y_SET_, &in_, &out_); + go_seq_free(&in_); + go_seq_free(&out_); +} + +- (double)Sum { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, self.ref); + go_seq_send(_GO_testpkg_S_DESCRIPTOR_, _GO_testpkg_S_Sum_, &in_, &out_); + double ret0_ = go_seq_readFloat64(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + +@end + +NSData* GoTestpkgBytesAppend(NSData* a, NSData* b) { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeByteArray(&in_, a); + go_seq_writeByteArray(&in_, b); + go_seq_send(_DESCRIPTOR_, _CALL_BytesAppend_, &in_, &out_); + NSData* ret0_ = go_seq_readByteArray(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + +void GoTestpkgCallI(GoTestpkgI* i, int32_t v) { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, i.ref); + go_seq_writeInt32(&in_, v); + go_seq_send(_DESCRIPTOR_, _CALL_CallI_, &in_, &out_); + return; +} + +double GoTestpkgCallSSum(GoTestpkgS* s) { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, s.ref); + go_seq_send(_DESCRIPTOR_, _CALL_CallSSum_, &in_, &out_); + double ret0_ = go_seq_readFloat64(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + +int GoTestpkgCollectS(int want, int timeoutSec) { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeInt(&in_, want); + go_seq_writeInt(&in_, timeoutSec); + go_seq_send(_DESCRIPTOR_, _CALL_CollectS_, &in_, &out_); + int ret0_ = go_seq_readInt(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + +NSString* GoTestpkgHello(NSString* s) { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeUTF8(&in_, s); + go_seq_send(_DESCRIPTOR_, _CALL_Hello_, &in_, &out_); + NSString* ret0_ = go_seq_readUTF8(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + +void GoTestpkgHi() { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_send(_DESCRIPTOR_, _CALL_Hi_, &in_, &out_); + go_seq_free(&in_); + go_seq_free(&out_); +} + +void GoTestpkgInt(int32_t x) { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeInt32(&in_, x); + go_seq_send(_DESCRIPTOR_, _CALL_Int_, &in_, &out_); + go_seq_free(&in_); + go_seq_free(&out_); +} + +GoTestpkgS* GoTestpkgNewS(double x, double y) { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeFloat64(&in_, x); + go_seq_writeFloat64(&in_, y); + go_seq_send(_DESCRIPTOR_, _CALL_NewS_, &in_, &out_); + GoSeqRef* ret0__ref = go_seq_readRef(&out_); + GoTestpkgS* ret0_ = ret0__ref.obj; + if (ret0_ == NULL) { + ret0_ = [[GoTestpkgS alloc] initWithRef:ret0__ref]; + } + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + +BOOL GoTestpkgReturnsError(BOOL b, NSString** ret0_, NSError** error) { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeBool(&in_, b); + go_seq_send(_DESCRIPTOR_, _CALL_ReturnsError_, &in_, &out_); + NSString* ret0__val = go_seq_readUTF8(&out_); + if (ret0_ != NULL) { + *ret0_ = ret0__val; + } + NSString* _error = go_seq_readUTF8(&out_); + if ([_error length] != 0 && error != nil) { + NSMutableDictionary *details = [NSMutableDictionary dictionary]; + [details setValue:_error forKey:NSLocalizedDescriptionKey]; + *error = [NSError errorWithDomain:errDomain code:1 userInfo:details]; + } + go_seq_free(&in_); + go_seq_free(&out_); + return ([_error length] == 0); +} + +int64_t GoTestpkgSum(int64_t x, int64_t y) { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeInt64(&in_, x); + go_seq_writeInt64(&in_, y); + go_seq_send(_DESCRIPTOR_, _CALL_Sum_, &in_, &out_); + int64_t ret0_ = go_seq_readInt64(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + diff --git a/src/golang.org/x/mobile/bind/objc/testpkg/testpkg.go b/src/golang.org/x/mobile/bind/objc/testpkg/testpkg.go new file mode 100644 index 0000000000..15d2c15e68 --- /dev/null +++ b/src/golang.org/x/mobile/bind/objc/testpkg/testpkg.go @@ -0,0 +1,124 @@ +// Copyright 2015 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. + +package testpkg + +//go:generate gobind -lang=go -outdir=go_testpkg . +//go:generate gobind -lang=objc -outdir=objc_testpkg . + +import ( + "errors" + "fmt" + "runtime" + "time" +) + +type I interface { + Times(v int32) int64 +} + +type myI struct{} + +func (i *myI) Times(v int32) int64 { + return int64(v) * 10 +} + +func NewI() I { + return &myI{} +} + +var pinnedI = make(map[int32]I) + +func RegisterI(idx int32, i I) { + pinnedI[idx] = i +} + +func UnregisterI(idx int32) { + delete(pinnedI, idx) +} + +func Multiply(idx int32, val int32) int64 { + i, ok := pinnedI[idx] + if !ok { + panic(fmt.Sprintf("unknown I with index %d", i)) + } + return i.Times(val) +} + +func GC() { + runtime.GC() +} + +func Hi() { + fmt.Println("Hi") +} + +func Int(x int32) { + fmt.Println("Received int32", x) +} + +func Sum(x, y int64) int64 { + return x + y +} + +func Hello(s string) string { + return fmt.Sprintf("Hello, %s!", s) +} + +func BytesAppend(a []byte, b []byte) []byte { + return append(a, b...) +} + +func ReturnsError(b bool) (string, error) { + if b { + return "", errors.New("Error") + } + return "OK", nil +} + +var collectS = make(chan struct{}, 100) + +func finalizeS(a *S) { + collectS <- struct{}{} +} + +func CollectS(want, timeoutSec int) int { + runtime.GC() + + tick := time.NewTicker(time.Duration(timeoutSec) * time.Second) + defer tick.Stop() + + for i := 0; i < want; i++ { + select { + case <-collectS: + case <-tick.C: + fmt.Println("CollectS: timed out") + return i + } + } + return want +} + +type S struct { + X, Y float64 + unexported bool +} + +func NewS(x, y float64) *S { + s := &S{X: x, Y: y} + runtime.SetFinalizer(s, finalizeS) + return s +} + +func (s *S) TryTwoStrings(first, second string) string { + return first + second +} + +func (s *S) Sum() float64 { + return s.X + s.Y +} + +func CallSSum(s *S) float64 { + return s.Sum() +} diff --git a/src/golang.org/x/mobile/bind/printer.go b/src/golang.org/x/mobile/bind/printer.go new file mode 100644 index 0000000000..39cb809678 --- /dev/null +++ b/src/golang.org/x/mobile/bind/printer.go @@ -0,0 +1,67 @@ +// Copyright 2014 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. + +package bind + +import ( + "bytes" + "fmt" +) + +type printer struct { + buf *bytes.Buffer + indentEach []byte + indentText []byte + needIndent bool +} + +func (p *printer) writeIndent() error { + if !p.needIndent { + return nil + } + p.needIndent = false + _, err := p.buf.Write(p.indentText) + return err +} + +func (p *printer) Write(b []byte) (n int, err error) { + wrote := 0 + for len(b) > 0 { + if err := p.writeIndent(); err != nil { + return wrote, err + } + i := bytes.IndexByte(b, '\n') + if i < 0 { + break + } + n, err = p.buf.Write(b[0 : i+1]) + wrote += n + if err != nil { + return wrote, err + } + b = b[i+1:] + p.needIndent = true + } + if len(b) > 0 { + n, err = p.buf.Write(b) + wrote += n + } + return wrote, err +} + +func (p *printer) Printf(format string, args ...interface{}) { + if _, err := fmt.Fprintf(p, format, args...); err != nil { + panic(fmt.Sprintf("printer: %v", err)) + } +} + +func (p *printer) Indent() { + p.indentText = append(p.indentText, p.indentEach...) +} + +func (p *printer) Outdent() { + if len(p.indentText) > len(p.indentEach)-1 { + p.indentText = p.indentText[len(p.indentEach):] + } +} diff --git a/src/golang.org/x/mobile/bind/seq.go b/src/golang.org/x/mobile/bind/seq.go new file mode 100644 index 0000000000..05e4d24287 --- /dev/null +++ b/src/golang.org/x/mobile/bind/seq.go @@ -0,0 +1,87 @@ +package bind + +import ( + "fmt" + "go/types" +) + +// seqType returns a string that can be used for reading and writing a +// type using the seq library. +// TODO(hyangah): avoid panic; gobind needs to output the problematic code location. +func seqType(t types.Type) string { + if isErrorType(t) { + return "String" + } + switch t := t.(type) { + case *types.Basic: + switch t.Kind() { + case types.Bool: + return "Bool" + case types.Int: + return "Int" + case types.Int8: + return "Int8" + case types.Int16: + return "Int16" + case types.Int32: + return "Int32" + case types.Int64: + return "Int64" + case types.Uint8: // Byte. + // TODO(crawshaw): questionable, but vital? + return "Byte" + // TODO(crawshaw): case types.Uint, types.Uint16, types.Uint32, types.Uint64: + case types.Float32: + return "Float32" + case types.Float64: + return "Float64" + case types.String: + return "String" + default: + // Should be caught earlier in processing. + panic(fmt.Sprintf("unsupported basic seqType: %s", t)) + } + case *types.Named: + switch u := t.Underlying().(type) { + case *types.Interface: + return "Ref" + default: + panic(fmt.Sprintf("unsupported named seqType: %s / %T", u, u)) + } + case *types.Slice: + switch e := t.Elem().(type) { + case *types.Basic: + switch e.Kind() { + case types.Uint8: // Byte. + return "ByteArray" + default: + panic(fmt.Sprintf("unsupported seqType: %s(%s) / %T(%T)", t, e, t, e)) + } + default: + panic(fmt.Sprintf("unsupported seqType: %s(%s) / %T(%T)", t, e, t, e)) + } + // TODO: let the types.Array case handled like types.Slice? + case *types.Pointer: + if _, ok := t.Elem().(*types.Named); ok { + return "Ref" + } + panic(fmt.Sprintf("not supported yet, pointer type: %s / %T", t, t)) + + default: + panic(fmt.Sprintf("unsupported seqType: %s / %T", t, t)) + } +} + +func seqRead(o types.Type) string { + t := seqType(o) + return t + "()" +} + +func seqWrite(o types.Type, name string) string { + t := seqType(o) + if t == "Ref" { + // TODO(crawshaw): do something cleaner, i.e. genWrite. + return t + "(" + name + ".ref())" + } + return t + "(" + name + ")" +} diff --git a/src/golang.org/x/mobile/bind/seq/buffer.go b/src/golang.org/x/mobile/bind/seq/buffer.go new file mode 100644 index 0000000000..45f40cae63 --- /dev/null +++ b/src/golang.org/x/mobile/bind/seq/buffer.go @@ -0,0 +1,268 @@ +// Copyright 2014 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. + +package seq + +import ( + "bytes" + "fmt" + "runtime" + "unsafe" +) + +// Buffer is a set of arguments or return values from a function call +// across the language boundary. Encoding is machine-dependent. +type Buffer struct { + Data []byte + Offset int // position of next read/write from Data +} + +func (b *Buffer) String() string { + // Debugging. + var buf bytes.Buffer + fmt.Fprintf(&buf, "seq{Off=%d, Len=%d Data=", b.Offset, len(b.Data)) + const hextable = "0123456789abcdef" + for i, v := range b.Data { + if i > 0 { + buf.WriteByte(':') + } + buf.WriteByte(hextable[v>>4]) + buf.WriteByte(hextable[v&0x0f]) + } + buf.WriteByte('}') + return buf.String() +} + +func (b *Buffer) panic(need int) { + panic(fmt.Sprintf("need %d bytes: %s", need, b)) +} + +func (b *Buffer) grow(need int) { + size := len(b.Data) + if size == 0 { + size = 2 + } + for size < need { + size *= 2 + } + data := make([]byte, size+len(b.Data)) + copy(data, b.Data[:b.Offset]) + b.Data = data +} + +// align returns the aligned offset. +func align(offset, alignment int) int { + pad := offset % alignment + if pad > 0 { + pad = alignment - pad + } + return pad + offset +} + +func (b *Buffer) ReadInt8() int8 { + offset := b.Offset + if len(b.Data)-offset < 1 { + b.panic(1) + } + v := *(*int8)(unsafe.Pointer(&b.Data[offset])) + b.Offset++ + return v +} + +func (b *Buffer) ReadInt16() int16 { + offset := align(b.Offset, 2) + if len(b.Data)-offset < 2 { + b.panic(2) + } + v := *(*int16)(unsafe.Pointer(&b.Data[offset])) + b.Offset = offset + 2 + return v +} + +func (b *Buffer) ReadInt32() int32 { + offset := align(b.Offset, 4) + if len(b.Data)-offset < 4 { + b.panic(4) + } + v := *(*int32)(unsafe.Pointer(&b.Data[offset])) + b.Offset = offset + 4 + return v +} + +func (b *Buffer) ReadInt64() int64 { + offset := align(b.Offset, 8) + if len(b.Data)-offset < 8 { + b.panic(8) + } + v := *(*int64)(unsafe.Pointer(&b.Data[offset])) + b.Offset = offset + 8 + return v +} + +func (b *Buffer) ReadBool() bool { + return b.ReadInt8() != 0 +} + +func (b *Buffer) ReadInt() int { + return int(b.ReadInt64()) +} + +func (b *Buffer) ReadFloat32() float32 { + offset := align(b.Offset, 4) + if len(b.Data)-offset < 4 { + b.panic(4) + } + v := *(*float32)(unsafe.Pointer(&b.Data[offset])) + b.Offset = offset + 4 + return v +} + +func (b *Buffer) ReadFloat64() float64 { + offset := align(b.Offset, 8) + if len(b.Data)-offset < 8 { + b.panic(8) + } + v := *(*float64)(unsafe.Pointer(&b.Data[offset])) + b.Offset = offset + 8 + return v +} + +func (b *Buffer) ReadByteArray() []byte { + sz := b.ReadInt64() + if sz == 0 { + return nil + } + + ptr := b.ReadInt64() + org := (*[1 << 30]byte)(unsafe.Pointer(uintptr(ptr)))[:sz] + + // Make a copy managed by Go, so the returned byte array can be + // used safely in Go. + slice := make([]byte, sz) + copy(slice, org) + return slice +} + +func (b *Buffer) ReadRef() *Ref { + ref := &Ref{b.ReadInt32()} + if ref.Num > 0 { + // This is a foreign object reference. + // Track its lifetime with a finalizer. + runtime.SetFinalizer(ref, FinalizeRef) + } + return ref +} + +func (b *Buffer) ReadString() string { + return DecString(b) +} + +func (b *Buffer) WriteInt8(v int8) { + offset := b.Offset + if len(b.Data)-offset < 1 { + b.grow(offset + 1 - len(b.Data)) + } + *(*int8)(unsafe.Pointer(&b.Data[offset])) = v + b.Offset++ +} + +func (b *Buffer) WriteInt16(v int16) { + offset := align(b.Offset, 2) + if len(b.Data)-offset < 2 { + b.grow(offset + 2 - len(b.Data)) + } + *(*int16)(unsafe.Pointer(&b.Data[offset])) = v + b.Offset = offset + 2 +} + +func (b *Buffer) WriteInt32(v int32) { + offset := align(b.Offset, 4) + if len(b.Data)-offset < 4 { + b.grow(offset + 4 - len(b.Data)) + } + *(*int32)(unsafe.Pointer(&b.Data[offset])) = v + b.Offset = offset + 4 +} + +func (b *Buffer) WriteInt64(v int64) { + offset := align(b.Offset, 8) + if len(b.Data)-offset < 8 { + b.grow(offset + 8 - len(b.Data)) + } + *(*int64)(unsafe.Pointer(&b.Data[offset])) = v + b.Offset = offset + 8 +} + +func (b *Buffer) WriteBool(v bool) { + if v { + b.WriteInt8(1) + } else { + b.WriteInt8(0) + } +} + +func (b *Buffer) WriteInt(v int) { + b.WriteInt64(int64(v)) +} + +func (b *Buffer) WriteFloat32(v float32) { + offset := align(b.Offset, 4) + if len(b.Data)-offset < 4 { + b.grow(offset + 4 - len(b.Data)) + } + *(*float32)(unsafe.Pointer(&b.Data[offset])) = v + b.Offset = offset + 4 +} + +func (b *Buffer) WriteFloat64(v float64) { + offset := align(b.Offset, 8) + if len(b.Data)-offset < 8 { + b.grow(offset + 8 - len(b.Data)) + } + *(*float64)(unsafe.Pointer(&b.Data[offset])) = v + b.Offset = offset + 8 +} + +func (b *Buffer) WriteByteArray(byt []byte) { + sz := len(byt) + if sz == 0 { + b.WriteInt64(int64(sz)) + return + } + + ptr := uintptr(unsafe.Pointer(&byt[0])) + b.WriteInt64(int64(sz)) + b.WriteInt64(int64(ptr)) + return +} + +func (b *Buffer) WriteString(v string) { + EncString(b, v) +} + +func (b *Buffer) WriteGoRef(obj interface{}) { + refs.Lock() + num := refs.refs[obj] + if num != 0 { + s := refs.objs[num] + refs.objs[num] = countedObj{s.obj, s.cnt + 1} + } else { + num = refs.next + refs.next-- + if refs.next > 0 { + panic("refs.next underflow") + } + refs.refs[obj] = num + refs.objs[num] = countedObj{obj, 1} + } + refs.Unlock() + + b.WriteInt32(int32(num)) +} + +/* TODO: Will we need it? +func (b *Buffer) WriteRef(ref *Ref) { + b.WriteInt32(ref.Num) +} +*/ diff --git a/src/golang.org/x/mobile/bind/seq/ref.go b/src/golang.org/x/mobile/bind/seq/ref.go new file mode 100644 index 0000000000..2eed8685a2 --- /dev/null +++ b/src/golang.org/x/mobile/bind/seq/ref.go @@ -0,0 +1,70 @@ +// Copyright 2014 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. + +package seq + +//#cgo LDFLAGS: -llog +//#include +//#include +//import "C" + +import ( + "fmt" + "sync" +) + +type countedObj struct { + obj interface{} + cnt int32 +} + +// refs stores Go objects that have been passed to another language. +var refs struct { + sync.Mutex + next int32 // next reference number to use for Go object, always negative + refs map[interface{}]int32 + objs map[int32]countedObj +} + +func init() { + refs.Lock() + refs.next = -24 // Go objects get negative reference numbers. Arbitrary starting point. + refs.refs = make(map[interface{}]int32) + refs.objs = make(map[int32]countedObj) + refs.Unlock() +} + +// A Ref represents a Java or Go object passed across the language +// boundary. +type Ref struct { + Num int32 +} + +// Get returns the underlying object. +func (r *Ref) Get() interface{} { + refs.Lock() + o, ok := refs.objs[r.Num] + refs.Unlock() + if !ok { + panic(fmt.Sprintf("unknown ref %d", r.Num)) + } + return o.obj +} + +// Delete decrements the reference count and removes the pinned object +// from the object map when the reference count becomes zero. +func Delete(num int32) { + refs.Lock() + defer refs.Unlock() + o, ok := refs.objs[num] + if !ok { + panic(fmt.Sprintf("seq.Delete unknown refnum: %d", num)) + } + if o.cnt <= 1 { + delete(refs.objs, num) + delete(refs.refs, o.obj) + } else { + refs.objs[num] = countedObj{o.obj, o.cnt - 1} + } +} diff --git a/src/golang.org/x/mobile/bind/seq/seq.go b/src/golang.org/x/mobile/bind/seq/seq.go new file mode 100644 index 0000000000..21f30931fb --- /dev/null +++ b/src/golang.org/x/mobile/bind/seq/seq.go @@ -0,0 +1,56 @@ +// Copyright 2014 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. + +// Package seq implements the machine-dependent seq serialization format. +// +// Implementations of Transact and FinalizeRef are provided by a +// specific foreign language binding package, e.g. go.mobile/bind/java. +// +// Designed only for use by the code generated by gobind. Don't try to +// use this directly. +package seq // import "golang.org/x/mobile/bind/seq" + +// TODO(crawshaw): +// There is opportunity for optimizing these language +// bindings which requires deconstructing seq into something +// gnarly. So don't get too attached to the design. + +import ( + "fmt" + + _ "golang.org/x/mobile/internal/mobileinit" +) + +// Transact calls a method on a foreign object instance. +// It blocks until the call is complete. +var Transact func(ref *Ref, desc string, code int, in *Buffer) (out *Buffer) + +// FinalizeRef is the finalizer used on foreign objects. +var FinalizeRef func(ref *Ref) + +// A Func can be registered and called by a foreign language. +type Func func(out, in *Buffer) + +// Registry holds functions callable from gobind generated bindings. +// Functions are keyed by descriptor and function code. +var Registry = make(map[string]map[int]Func) + +// Register registers a function in the Registry. +func Register(descriptor string, code int, fn Func) { + m := Registry[descriptor] + if m == nil { + m = make(map[int]Func) + Registry[descriptor] = m + } + if m[code] != nil { + panic(fmt.Sprintf("registry.Register: %q/%d already registered", descriptor, code)) + } + m[code] = fn +} + +// DecString decodes a string encoded in the Buffer. +var DecString func(in *Buffer) string + +// EncString encodes a Go string into the Buffer. +var EncString func(out *Buffer, v string) diff --git a/src/golang.org/x/mobile/bind/seq/seq_test.go b/src/golang.org/x/mobile/bind/seq/seq_test.go new file mode 100644 index 0000000000..cc24292735 --- /dev/null +++ b/src/golang.org/x/mobile/bind/seq/seq_test.go @@ -0,0 +1,36 @@ +// Copyright 2014 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. + +package seq + +import "testing" + +func TestBuffer(t *testing.T) { + buf := new(Buffer) + buf.WriteInt64(1 << 42) + buf.WriteInt32(1 << 13) + buf.WriteUTF16("Hello, world") + buf.WriteFloat64(4.02) + buf.WriteFloat32(1.2) + buf.WriteGoRef(new(int)) + buf.WriteGoRef(new(int)) + + buf.Offset = 0 + + if got, want := buf.ReadInt64(), int64(1<<42); got != want { + t.Errorf("buf.ReadInt64()=%d, want %d", got, want) + } + if got, want := buf.ReadInt32(), int32(1<<13); got != want { + t.Errorf("buf.ReadInt32()=%d, want %d", got, want) + } + if got, want := buf.ReadUTF16(), "Hello, world"; got != want { + t.Errorf("buf.ReadUTF16()=%q, want %q", got, want) + } + if got, want := buf.ReadFloat64(), 4.02; got != want { + t.Errorf("buf.ReadFloat64()=%f, want %f", got, want) + } + if got, want := buf.ReadFloat32(), float32(1.2); got != want { + t.Errorf("buf.ReadFloat32()=%f, want %f", got, want) + } +} diff --git a/src/golang.org/x/mobile/bind/seq/string.go b/src/golang.org/x/mobile/bind/seq/string.go new file mode 100644 index 0000000000..3354a4b0e3 --- /dev/null +++ b/src/golang.org/x/mobile/bind/seq/string.go @@ -0,0 +1,123 @@ +// Copyright 2014 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. + +package seq + +import ( + "errors" + "fmt" + "unicode/utf16" + "unsafe" +) + +// Based heavily on package unicode/utf16 from the Go standard library. + +const ( + replacementChar = '\uFFFD' // Unicode replacement character + maxRune = '\U0010FFFF' // Maximum valid Unicode code point. +) + +const ( + // 0xd800-0xdc00 encodes the high 10 bits of a pair. + // 0xdc00-0xe000 encodes the low 10 bits of a pair. + // the value is those 20 bits plus 0x10000. + surr1 = 0xd800 + surr2 = 0xdc00 + surr3 = 0xe000 + + surrSelf = 0x10000 +) + +func writeUint16(b []byte, v rune) { + *(*uint16)(unsafe.Pointer(&b[0])) = uint16(v) +} + +func (b *Buffer) WriteUTF16(s string) { + // The first 4 bytes is the length, as int32 (4-byte aligned). + // written last. + // The next n bytes is utf-16 string (1-byte aligned). + offset0 := align(b.Offset, 4) // length. + offset1 := align(offset0+4, 1) // contents. + + if len(b.Data)-offset1 < 4*len(s) { + // worst case estimate, everything is surrogate pair + b.grow(offset1 + 4*len(s) - len(b.Data)) + } + data := b.Data[offset1:] + n := 0 + for _, v := range s { + switch { + case v < 0, surr1 <= v && v < surr3, v > maxRune: + v = replacementChar + fallthrough + case v < surrSelf: + writeUint16(data[n:], v) + n += 2 + default: + // surrogate pair, two uint16 values + r1, r2 := utf16.EncodeRune(v) + writeUint16(data[n:], r1) + writeUint16(data[n+2:], r2) + n += 4 + } + } + + // write length at b.Data[b.Offset:], before contents. + // length is number of uint16 values, not number of bytes. + b.WriteInt32(int32(n / 2)) + + b.Offset = offset1 + n +} + +func (b *Buffer) WriteUTF8(s string) { + n := len(s) + b.WriteInt32(int32(n)) + if len(s) == 0 { + return + } + offset := align(b.Offset, 1) + if len(b.Data)-offset < n { + b.grow(offset + n - len(b.Data)) + } + copy(b.Data[offset:], s) + b.Offset = offset + n +} + +const maxSliceLen = (1<<31 - 1) / 2 + +func (b *Buffer) ReadError() error { + if s := b.ReadUTF16(); s != "" { + return errors.New(s) + } + return nil +} + +func (b *Buffer) ReadUTF16() string { + size := int(b.ReadInt32()) + if size == 0 { + return "" + } + if size < 0 { + panic(fmt.Sprintf("string size negative: %d", size)) + } + offset := align(b.Offset, 1) + u := (*[maxSliceLen]uint16)(unsafe.Pointer(&b.Data[offset]))[:size] + s := string(utf16.Decode(u)) // TODO: save the []rune alloc + b.Offset = offset + 2*size + + return s +} + +func (b *Buffer) ReadUTF8() string { + size := int(b.ReadInt32()) + if size == 0 { + return "" + } + if size < 0 { + panic(fmt.Sprintf("string size negative: %d", size)) + } + offset := align(b.Offset, 1) + b.Offset = offset + size + return string(b.Data[offset : offset+size]) +} diff --git a/src/golang.org/x/mobile/bind/seq/string_test.go b/src/golang.org/x/mobile/bind/seq/string_test.go new file mode 100644 index 0000000000..42e144e485 --- /dev/null +++ b/src/golang.org/x/mobile/bind/seq/string_test.go @@ -0,0 +1,51 @@ +// Copyright 2014 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. + +package seq + +import "testing" + +var strData = []string{ + "abcxyz09{}", + "Hello, 世界", + string([]rune{0xffff, 0x10000, 0x10001, 0x12345, 0x10ffff}), +} + +var stringEncoder = map[string]struct { + write func(*Buffer, string) + read func(*Buffer) string +}{ + "UTF16": {write: (*Buffer).WriteUTF16, read: (*Buffer).ReadUTF16}, + "UTF8": {write: (*Buffer).WriteUTF8, read: (*Buffer).ReadUTF8}, +} + +func TestString(t *testing.T) { + for encoding, f := range stringEncoder { + for _, test := range strData { + buf := new(Buffer) + f.write(buf, test) + buf.Offset = 0 + got := f.read(buf) + if got != test { + t.Errorf("%s: got %q, want %q", encoding, got, test) + } + } + } +} + +func TestSequential(t *testing.T) { + for encoding, f := range stringEncoder { + buf := new(Buffer) + for _, test := range strData { + f.write(buf, test) + } + buf.Offset = 0 + for i, test := range strData { + got := f.read(buf) + if got != test { + t.Errorf("%s: %d: got %q, want %q", encoding, i, got, test) + } + } + } +} diff --git a/src/golang.org/x/mobile/bind/testdata/basictypes.go b/src/golang.org/x/mobile/bind/testdata/basictypes.go new file mode 100644 index 0000000000..579fbf7eb3 --- /dev/null +++ b/src/golang.org/x/mobile/bind/testdata/basictypes.go @@ -0,0 +1,15 @@ +// Copyright 2014 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. + +package basictypes + +func Ints(x int8, y int16, z int32, t int64, u int) {} + +func Error() error { return nil } + +func ErrorPair() (int, error) { return 0, nil } + +func ByteArrays(x []byte) []byte { return nil } + +func Bool(bool) bool { return true } diff --git a/src/golang.org/x/mobile/bind/testdata/basictypes.go.golden b/src/golang.org/x/mobile/bind/testdata/basictypes.go.golden new file mode 100644 index 0000000000..f741dc3272 --- /dev/null +++ b/src/golang.org/x/mobile/bind/testdata/basictypes.go.golden @@ -0,0 +1,58 @@ +// Package go_basictypes is an autogenerated binder stub for package basictypes. +// gobind -lang=go basictypes +// +// File is generated by gobind. Do not edit. +package go_basictypes + +import ( + "basictypes" + "golang.org/x/mobile/bind/seq" +) + +func proxy_Bool(out, in *seq.Buffer) { + param_p0 := in.ReadBool() + res := basictypes.Bool(param_p0) + out.WriteBool(res) +} + +func proxy_ByteArrays(out, in *seq.Buffer) { + param_x := in.ReadByteArray() + res := basictypes.ByteArrays(param_x) + out.WriteByteArray(res) +} + +func proxy_Error(out, in *seq.Buffer) { + err := basictypes.Error() + if err == nil { + out.WriteString("") + } else { + out.WriteString(err.Error()) + } +} + +func proxy_ErrorPair(out, in *seq.Buffer) { + res, err := basictypes.ErrorPair() + out.WriteInt(res) + if err == nil { + out.WriteString("") + } else { + out.WriteString(err.Error()) + } +} + +func proxy_Ints(out, in *seq.Buffer) { + param_x := in.ReadInt8() + param_y := in.ReadInt16() + param_z := in.ReadInt32() + param_t := in.ReadInt64() + param_u := in.ReadInt() + basictypes.Ints(param_x, param_y, param_z, param_t, param_u) +} + +func init() { + seq.Register("basictypes", 1, proxy_Bool) + seq.Register("basictypes", 2, proxy_ByteArrays) + seq.Register("basictypes", 3, proxy_Error) + seq.Register("basictypes", 4, proxy_ErrorPair) + seq.Register("basictypes", 5, proxy_Ints) +} diff --git a/src/golang.org/x/mobile/bind/testdata/basictypes.java.golden b/src/golang.org/x/mobile/bind/testdata/basictypes.java.golden new file mode 100644 index 0000000000..8255242ce8 --- /dev/null +++ b/src/golang.org/x/mobile/bind/testdata/basictypes.java.golden @@ -0,0 +1,72 @@ +// Java Package basictypes is a proxy for talking to a Go program. +// gobind -lang=java basictypes +// +// File is generated by gobind. Do not edit. +package go.basictypes; + +import go.Seq; + +public abstract class Basictypes { + private Basictypes() {} // uninstantiable + + public static boolean Bool(boolean p0) { + go.Seq _in = new go.Seq(); + go.Seq _out = new go.Seq(); + boolean _result; + _in.writeBool(p0); + Seq.send(DESCRIPTOR, CALL_Bool, _in, _out); + _result = _out.readBool(); + return _result; + } + + public static byte[] ByteArrays(byte[] x) { + go.Seq _in = new go.Seq(); + go.Seq _out = new go.Seq(); + byte[] _result; + _in.writeByteArray(x); + Seq.send(DESCRIPTOR, CALL_ByteArrays, _in, _out); + _result = _out.readByteArray(); + return _result; + } + + public static void Error() throws Exception { + go.Seq _in = new go.Seq(); + go.Seq _out = new go.Seq(); + Seq.send(DESCRIPTOR, CALL_Error, _in, _out); + String _err = _out.readString(); + if (_err != null) { + throw new Exception(_err); + } + } + + public static long ErrorPair() throws Exception { + go.Seq _in = new go.Seq(); + go.Seq _out = new go.Seq(); + long _result; + Seq.send(DESCRIPTOR, CALL_ErrorPair, _in, _out); + _result = _out.readInt(); + String _err = _out.readString(); + if (_err != null) { + throw new Exception(_err); + } + return _result; + } + + public static void Ints(byte x, short y, int z, long t, long u) { + go.Seq _in = new go.Seq(); + go.Seq _out = new go.Seq(); + _in.writeInt8(x); + _in.writeInt16(y); + _in.writeInt32(z); + _in.writeInt64(t); + _in.writeInt(u); + Seq.send(DESCRIPTOR, CALL_Ints, _in, _out); + } + + private static final int CALL_Bool = 1; + private static final int CALL_ByteArrays = 2; + private static final int CALL_Error = 3; + private static final int CALL_ErrorPair = 4; + private static final int CALL_Ints = 5; + private static final String DESCRIPTOR = "basictypes"; +} diff --git a/src/golang.org/x/mobile/bind/testdata/basictypes.objc.h.golden b/src/golang.org/x/mobile/bind/testdata/basictypes.objc.h.golden new file mode 100644 index 0000000000..468647d992 --- /dev/null +++ b/src/golang.org/x/mobile/bind/testdata/basictypes.objc.h.golden @@ -0,0 +1,21 @@ +// Objective-C API for talking to basictypes Go package. +// gobind -lang=objc basictypes +// +// File is generated by gobind. Do not edit. + +#ifndef __GoBasictypes_H__ +#define __GoBasictypes_H__ + +#include + +FOUNDATION_EXPORT BOOL GoBasictypesBool(BOOL p0); + +FOUNDATION_EXPORT NSData* GoBasictypesByteArrays(NSData* x); + +FOUNDATION_EXPORT BOOL GoBasictypesError(NSError** error); + +FOUNDATION_EXPORT BOOL GoBasictypesErrorPair(int* ret0_, NSError** error); + +FOUNDATION_EXPORT void GoBasictypesInts(int8_t x, int16_t y, int32_t z, int64_t t, int u); + +#endif diff --git a/src/golang.org/x/mobile/bind/testdata/basictypes.objc.m.golden b/src/golang.org/x/mobile/bind/testdata/basictypes.objc.m.golden new file mode 100644 index 0000000000..dbec0bc989 --- /dev/null +++ b/src/golang.org/x/mobile/bind/testdata/basictypes.objc.m.golden @@ -0,0 +1,92 @@ +// Objective-C API for talking to basictypes Go package. +// gobind -lang=objc basictypes +// +// File is generated by gobind. Do not edit. + +#include "GoBasictypes.h" +#include +#include "seq.h" + +static NSString* errDomain = @"go.basictypes"; + +@protocol goSeqRefInterface +-(GoSeqRef*) ref; +@end + +#define _DESCRIPTOR_ "basictypes" + +#define _CALL_Bool_ 1 +#define _CALL_ByteArrays_ 2 +#define _CALL_Error_ 3 +#define _CALL_ErrorPair_ 4 +#define _CALL_Ints_ 5 + +BOOL GoBasictypesBool(BOOL p0) { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeBool(&in_, p0); + go_seq_send(_DESCRIPTOR_, _CALL_Bool_, &in_, &out_); + BOOL ret0_ = go_seq_readBool(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + +NSData* GoBasictypesByteArrays(NSData* x) { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeByteArray(&in_, x); + go_seq_send(_DESCRIPTOR_, _CALL_ByteArrays_, &in_, &out_); + NSData* ret0_ = go_seq_readByteArray(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + +BOOL GoBasictypesError(NSError** error) { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_send(_DESCRIPTOR_, _CALL_Error_, &in_, &out_); + NSString* _error = go_seq_readUTF8(&out_); + if ([_error length] != 0 && error != nil) { + NSMutableDictionary* details = [NSMutableDictionary dictionary]; + [details setValue:_error forKey:NSLocalizedDescriptionKey]; + *error = [NSError errorWithDomain:errDomain code:1 userInfo:details]; + } + go_seq_free(&in_); + go_seq_free(&out_); + return ([_error length] == 0); +} + +BOOL GoBasictypesErrorPair(int* ret0_, NSError** error) { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_send(_DESCRIPTOR_, _CALL_ErrorPair_, &in_, &out_); + int ret0__val = go_seq_readInt(&out_); + if (ret0_ != NULL) { + *ret0_ = ret0__val; + } + NSString* _error = go_seq_readUTF8(&out_); + if ([_error length] != 0 && error != nil) { + NSMutableDictionary* details = [NSMutableDictionary dictionary]; + [details setValue:_error forKey:NSLocalizedDescriptionKey]; + *error = [NSError errorWithDomain:errDomain code:1 userInfo:details]; + } + go_seq_free(&in_); + go_seq_free(&out_); + return ([_error length] == 0); +} + +void GoBasictypesInts(int8_t x, int16_t y, int32_t z, int64_t t, int u) { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeInt8(&in_, x); + go_seq_writeInt16(&in_, y); + go_seq_writeInt32(&in_, z); + go_seq_writeInt64(&in_, t); + go_seq_writeInt(&in_, u); + go_seq_send(_DESCRIPTOR_, _CALL_Ints_, &in_, &out_); + go_seq_free(&in_); + go_seq_free(&out_); +} + diff --git a/src/golang.org/x/mobile/bind/testdata/interfaces.go b/src/golang.org/x/mobile/bind/testdata/interfaces.go new file mode 100644 index 0000000000..fcfed0075c --- /dev/null +++ b/src/golang.org/x/mobile/bind/testdata/interfaces.go @@ -0,0 +1,25 @@ +// Copyright 2014 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. + +package interfaces + +type I interface { + Rand() int32 +} + +func Add3(r I) int32 { + return r.Rand() + r.Rand() + r.Rand() +} + +// chosen by fair dice roll. +// guaranteed to be random. +type seven struct{} + +func (seven) Rand() int32 { return 7 } + +func Seven() I { return seven{} } + +type WithParam interface { + HasParam(p bool) +} diff --git a/src/golang.org/x/mobile/bind/testdata/interfaces.go.golden b/src/golang.org/x/mobile/bind/testdata/interfaces.go.golden new file mode 100644 index 0000000000..35bf2af273 --- /dev/null +++ b/src/golang.org/x/mobile/bind/testdata/interfaces.go.golden @@ -0,0 +1,81 @@ +// Package go_interfaces is an autogenerated binder stub for package interfaces. +// gobind -lang=go interfaces +// +// File is generated by gobind. Do not edit. +package go_interfaces + +import ( + "golang.org/x/mobile/bind/seq" + "interfaces" +) + +func proxy_Add3(out, in *seq.Buffer) { + var param_r interfaces.I + param_r_ref := in.ReadRef() + if param_r_ref.Num < 0 { // go object + param_r = param_r_ref.Get().(interfaces.I) + } else { // foreign object + param_r = (*proxyI)(param_r_ref) + } + res := interfaces.Add3(param_r) + out.WriteInt32(res) +} + +const ( + proxyI_Descriptor = "go.interfaces.I" + proxyI_Rand_Code = 0x10a +) + +func proxyI_Rand(out, in *seq.Buffer) { + ref := in.ReadRef() + v := ref.Get().(interfaces.I) + res := v.Rand() + out.WriteInt32(res) +} + +func init() { + seq.Register(proxyI_Descriptor, proxyI_Rand_Code, proxyI_Rand) +} + +type proxyI seq.Ref + +func (p *proxyI) Rand() int32 { + in := new(seq.Buffer) + out := seq.Transact((*seq.Ref)(p), "go.interfaces.I", proxyI_Rand_Code, in) + res_0 := out.ReadInt32() + return res_0 +} + +func proxy_Seven(out, in *seq.Buffer) { + res := interfaces.Seven() + out.WriteGoRef(res) +} + +const ( + proxyWithParam_Descriptor = "go.interfaces.WithParam" + proxyWithParam_HasParam_Code = 0x10a +) + +func proxyWithParam_HasParam(out, in *seq.Buffer) { + ref := in.ReadRef() + v := ref.Get().(interfaces.WithParam) + param_p0 := in.ReadBool() + v.HasParam(param_p0) +} + +func init() { + seq.Register(proxyWithParam_Descriptor, proxyWithParam_HasParam_Code, proxyWithParam_HasParam) +} + +type proxyWithParam seq.Ref + +func (p *proxyWithParam) HasParam(p0 bool) { + in := new(seq.Buffer) + in.WriteBool(p0) + seq.Transact((*seq.Ref)(p), "go.interfaces.WithParam", proxyWithParam_HasParam_Code, in) +} + +func init() { + seq.Register("interfaces", 1, proxy_Add3) + seq.Register("interfaces", 2, proxy_Seven) +} diff --git a/src/golang.org/x/mobile/bind/testdata/interfaces.java.golden b/src/golang.org/x/mobile/bind/testdata/interfaces.java.golden new file mode 100644 index 0000000000..862f9cb20a --- /dev/null +++ b/src/golang.org/x/mobile/bind/testdata/interfaces.java.golden @@ -0,0 +1,139 @@ +// Java Package interfaces is a proxy for talking to a Go program. +// gobind -lang=java interfaces +// +// File is generated by gobind. Do not edit. +package go.interfaces; + +import go.Seq; + +public abstract class Interfaces { + private Interfaces() {} // uninstantiable + + public static int Add3(I r) { + go.Seq _in = new go.Seq(); + go.Seq _out = new go.Seq(); + int _result; + _in.writeRef(r.ref()); + Seq.send(DESCRIPTOR, CALL_Add3, _in, _out); + _result = _out.readInt32(); + return _result; + } + + public interface I extends go.Seq.Object { + public int Rand(); + + public static abstract class Stub implements I { + static final String DESCRIPTOR = "go.interfaces.I"; + + private final go.Seq.Ref ref; + public Stub() { + ref = go.Seq.createRef(this); + } + + public go.Seq.Ref ref() { return ref; } + + public void call(int code, go.Seq in, go.Seq out) { + switch (code) { + case Proxy.CALL_Rand: { + int result = this.Rand(); + out.writeInt32(result); + return; + } + default: + throw new RuntimeException("unknown code: "+ code); + } + } + } + + static final class Proxy implements I { + static final String DESCRIPTOR = Stub.DESCRIPTOR; + + private go.Seq.Ref ref; + + Proxy(go.Seq.Ref ref) { this.ref = ref; } + + public go.Seq.Ref ref() { return ref; } + + public void call(int code, go.Seq in, go.Seq out) { + throw new RuntimeException("cycle: cannot call proxy"); + } + + public int Rand() { + go.Seq _in = new go.Seq(); + go.Seq _out = new go.Seq(); + int _result; + _in.writeRef(ref); + Seq.send(DESCRIPTOR, CALL_Rand, _in, _out); + _result = _out.readInt32(); + return _result; + } + + static final int CALL_Rand = 0x10a; + } + } + + public static I Seven() { + go.Seq _in = new go.Seq(); + go.Seq _out = new go.Seq(); + I _result; + Seq.send(DESCRIPTOR, CALL_Seven, _in, _out); + _result = new I.Proxy(_out.readRef()); + return _result; + } + + public interface WithParam extends go.Seq.Object { + public void HasParam(boolean p0); + + public static abstract class Stub implements WithParam { + static final String DESCRIPTOR = "go.interfaces.WithParam"; + + private final go.Seq.Ref ref; + public Stub() { + ref = go.Seq.createRef(this); + } + + public go.Seq.Ref ref() { return ref; } + + public void call(int code, go.Seq in, go.Seq out) { + switch (code) { + case Proxy.CALL_HasParam: { + boolean param_p0; + param_p0 = in.readBool(); + this.HasParam(param_p0); + return; + } + default: + throw new RuntimeException("unknown code: "+ code); + } + } + } + + static final class Proxy implements WithParam { + static final String DESCRIPTOR = Stub.DESCRIPTOR; + + private go.Seq.Ref ref; + + Proxy(go.Seq.Ref ref) { this.ref = ref; } + + public go.Seq.Ref ref() { return ref; } + + public void call(int code, go.Seq in, go.Seq out) { + throw new RuntimeException("cycle: cannot call proxy"); + } + + public void HasParam(boolean p0) { + go.Seq _in = new go.Seq(); + go.Seq _out = new go.Seq(); + _in.writeRef(ref); + _in.writeBool(p0); + Seq.send(DESCRIPTOR, CALL_HasParam, _in, _out); + } + + static final int CALL_HasParam = 0x10a; + } + } + + private static final int CALL_Add3 = 1; + private static final int CALL_Seven = 2; + private static final String DESCRIPTOR = "interfaces"; +} diff --git a/src/golang.org/x/mobile/bind/testdata/interfaces.objc.h.golden b/src/golang.org/x/mobile/bind/testdata/interfaces.objc.h.golden new file mode 100644 index 0000000000..7af90a822a --- /dev/null +++ b/src/golang.org/x/mobile/bind/testdata/interfaces.objc.h.golden @@ -0,0 +1,27 @@ +// Objective-C API for talking to interfaces Go package. +// gobind -lang=objc interfaces +// +// File is generated by gobind. Do not edit. + +#ifndef __GoInterfaces_H__ +#define __GoInterfaces_H__ + +#include + +@class GoInterfacesI; + +@class GoInterfacesWithParam; + +@protocol GoInterfacesI +- (int32_t)Rand; +@end + +@protocol GoInterfacesWithParam +- (void)HasParam:(BOOL)p0; +@end + +FOUNDATION_EXPORT int32_t GoInterfacesAdd3(id r); + +FOUNDATION_EXPORT id GoInterfacesSeven(); + +#endif diff --git a/src/golang.org/x/mobile/bind/testdata/interfaces.objc.m.golden b/src/golang.org/x/mobile/bind/testdata/interfaces.objc.m.golden new file mode 100644 index 0000000000..bc5c43d13a --- /dev/null +++ b/src/golang.org/x/mobile/bind/testdata/interfaces.objc.m.golden @@ -0,0 +1,143 @@ +// Objective-C API for talking to interfaces Go package. +// gobind -lang=objc interfaces +// +// File is generated by gobind. Do not edit. + +#include "GoInterfaces.h" +#include +#include "seq.h" + +static NSString* errDomain = @"go.interfaces"; + +@protocol goSeqRefInterface +-(GoSeqRef*) ref; +@end + +#define _DESCRIPTOR_ "interfaces" + +#define _CALL_Add3_ 1 +#define _CALL_Seven_ 2 + +#define _GO_interfaces_I_DESCRIPTOR_ "go.interfaces.I" +#define _GO_interfaces_I_Rand_ (0x10a) + +@interface GoInterfacesI : NSObject { +} +@property(strong, readonly) id ref; + +- (id)initWithRef:(id)ref; +- (int32_t)Rand; +@end + +@implementation GoInterfacesI { +} + +- (id)initWithRef:(id)ref { + self = [super init]; + if (self) { _ref = ref; } + return self; +} + +- (int32_t)Rand { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, self.ref); + go_seq_send(_GO_interfaces_I_DESCRIPTOR_, _GO_interfaces_I_Rand_, &in_, &out_); + int32_t ret0_ = go_seq_readInt32(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + +@end + +static void proxyGoInterfacesI(id obj, int code, GoSeq* in, GoSeq* out) { + switch (code) { + case _GO_interfaces_I_Rand_: { + id o = (id)(obj); + int32_t returnVal = [o Rand]; + go_seq_writeInt32(out, returnVal); + } break; + default: + NSLog(@"unknown code %x for _GO_interfaces_I_DESCRIPTOR_", code); + } +} + +#define _GO_interfaces_WithParam_DESCRIPTOR_ "go.interfaces.WithParam" +#define _GO_interfaces_WithParam_HasParam_ (0x10a) + +@interface GoInterfacesWithParam : NSObject { +} +@property(strong, readonly) id ref; + +- (id)initWithRef:(id)ref; +- (void)HasParam:(BOOL)p0; +@end + +@implementation GoInterfacesWithParam { +} + +- (id)initWithRef:(id)ref { + self = [super init]; + if (self) { _ref = ref; } + return self; +} + +- (void)HasParam:(BOOL)p0 { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, self.ref); + go_seq_writeBool(&in_, p0); + go_seq_send(_GO_interfaces_WithParam_DESCRIPTOR_, _GO_interfaces_WithParam_HasParam_, &in_, &out_); + go_seq_free(&in_); + go_seq_free(&out_); +} + +@end + +static void proxyGoInterfacesWithParam(id obj, int code, GoSeq* in, GoSeq* out) { + switch (code) { + case _GO_interfaces_WithParam_HasParam_: { + id o = (id)(obj); + BOOL p0 = go_seq_readBool(in); + [o HasParam:p0]; + } break; + default: + NSLog(@"unknown code %x for _GO_interfaces_WithParam_DESCRIPTOR_", code); + } +} + +int32_t GoInterfacesAdd3(id r) { + GoSeq in_ = {}; + GoSeq out_ = {}; + if ([(id)(r) isKindOfClass:[GoInterfacesI class]]) { + id r_proxy = (id)(r); + go_seq_writeRef(&in_, r_proxy.ref); + } else { + go_seq_writeObjcRef(&in_, r); + } + go_seq_send(_DESCRIPTOR_, _CALL_Add3_, &in_, &out_); + int32_t ret0_ = go_seq_readInt32(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + +id GoInterfacesSeven() { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_send(_DESCRIPTOR_, _CALL_Seven_, &in_, &out_); + GoSeqRef* ret0__ref = go_seq_readRef(&out_); + id ret0_ = ret0__ref.obj; + if (ret0_ == NULL) { + ret0_ = [[GoInterfacesI alloc] initWithRef:ret0__ref]; + } + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + +__attribute__((constructor)) static void init() { + go_seq_register_proxy("go.interfaces.I", proxyGoInterfacesI); + go_seq_register_proxy("go.interfaces.WithParam", proxyGoInterfacesWithParam); +} diff --git a/src/golang.org/x/mobile/bind/testdata/issue10788.go b/src/golang.org/x/mobile/bind/testdata/issue10788.go new file mode 100644 index 0000000000..e5c6a129b2 --- /dev/null +++ b/src/golang.org/x/mobile/bind/testdata/issue10788.go @@ -0,0 +1,14 @@ +// Copyright 2015 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. + +package issue10788 + +type TestStruct struct { + Value string +} + +type TestInterface interface { + DoSomeWork(s *TestStruct) + MultipleUnnamedParams(_ int, p0 string, _ int64) +} diff --git a/src/golang.org/x/mobile/bind/testdata/issue10788.go.golden b/src/golang.org/x/mobile/bind/testdata/issue10788.go.golden new file mode 100644 index 0000000000..ee9d8cef40 --- /dev/null +++ b/src/golang.org/x/mobile/bind/testdata/issue10788.go.golden @@ -0,0 +1,83 @@ +// Package go_issue10788 is an autogenerated binder stub for package issue10788. +// gobind -lang=go issue10788 +// +// File is generated by gobind. Do not edit. +package go_issue10788 + +import ( + "golang.org/x/mobile/bind/seq" + "issue10788" +) + +const ( + proxyTestInterface_Descriptor = "go.issue10788.TestInterface" + proxyTestInterface_DoSomeWork_Code = 0x10a + proxyTestInterface_MultipleUnnamedParams_Code = 0x20a +) + +func proxyTestInterface_DoSomeWork(out, in *seq.Buffer) { + ref := in.ReadRef() + v := ref.Get().(issue10788.TestInterface) + // Must be a Go object + param_s_ref := in.ReadRef() + param_s := param_s_ref.Get().(*issue10788.TestStruct) + v.DoSomeWork(param_s) +} + +func proxyTestInterface_MultipleUnnamedParams(out, in *seq.Buffer) { + ref := in.ReadRef() + v := ref.Get().(issue10788.TestInterface) + param_p0 := in.ReadInt() + param_p1 := in.ReadString() + param_p2 := in.ReadInt64() + v.MultipleUnnamedParams(param_p0, param_p1, param_p2) +} + +func init() { + seq.Register(proxyTestInterface_Descriptor, proxyTestInterface_DoSomeWork_Code, proxyTestInterface_DoSomeWork) + seq.Register(proxyTestInterface_Descriptor, proxyTestInterface_MultipleUnnamedParams_Code, proxyTestInterface_MultipleUnnamedParams) +} + +type proxyTestInterface seq.Ref + +func (p *proxyTestInterface) DoSomeWork(s *issue10788.TestStruct) { + in := new(seq.Buffer) + in.WriteGoRef(s) + seq.Transact((*seq.Ref)(p), "go.issue10788.TestInterface", proxyTestInterface_DoSomeWork_Code, in) +} + +func (p *proxyTestInterface) MultipleUnnamedParams(p0 int, p1 string, p2 int64) { + in := new(seq.Buffer) + in.WriteInt(p0) + in.WriteString(p1) + in.WriteInt64(p2) + seq.Transact((*seq.Ref)(p), "go.issue10788.TestInterface", proxyTestInterface_MultipleUnnamedParams_Code, in) +} + +const ( + proxyTestStruct_Descriptor = "go.issue10788.TestStruct" + proxyTestStruct_Value_Get_Code = 0x00f + proxyTestStruct_Value_Set_Code = 0x01f +) + +type proxyTestStruct seq.Ref + +func proxyTestStruct_Value_Set(out, in *seq.Buffer) { + ref := in.ReadRef() + v := in.ReadString() + ref.Get().(*issue10788.TestStruct).Value = v +} + +func proxyTestStruct_Value_Get(out, in *seq.Buffer) { + ref := in.ReadRef() + v := ref.Get().(*issue10788.TestStruct).Value + out.WriteString(v) +} + +func init() { + seq.Register(proxyTestStruct_Descriptor, proxyTestStruct_Value_Set_Code, proxyTestStruct_Value_Set) + seq.Register(proxyTestStruct_Descriptor, proxyTestStruct_Value_Get_Code, proxyTestStruct_Value_Get) +} + +func init() { +} diff --git a/src/golang.org/x/mobile/bind/testdata/issue10788.java.golden b/src/golang.org/x/mobile/bind/testdata/issue10788.java.golden new file mode 100644 index 0000000000..3cae2430d6 --- /dev/null +++ b/src/golang.org/x/mobile/bind/testdata/issue10788.java.golden @@ -0,0 +1,149 @@ +// Java Package issue10788 is a proxy for talking to a Go program. +// gobind -lang=java issue10788 +// +// File is generated by gobind. Do not edit. +package go.issue10788; + +import go.Seq; + +public abstract class Issue10788 { + private Issue10788() {} // uninstantiable + + public interface TestInterface extends go.Seq.Object { + public void DoSomeWork(TestStruct s); + + public void MultipleUnnamedParams(long p0, String p1, long p2); + + public static abstract class Stub implements TestInterface { + static final String DESCRIPTOR = "go.issue10788.TestInterface"; + + private final go.Seq.Ref ref; + public Stub() { + ref = go.Seq.createRef(this); + } + + public go.Seq.Ref ref() { return ref; } + + public void call(int code, go.Seq in, go.Seq out) { + switch (code) { + case Proxy.CALL_DoSomeWork: { + TestStruct param_s; + param_s = new TestStruct(in.readRef()); + this.DoSomeWork(param_s); + return; + } + case Proxy.CALL_MultipleUnnamedParams: { + long param_p0; + param_p0 = in.readInt(); + String param_p1; + param_p1 = in.readString(); + long param_p2; + param_p2 = in.readInt64(); + this.MultipleUnnamedParams(param_p0, param_p1, param_p2); + return; + } + default: + throw new RuntimeException("unknown code: "+ code); + } + } + } + + static final class Proxy implements TestInterface { + static final String DESCRIPTOR = Stub.DESCRIPTOR; + + private go.Seq.Ref ref; + + Proxy(go.Seq.Ref ref) { this.ref = ref; } + + public go.Seq.Ref ref() { return ref; } + + public void call(int code, go.Seq in, go.Seq out) { + throw new RuntimeException("cycle: cannot call proxy"); + } + + public void DoSomeWork(TestStruct s) { + go.Seq _in = new go.Seq(); + go.Seq _out = new go.Seq(); + _in.writeRef(ref); + _in.writeRef(s.ref()); + Seq.send(DESCRIPTOR, CALL_DoSomeWork, _in, _out); + } + + public void MultipleUnnamedParams(long p0, String p1, long p2) { + go.Seq _in = new go.Seq(); + go.Seq _out = new go.Seq(); + _in.writeRef(ref); + _in.writeInt(p0); + _in.writeString(p1); + _in.writeInt64(p2); + Seq.send(DESCRIPTOR, CALL_MultipleUnnamedParams, _in, _out); + } + + static final int CALL_DoSomeWork = 0x10a; + static final int CALL_MultipleUnnamedParams = 0x20a; + } + } + + public static final class TestStruct implements go.Seq.Object { + private static final String DESCRIPTOR = "go.issue10788.TestStruct"; + private static final int FIELD_Value_GET = 0x00f; + private static final int FIELD_Value_SET = 0x01f; + + private go.Seq.Ref ref; + + private TestStruct(go.Seq.Ref ref) { this.ref = ref; } + + public go.Seq.Ref ref() { return ref; } + + public void call(int code, go.Seq in, go.Seq out) { + throw new RuntimeException("internal error: cycle: cannot call concrete proxy"); + } + + public String getValue() { + Seq in = new Seq(); + Seq out = new Seq(); + in.writeRef(ref); + Seq.send(DESCRIPTOR, FIELD_Value_GET, in, out); + return out.readString(); + } + + public void setValue(String v) { + Seq in = new Seq(); + Seq out = new Seq(); + in.writeRef(ref); + in.writeString(v); + Seq.send(DESCRIPTOR, FIELD_Value_SET, in, out); + } + + @Override public boolean equals(Object o) { + if (o == null || !(o instanceof TestStruct)) { + return false; + } + TestStruct that = (TestStruct)o; + String thisValue = getValue(); + String thatValue = that.getValue(); + if (thisValue == null) { + if (thatValue != null) { + return false; + } + } else if (!thisValue.equals(thatValue)) { + return false; + } + return true; + } + + @Override public int hashCode() { + return java.util.Arrays.hashCode(new Object[] {getValue()}); + } + + @Override public String toString() { + StringBuilder b = new StringBuilder(); + b.append("TestStruct").append("{"); + b.append("Value:").append(getValue()).append(","); + return b.append("}").toString(); + } + + } + + private static final String DESCRIPTOR = "issue10788"; +} diff --git a/src/golang.org/x/mobile/bind/testdata/issue10788.objc.h.golden b/src/golang.org/x/mobile/bind/testdata/issue10788.objc.h.golden new file mode 100644 index 0000000000..be402cd92b --- /dev/null +++ b/src/golang.org/x/mobile/bind/testdata/issue10788.objc.h.golden @@ -0,0 +1,29 @@ +// Objective-C API for talking to issue10788 Go package. +// gobind -lang=objc issue10788 +// +// File is generated by gobind. Do not edit. + +#ifndef __GoIssue10788_H__ +#define __GoIssue10788_H__ + +#include + +@class GoIssue10788TestInterface; + +@class GoIssue10788TestStruct; + +@protocol GoIssue10788TestInterface +- (void)DoSomeWork:(GoIssue10788TestStruct*)s; +- (void)MultipleUnnamedParams:(int)p0 p1:(NSString*)p1 p2:(int64_t)p2; +@end + +@interface GoIssue10788TestStruct : NSObject { +} +@property(strong, readonly) id ref; + +- (id)initWithRef:(id)ref; +- (NSString*)Value; +- (void)setValue:(NSString*)v; +@end + +#endif diff --git a/src/golang.org/x/mobile/bind/testdata/issue10788.objc.m.golden b/src/golang.org/x/mobile/bind/testdata/issue10788.objc.m.golden new file mode 100644 index 0000000000..c631702c08 --- /dev/null +++ b/src/golang.org/x/mobile/bind/testdata/issue10788.objc.m.golden @@ -0,0 +1,131 @@ +// Objective-C API for talking to issue10788 Go package. +// gobind -lang=objc issue10788 +// +// File is generated by gobind. Do not edit. + +#include "GoIssue10788.h" +#include +#include "seq.h" + +static NSString* errDomain = @"go.issue10788"; + +@protocol goSeqRefInterface +-(GoSeqRef*) ref; +@end + +#define _DESCRIPTOR_ "issue10788" + + +#define _GO_issue10788_TestInterface_DESCRIPTOR_ "go.issue10788.TestInterface" +#define _GO_issue10788_TestInterface_DoSomeWork_ (0x10a) +#define _GO_issue10788_TestInterface_MultipleUnnamedParams_ (0x20a) + +@interface GoIssue10788TestInterface : NSObject { +} +@property(strong, readonly) id ref; + +- (id)initWithRef:(id)ref; +- (void)DoSomeWork:(GoIssue10788TestStruct*)s; +- (void)MultipleUnnamedParams:(int)p0 p1:(NSString*)p1 p2:(int64_t)p2; +@end + +@implementation GoIssue10788TestInterface { +} + +- (id)initWithRef:(id)ref { + self = [super init]; + if (self) { _ref = ref; } + return self; +} + +- (void)DoSomeWork:(GoIssue10788TestStruct*)s { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, self.ref); + if ([(id)(s) isKindOfClass:[GoIssue10788TestStruct class]]) { + id s_proxy = (id)(s); + go_seq_writeRef(&in_, s_proxy.ref); + } else { + go_seq_writeObjcRef(&in_, s); + } + go_seq_send(_GO_issue10788_TestInterface_DESCRIPTOR_, _GO_issue10788_TestInterface_DoSomeWork_, &in_, &out_); + go_seq_free(&in_); + go_seq_free(&out_); +} + +- (void)MultipleUnnamedParams:(int)p0 p1:(NSString*)p1 p2:(int64_t)p2 { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, self.ref); + go_seq_writeInt(&in_, p0); + go_seq_writeUTF8(&in_, p1); + go_seq_writeInt64(&in_, p2); + go_seq_send(_GO_issue10788_TestInterface_DESCRIPTOR_, _GO_issue10788_TestInterface_MultipleUnnamedParams_, &in_, &out_); + go_seq_free(&in_); + go_seq_free(&out_); +} + +@end + +static void proxyGoIssue10788TestInterface(id obj, int code, GoSeq* in, GoSeq* out) { + switch (code) { + case _GO_issue10788_TestInterface_DoSomeWork_: { + id o = (id)(obj); + GoSeqRef* s_ref = go_seq_readRef(in); + GoIssue10788TestStruct* s = s_ref.obj; + if (s == NULL) { + s = [[GoIssue10788TestStruct alloc] initWithRef:s_ref]; + } + [o DoSomeWork:s]; + } break; + case _GO_issue10788_TestInterface_MultipleUnnamedParams_: { + id o = (id)(obj); + int p0 = go_seq_readInt(in); + NSString* p1 = go_seq_readUTF8(in); + int64_t p2 = go_seq_readInt64(in); + [o MultipleUnnamedParams:p0 p1:p1 p2:p2]; + } break; + default: + NSLog(@"unknown code %x for _GO_issue10788_TestInterface_DESCRIPTOR_", code); + } +} + +#define _GO_issue10788_TestStruct_DESCRIPTOR_ "go.issue10788.TestStruct" +#define _GO_issue10788_TestStruct_FIELD_Value_GET_ (0x00f) +#define _GO_issue10788_TestStruct_FIELD_Value_SET_ (0x01f) + +@implementation GoIssue10788TestStruct { +} + +- (id)initWithRef:(id)ref { + self = [super init]; + if (self) { _ref = ref; } + return self; +} + +- (NSString*)Value { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, self.ref); + go_seq_send(_GO_issue10788_TestStruct_DESCRIPTOR_, _GO_issue10788_TestStruct_FIELD_Value_GET_, &in_, &out_); + NSString* ret_ = go_seq_readUTF8(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret_; +} + +- (void)setValue:(NSString*)v { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, self.ref); + go_seq_writeUTF8(&in_, v); + go_seq_send(_GO_issue10788_TestStruct_DESCRIPTOR_, _GO_issue10788_TestStruct_FIELD_Value_SET_, &in_, &out_); + go_seq_free(&in_); + go_seq_free(&out_); +} + +@end + +__attribute__((constructor)) static void init() { + go_seq_register_proxy("go.issue10788.TestInterface", proxyGoIssue10788TestInterface); +} diff --git a/src/golang.org/x/mobile/bind/testdata/structs.go b/src/golang.org/x/mobile/bind/testdata/structs.go new file mode 100644 index 0000000000..9b493ff049 --- /dev/null +++ b/src/golang.org/x/mobile/bind/testdata/structs.go @@ -0,0 +1,26 @@ +// Copyright 2014 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. + +package structs + +type S struct { + X, Y float64 + unexported bool +} + +func (s *S) Sum() float64 { + return s.X + s.Y +} + +func (s *S) Identity() (*S, error) { + return s, nil +} + +func Identity(s *S) *S { + return s +} + +func IdentityWithError(s *S) (*S, error) { + return s, nil +} diff --git a/src/golang.org/x/mobile/bind/testdata/structs.go.golden b/src/golang.org/x/mobile/bind/testdata/structs.go.golden new file mode 100644 index 0000000000..0fb4cff043 --- /dev/null +++ b/src/golang.org/x/mobile/bind/testdata/structs.go.golden @@ -0,0 +1,100 @@ +// Package go_structs is an autogenerated binder stub for package structs. +// gobind -lang=go structs +// +// File is generated by gobind. Do not edit. +package go_structs + +import ( + "golang.org/x/mobile/bind/seq" + "structs" +) + +func proxy_Identity(out, in *seq.Buffer) { + // Must be a Go object + param_s_ref := in.ReadRef() + param_s := param_s_ref.Get().(*structs.S) + res := structs.Identity(param_s) + out.WriteGoRef(res) +} + +func proxy_IdentityWithError(out, in *seq.Buffer) { + // Must be a Go object + param_s_ref := in.ReadRef() + param_s := param_s_ref.Get().(*structs.S) + res, err := structs.IdentityWithError(param_s) + out.WriteGoRef(res) + if err == nil { + out.WriteString("") + } else { + out.WriteString(err.Error()) + } +} + +const ( + proxyS_Descriptor = "go.structs.S" + proxyS_X_Get_Code = 0x00f + proxyS_X_Set_Code = 0x01f + proxyS_Y_Get_Code = 0x10f + proxyS_Y_Set_Code = 0x11f + proxyS_Identity_Code = 0x00c + proxyS_Sum_Code = 0x10c +) + +type proxyS seq.Ref + +func proxyS_X_Set(out, in *seq.Buffer) { + ref := in.ReadRef() + v := in.ReadFloat64() + ref.Get().(*structs.S).X = v +} + +func proxyS_X_Get(out, in *seq.Buffer) { + ref := in.ReadRef() + v := ref.Get().(*structs.S).X + out.WriteFloat64(v) +} + +func proxyS_Y_Set(out, in *seq.Buffer) { + ref := in.ReadRef() + v := in.ReadFloat64() + ref.Get().(*structs.S).Y = v +} + +func proxyS_Y_Get(out, in *seq.Buffer) { + ref := in.ReadRef() + v := ref.Get().(*structs.S).Y + out.WriteFloat64(v) +} + +func proxyS_Identity(out, in *seq.Buffer) { + ref := in.ReadRef() + v := ref.Get().(*structs.S) + res, err := v.Identity() + out.WriteGoRef(res) + if err == nil { + out.WriteString("") + } else { + out.WriteString(err.Error()) + } +} + +func proxyS_Sum(out, in *seq.Buffer) { + ref := in.ReadRef() + v := ref.Get().(*structs.S) + res := v.Sum() + out.WriteFloat64(res) +} + +func init() { + seq.Register(proxyS_Descriptor, proxyS_X_Set_Code, proxyS_X_Set) + seq.Register(proxyS_Descriptor, proxyS_X_Get_Code, proxyS_X_Get) + seq.Register(proxyS_Descriptor, proxyS_Y_Set_Code, proxyS_Y_Set) + seq.Register(proxyS_Descriptor, proxyS_Y_Get_Code, proxyS_Y_Get) + seq.Register(proxyS_Descriptor, proxyS_Identity_Code, proxyS_Identity) + seq.Register(proxyS_Descriptor, proxyS_Sum_Code, proxyS_Sum) +} + +func init() { + seq.Register("structs", 1, proxy_Identity) + seq.Register("structs", 2, proxy_IdentityWithError) +} diff --git a/src/golang.org/x/mobile/bind/testdata/structs.java.golden b/src/golang.org/x/mobile/bind/testdata/structs.java.golden new file mode 100644 index 0000000000..52aaa74b8e --- /dev/null +++ b/src/golang.org/x/mobile/bind/testdata/structs.java.golden @@ -0,0 +1,146 @@ +// Java Package structs is a proxy for talking to a Go program. +// gobind -lang=java structs +// +// File is generated by gobind. Do not edit. +package go.structs; + +import go.Seq; + +public abstract class Structs { + private Structs() {} // uninstantiable + + public static S Identity(S s) { + go.Seq _in = new go.Seq(); + go.Seq _out = new go.Seq(); + S _result; + _in.writeRef(s.ref()); + Seq.send(DESCRIPTOR, CALL_Identity, _in, _out); + _result = new S(_out.readRef()); + return _result; + } + + public static S IdentityWithError(S s) throws Exception { + go.Seq _in = new go.Seq(); + go.Seq _out = new go.Seq(); + S _result; + _in.writeRef(s.ref()); + Seq.send(DESCRIPTOR, CALL_IdentityWithError, _in, _out); + _result = new S(_out.readRef()); + String _err = _out.readString(); + if (_err != null) { + throw new Exception(_err); + } + return _result; + } + + public static final class S implements go.Seq.Object { + private static final String DESCRIPTOR = "go.structs.S"; + private static final int FIELD_X_GET = 0x00f; + private static final int FIELD_X_SET = 0x01f; + private static final int FIELD_Y_GET = 0x10f; + private static final int FIELD_Y_SET = 0x11f; + private static final int CALL_Identity = 0x00c; + private static final int CALL_Sum = 0x10c; + + private go.Seq.Ref ref; + + private S(go.Seq.Ref ref) { this.ref = ref; } + + public go.Seq.Ref ref() { return ref; } + + public void call(int code, go.Seq in, go.Seq out) { + throw new RuntimeException("internal error: cycle: cannot call concrete proxy"); + } + + public double getX() { + Seq in = new Seq(); + Seq out = new Seq(); + in.writeRef(ref); + Seq.send(DESCRIPTOR, FIELD_X_GET, in, out); + return out.readFloat64(); + } + + public void setX(double v) { + Seq in = new Seq(); + Seq out = new Seq(); + in.writeRef(ref); + in.writeFloat64(v); + Seq.send(DESCRIPTOR, FIELD_X_SET, in, out); + } + + public double getY() { + Seq in = new Seq(); + Seq out = new Seq(); + in.writeRef(ref); + Seq.send(DESCRIPTOR, FIELD_Y_GET, in, out); + return out.readFloat64(); + } + + public void setY(double v) { + Seq in = new Seq(); + Seq out = new Seq(); + in.writeRef(ref); + in.writeFloat64(v); + Seq.send(DESCRIPTOR, FIELD_Y_SET, in, out); + } + + public S Identity() throws Exception { + go.Seq _in = new go.Seq(); + go.Seq _out = new go.Seq(); + S _result; + _in.writeRef(ref); + Seq.send(DESCRIPTOR, CALL_Identity, _in, _out); + _result = new S(_out.readRef()); + String _err = _out.readString(); + if (_err != null) { + throw new Exception(_err); + } + return _result; + } + + public double Sum() { + go.Seq _in = new go.Seq(); + go.Seq _out = new go.Seq(); + double _result; + _in.writeRef(ref); + Seq.send(DESCRIPTOR, CALL_Sum, _in, _out); + _result = _out.readFloat64(); + return _result; + } + + @Override public boolean equals(Object o) { + if (o == null || !(o instanceof S)) { + return false; + } + S that = (S)o; + double thisX = getX(); + double thatX = that.getX(); + if (thisX != thatX) { + return false; + } + double thisY = getY(); + double thatY = that.getY(); + if (thisY != thatY) { + return false; + } + return true; + } + + @Override public int hashCode() { + return java.util.Arrays.hashCode(new Object[] {getX(), getY()}); + } + + @Override public String toString() { + StringBuilder b = new StringBuilder(); + b.append("S").append("{"); + b.append("X:").append(getX()).append(","); + b.append("Y:").append(getY()).append(","); + return b.append("}").toString(); + } + + } + + private static final int CALL_Identity = 1; + private static final int CALL_IdentityWithError = 2; + private static final String DESCRIPTOR = "structs"; +} diff --git a/src/golang.org/x/mobile/bind/testdata/structs.objc.h.golden b/src/golang.org/x/mobile/bind/testdata/structs.objc.h.golden new file mode 100644 index 0000000000..aae9b4b229 --- /dev/null +++ b/src/golang.org/x/mobile/bind/testdata/structs.objc.h.golden @@ -0,0 +1,30 @@ +// Objective-C API for talking to structs Go package. +// gobind -lang=objc structs +// +// File is generated by gobind. Do not edit. + +#ifndef __GoStructs_H__ +#define __GoStructs_H__ + +#include + +@class GoStructsS; + +@interface GoStructsS : NSObject { +} +@property(strong, readonly) id ref; + +- (id)initWithRef:(id)ref; +- (double)X; +- (void)setX:(double)v; +- (double)Y; +- (void)setY:(double)v; +- (BOOL)Identity:(GoStructsS**)ret0_ error:(NSError**)error; +- (double)Sum; +@end + +FOUNDATION_EXPORT GoStructsS* GoStructsIdentity(GoStructsS* s); + +FOUNDATION_EXPORT BOOL GoStructsIdentityWithError(GoStructsS* s, GoStructsS** ret0_, NSError** error); + +#endif diff --git a/src/golang.org/x/mobile/bind/testdata/structs.objc.m.golden b/src/golang.org/x/mobile/bind/testdata/structs.objc.m.golden new file mode 100644 index 0000000000..c40ffcc904 --- /dev/null +++ b/src/golang.org/x/mobile/bind/testdata/structs.objc.m.golden @@ -0,0 +1,163 @@ +// Objective-C API for talking to structs Go package. +// gobind -lang=objc structs +// +// File is generated by gobind. Do not edit. + +#include "GoStructs.h" +#include +#include "seq.h" + +static NSString* errDomain = @"go.structs"; + +@protocol goSeqRefInterface +-(GoSeqRef*) ref; +@end + +#define _DESCRIPTOR_ "structs" + +#define _CALL_Identity_ 1 +#define _CALL_IdentityWithError_ 2 + +#define _GO_structs_S_DESCRIPTOR_ "go.structs.S" +#define _GO_structs_S_FIELD_X_GET_ (0x00f) +#define _GO_structs_S_FIELD_X_SET_ (0x01f) +#define _GO_structs_S_FIELD_Y_GET_ (0x10f) +#define _GO_structs_S_FIELD_Y_SET_ (0x11f) +#define _GO_structs_S_Identity_ (0x00c) +#define _GO_structs_S_Sum_ (0x10c) + +@implementation GoStructsS { +} + +- (id)initWithRef:(id)ref { + self = [super init]; + if (self) { _ref = ref; } + return self; +} + +- (double)X { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, self.ref); + go_seq_send(_GO_structs_S_DESCRIPTOR_, _GO_structs_S_FIELD_X_GET_, &in_, &out_); + double ret_ = go_seq_readFloat64(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret_; +} + +- (void)setX:(double)v { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, self.ref); + go_seq_writeFloat64(&in_, v); + go_seq_send(_GO_structs_S_DESCRIPTOR_, _GO_structs_S_FIELD_X_SET_, &in_, &out_); + go_seq_free(&in_); + go_seq_free(&out_); +} + +- (double)Y { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, self.ref); + go_seq_send(_GO_structs_S_DESCRIPTOR_, _GO_structs_S_FIELD_Y_GET_, &in_, &out_); + double ret_ = go_seq_readFloat64(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret_; +} + +- (void)setY:(double)v { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, self.ref); + go_seq_writeFloat64(&in_, v); + go_seq_send(_GO_structs_S_DESCRIPTOR_, _GO_structs_S_FIELD_Y_SET_, &in_, &out_); + go_seq_free(&in_); + go_seq_free(&out_); +} + +- (BOOL)Identity:(GoStructsS**)ret0_ error:(NSError**)error { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, self.ref); + go_seq_send(_GO_structs_S_DESCRIPTOR_, _GO_structs_S_Identity_, &in_, &out_); + GoSeqRef* ret0__ref = go_seq_readRef(&out_); + if (ret0_ != NULL) { + *ret0_ = ret0__ref.obj; + if (*ret0_ == NULL) { + *ret0_ = [[GoStructsS alloc] initWithRef:ret0__ref]; + } + } + NSString* _error = go_seq_readUTF8(&out_); + if ([_error length] != 0 && error != nil) { + NSMutableDictionary* details = [NSMutableDictionary dictionary]; + [details setValue:_error forKey:NSLocalizedDescriptionKey]; + *error = [NSError errorWithDomain:errDomain code:1 userInfo:details]; + } + go_seq_free(&in_); + go_seq_free(&out_); + return ([_error length] == 0); +} + +- (double)Sum { + GoSeq in_ = {}; + GoSeq out_ = {}; + go_seq_writeRef(&in_, self.ref); + go_seq_send(_GO_structs_S_DESCRIPTOR_, _GO_structs_S_Sum_, &in_, &out_); + double ret0_ = go_seq_readFloat64(&out_); + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + +@end + +GoStructsS* GoStructsIdentity(GoStructsS* s) { + GoSeq in_ = {}; + GoSeq out_ = {}; + if ([(id)(s) isKindOfClass:[GoStructsS class]]) { + id s_proxy = (id)(s); + go_seq_writeRef(&in_, s_proxy.ref); + } else { + go_seq_writeObjcRef(&in_, s); + } + go_seq_send(_DESCRIPTOR_, _CALL_Identity_, &in_, &out_); + GoSeqRef* ret0__ref = go_seq_readRef(&out_); + GoStructsS* ret0_ = ret0__ref.obj; + if (ret0_ == NULL) { + ret0_ = [[GoStructsS alloc] initWithRef:ret0__ref]; + } + go_seq_free(&in_); + go_seq_free(&out_); + return ret0_; +} + +BOOL GoStructsIdentityWithError(GoStructsS* s, GoStructsS** ret0_, NSError** error) { + GoSeq in_ = {}; + GoSeq out_ = {}; + if ([(id)(s) isKindOfClass:[GoStructsS class]]) { + id s_proxy = (id)(s); + go_seq_writeRef(&in_, s_proxy.ref); + } else { + go_seq_writeObjcRef(&in_, s); + } + go_seq_send(_DESCRIPTOR_, _CALL_IdentityWithError_, &in_, &out_); + GoSeqRef* ret0__ref = go_seq_readRef(&out_); + if (ret0_ != NULL) { + *ret0_ = ret0__ref.obj; + if (*ret0_ == NULL) { + *ret0_ = [[GoStructsS alloc] initWithRef:ret0__ref]; + } + } + NSString* _error = go_seq_readUTF8(&out_); + if ([_error length] != 0 && error != nil) { + NSMutableDictionary* details = [NSMutableDictionary dictionary]; + [details setValue:_error forKey:NSLocalizedDescriptionKey]; + *error = [NSError errorWithDomain:errDomain code:1 userInfo:details]; + } + go_seq_free(&in_); + go_seq_free(&out_); + return ([_error length] == 0); +} + diff --git a/src/golang.org/x/mobile/cmd/gobind/doc.go b/src/golang.org/x/mobile/cmd/gobind/doc.go new file mode 100644 index 0000000000..c71920d6c2 --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gobind/doc.go @@ -0,0 +1,187 @@ +// Copyright 2014 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. + +/* +Gobind generates language bindings that make it possible to call Go +functions from Java and Objective-C. + +Typically gobind is not used directly. Instead, a binding is +generated and automatically packaged for Android or iOS by +`gomobile bind`. For more details on installing and using the gomobile +tool, see https://golang.org/x/mobile/cmd/gomobile. + +Binding Go + +Gobind generates target language (Java or Objective-C) bindings for +each exported symbol in a Go package. The Go package you choose to +bind defines a cross-language interface. + +Bindings require additional Go code be generated, so using gobind +manually requires calling it twice, first with -lang=, where +target is either java or objc, and again with -lang=go. The generated +package can then be _ imported into a Go program, typically built +with -buildmode=c-archive for iOS or -buildmode=c-shared for Android. +These details are handled by the `gomobile bind` command. + +Passing Go objects to target languages + +Consider a type for counting: + + package mypkg + + type Counter struct { + Value int + } + + func (c *Counter) Inc() { c.Value++ } + + func New() *Counter { return &Counter{ 5 } } + +In Java, the generated bindings are, + + public abstract class Mypkg { + private Mypkg() {} + public static final class Counter { + public void Inc(); + public long GetValue(); + public void SetValue(long value); + } + public static Counter New(); + } + +The package-level function New can be called like so: + + Counter c = Mypkg.New() + +returns a Java Counter, which is a proxy for a Go *Counter. Calling the Inc +and Get methods will call the Go implementations of these methods. + +Similarly, the same Go package will generate the Objective-C interface + + @class GoMypkgCounter; + + @interface GoMypkgCounter : NSObject { + } + + @property(strong, readonly) GoSeqRef *ref; + - (void)Inc; + - (int64_t)Value; + - (void)setValue:(int64_t)v; + @end + + FOUNDATION_EXPORT GoMypkgCounter* GoMypkgNewCounter(); + +The equivalent of calling New in Go is GoMypkgNewCounter in Objective-C. +The returned GoMypkgCounter* holds a reference to an underlying Go +*Counter. + +Passing target language objects to Go + +For a Go interface: + + package myfmt + + type Printer interface { + Print(s string) + } + + func PrintHello(p Printer) { + p.Print("Hello, World!") + } + +gobind generates a Java stub that can be used to implement a Printer: + + public abstract class Myfmt { + private Myfmt() {} + public interface Printer { + public void Print(String s); + + public static abstract class Stub implements Printer { + ... + } + + ... + } + + public static void PrintHello(Printer p) { ... } + } + +You can extend Myfmt.Printer.Stub to implement the Printer interface, and +pass it to Go using the PrintHello package function: + + public class SysPrint extends Myfmt.Printer.Stub { + public void Print(String s) { + System.out.println(s); + } + } + +The Java implementation can be used like so: + + Myfmt.Printer printer = new SysPrint(); + Myfmt.PrintHello(printer); + +Objective-C support for interfaces will be available soon. + +Type restrictions + +At present, only a subset of Go types are supported. + +All exported symbols in the package must have types that are supported. +Supported types include: + + - Signed integer and floating point types. + + - String and boolean types. + + - Byte slice types. + + - Any function type all of whose parameters and results have + supported types. Functions must return either no results, + one result, or two results where the type of the second is + the built-in 'error' type. + + - Any interface type, all of whose exported methods have + supported function types. + + - Any struct type, all of whose exported methods have + supported function types and all of whose exported fields + have supported types. + +Unexported symbols have no effect on the cross-language interface, and +as such are not restricted. + +The set of supported types will eventually be expanded to cover more +Go types, but this is a work in progress. + +Exceptions and panics are not yet supported. If either pass a language +boundary, the program will exit. + +Avoid reference cycles + +The language bindings maintain a reference to each object that has been +proxied. When a proxy object becomes unreachable, its finalizer reports +this fact to the object's native side, so that the reference can be +removed, potentially allowing the object to be reclaimed by its native +garbage collector. The mechanism is symmetric. + +However, it is possible to create a reference cycle between Go and +objects in target languages, via proxies, meaning objects cannot be +collected. This causes a memory leak. + +For example, in Java: if a Go object G holds a reference to the Go +proxy of a Java object J, and J holds a reference to the Java proxy +of G, then the language bindings on each side must keep G and J live +even if they are otherwise unreachable. + +We recommend that implementations of foreign interfaces do not hold +references to proxies of objects. That is: if you extend a Stub in +Java, do not store an instance of Seq.Object inside it. + +Further reading + +Examples can be found in http://golang.org/x/mobile/example. + +Design doc: http://golang.org/s/gobind +*/ +package main // import "golang.org/x/mobile/cmd/gobind" diff --git a/src/golang.org/x/mobile/cmd/gobind/gen.go b/src/golang.org/x/mobile/cmd/gobind/gen.go new file mode 100644 index 0000000000..377992acdd --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gobind/gen.go @@ -0,0 +1,162 @@ +// Copyright 2014 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. + +package main + +import ( + "go/ast" + "go/build" + "go/parser" + "go/scanner" + "go/token" + "go/types" + "io" + "os" + "path/filepath" + "unicode" + "unicode/utf8" + + "golang.org/x/mobile/bind" + "golang.org/x/mobile/internal/loader" +) + +func genPkg(pkg *build.Package) { + files := parseFiles(pkg.Dir, pkg.GoFiles) + if len(files) == 0 { + return // some error has been reported + } + + conf := loader.Config{ + Fset: fset, + AllowErrors: true, + } + conf.TypeChecker.IgnoreFuncBodies = true + conf.TypeChecker.FakeImportC = true + conf.TypeChecker.DisableUnusedImportCheck = true + var tcErrs []error + conf.TypeChecker.Error = func(err error) { + tcErrs = append(tcErrs, err) + } + conf.CreateFromFiles(pkg.ImportPath, files...) + program, err := conf.Load() + if err != nil { + for _, err := range tcErrs { + errorf("%v", err) + } + errorf("%v", err) + return + } + p := program.Created[0].Pkg + + fname := defaultFileName(*lang, p) + switch *lang { + case "java": + w, closer := writer(fname, p) + processErr(bind.GenJava(w, fset, p)) + closer() + case "go": + w, closer := writer(fname, p) + processErr(bind.GenGo(w, fset, p)) + closer() + case "objc": + if fname == "" { + processErr(bind.GenObjc(os.Stdout, fset, p, true)) + processErr(bind.GenObjc(os.Stdout, fset, p, false)) + } else { + hname := fname[:len(fname)-2] + ".h" + w, closer := writer(hname, p) + processErr(bind.GenObjc(w, fset, p, true)) + closer() + w, closer = writer(fname, p) + processErr(bind.GenObjc(w, fset, p, false)) + closer() + } + default: + errorf("unknown target language: %q", *lang) + } +} + +func processErr(err error) { + if err != nil { + if list, _ := err.(bind.ErrorList); len(list) > 0 { + for _, err := range list { + errorf("%v", err) + } + } else { + errorf("%v", err) + } + } +} + +var fset = token.NewFileSet() + +func parseFiles(dir string, filenames []string) []*ast.File { + var files []*ast.File + hasErr := false + for _, filename := range filenames { + path := filepath.Join(dir, filename) + file, err := parser.ParseFile(fset, path, nil, parser.AllErrors) + if err != nil { + hasErr = true + if list, _ := err.(scanner.ErrorList); len(list) > 0 { + for _, err := range list { + errorf("%v", err) + } + } else { + errorf("%v", err) + } + } + files = append(files, file) + } + if hasErr { + return nil + } + return files +} + +func writer(fname string, pkg *types.Package) (w io.Writer, closer func()) { + if fname == "" { + return os.Stdout, func() { return } + } + + dir := filepath.Dir(fname) + if err := os.MkdirAll(dir, 0755); err != nil { + errorf("invalid output dir: %v", err) + os.Exit(exitStatus) + } + + f, err := os.Create(fname) + if err != nil { + errorf("invalid output dir: %v", err) + os.Exit(exitStatus) + } + closer = func() { + if err := f.Close(); err != nil { + errorf("error in closing output file: %v", err) + } + } + return f, closer +} + +func defaultFileName(lang string, pkg *types.Package) string { + if *outdir == "" { + return "" + } + + switch lang { + case "java": + firstRune, size := utf8.DecodeRuneInString(pkg.Name()) + className := string(unicode.ToUpper(firstRune)) + pkg.Name()[size:] + return filepath.Join(*outdir, className+".java") + case "go": + return filepath.Join(*outdir, "go_"+pkg.Name()+".go") + case "objc": + firstRune, size := utf8.DecodeRuneInString(pkg.Name()) + className := string(unicode.ToUpper(firstRune)) + pkg.Name()[size:] + return filepath.Join(*outdir, "Go"+className+".m") + } + errorf("unknown target language: %q", lang) + os.Exit(exitStatus) + return "" +} diff --git a/src/golang.org/x/mobile/cmd/gobind/main.go b/src/golang.org/x/mobile/cmd/gobind/main.go new file mode 100644 index 0000000000..6d1f1c70a9 --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gobind/main.go @@ -0,0 +1,48 @@ +// Copyright 2014 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. + +package main + +import ( + "flag" + "fmt" + "go/build" + "log" + "os" +) + +var ( + lang = flag.String("lang", "java", "target language for bindings, either java, go, or objc (experimental).") + outdir = flag.String("outdir", "", "result will be written to the directory instead of stdout.") +) + +var usage = `The Gobind tool generates Java language bindings for Go. + +For usage details, see doc.go.` + +func main() { + flag.Parse() + + cwd, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + for _, arg := range flag.Args() { + pkg, err := build.Import(arg, cwd, 0) + if err != nil { + fmt.Fprintf(os.Stderr, "%s: %v\n", arg, err) + os.Exit(1) + } + genPkg(pkg) + } + os.Exit(exitStatus) +} + +var exitStatus = 0 + +func errorf(format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, format, args...) + fmt.Fprintln(os.Stderr) + exitStatus = 1 +} diff --git a/src/golang.org/x/mobile/cmd/gomobile/binary_xml.go b/src/golang.org/x/mobile/cmd/gomobile/binary_xml.go new file mode 100644 index 0000000000..7b11aaad3f --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/binary_xml.go @@ -0,0 +1,694 @@ +// Copyright 2015 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. + +package main + +import ( + "encoding/xml" + "fmt" + "io" + "sort" + "strconv" + "strings" + "unicode/utf16" +) + +// binaryXML converts XML into Android's undocumented binary XML format. +// +// The best source of information on this format seems to be the source code +// in AOSP frameworks-base. Android "resource" types seem to describe the +// encoded bytes, in particular: +// +// ResChunk_header +// ResStringPool_header +// ResXMLTree_node +// +// These are defined in: +// +// https://android.googlesource.com/platform/frameworks/base/+/master/include/androidfw/ResourceTypes.h +// +// The rough format of the file is a resource chunk containing a sequence of +// chunks. Each chunk is made up of a header and a body. The header begins with +// the contents of the ResChunk_header struct, which includes the size of both +// the header and the body. +// +// Both the header and body are 4-byte aligned. +// +// Values are encoded as little-endian. +// +// The android source code for encoding is done in the aapt tool. Its source +// code lives in AOSP: +// +// https://android.googlesource.com/platform/frameworks/base.git/+/master/tools/aapt +// +// A sample layout: +// +// File Header (ResChunk_header, type XML) +// Chunk: String Pool (type STRING_POOL) +// Sequence of strings, each with the format: +// uint16 length +// uint16 extended_length -- only if top bit set on length +// UTF-16LE string +// two zero bytes +// Resource Map +// The [i]th 4-byte entry in the resource map corresponds with +// the [i]th string from the string pool. The 4-bytes are a +// Resource ID constant defined: +// http://developer.android.com/reference/android/R.attr.html +// This appears to be a way to map strings onto enum values. +// Chunk: Namespace Start (ResXMLTree_node; ResXMLTree_namespaceExt) +// Chunk: Element Start +// ResXMLTree_node +// ResXMLTree_attrExt +// ResXMLTree_attribute (repeated attributeCount times) +// Chunk: Element End +// (ResXMLTree_node; ResXMLTree_endElementExt) +// ... +// Chunk: Namespace End +func binaryXML(r io.Reader) ([]byte, error) { + lr := &lineReader{r: r} + d := xml.NewDecoder(lr) + + pool := new(binStringPool) + depth := 0 + elements := []chunk{} + namespaceEnds := make(map[int][]binEndNamespace) + + for { + line := lr.line(d.InputOffset()) + tok, err := d.Token() + if err != nil { + if err == io.EOF { + break + } + return nil, err + } + switch tok := tok.(type) { + case xml.StartElement: + // Intercept namespace definitions. + var attr []*binAttr + for _, a := range tok.Attr { + if a.Name.Space == "xmlns" { + elements = append(elements, binStartNamespace{ + line: line, + prefix: pool.get(a.Name.Local), + url: pool.get(a.Value), + }) + namespaceEnds[depth] = append([]binEndNamespace{{ + line: line, + prefix: pool.get(a.Name.Local), + url: pool.get(a.Value), + }}, namespaceEnds[depth]...) + continue + } + ba, err := pool.getAttr(a) + if err != nil { + return nil, fmt.Errorf("%d: %s: %v", line, a.Name.Local, err) + } + attr = append(attr, ba) + } + + depth++ + elements = append(elements, &binStartElement{ + line: line, + ns: pool.getNS(tok.Name.Space), + name: pool.get(tok.Name.Local), + attr: attr, + }) + case xml.EndElement: + elements = append(elements, &binEndElement{ + line: line, + ns: pool.getNS(tok.Name.Space), + name: pool.get(tok.Name.Local), + }) + depth-- + if nsEnds := namespaceEnds[depth]; len(nsEnds) > 0 { + delete(namespaceEnds, depth) + for _, nsEnd := range nsEnds { + elements = append(elements, nsEnd) + } + } + case xml.CharData: + // The aapt tool appears to "compact" leading and + // trailing whitepsace. See XMLNode::removeWhitespace in + // https://android.googlesource.com/platform/frameworks/base.git/+/master/tools/aapt/XMLNode.cpp + if len(tok) == 0 { + continue + } + start, end := 0, len(tok) + for start < len(tok) && isSpace(tok[start]) { + start++ + } + for end > start && isSpace(tok[end-1]) { + end-- + } + if start == end { + continue // all whitespace, skip it + } + + // Preserve one character of whitespace. + if start > 0 { + start-- + } + if end < len(tok) { + end++ + } + + elements = append(elements, &binCharData{ + line: line, + data: pool.get(string(tok[start:end])), + }) + case xml.Comment: + // Ignored by Anroid Binary XML format. + case xml.ProcInst: + // Ignored by Anroid Binary XML format? + case xml.Directive: + // Ignored by Anroid Binary XML format. + default: + return nil, fmt.Errorf("apk: unexpected token: %v (%T)", tok, tok) + } + } + + sortPool(pool) + for _, e := range elements { + if e, ok := e.(*binStartElement); ok { + sortAttr(e, pool) + } + } + + resMap := &binResMap{pool} + + size := 8 + pool.size() + resMap.size() + for _, e := range elements { + size += e.size() + } + + b := make([]byte, 0, size) + b = appendHeader(b, headerXML, size) + b = pool.append(b) + b = resMap.append(b) + for _, e := range elements { + b = e.append(b) + } + + return b, nil +} + +func isSpace(b byte) bool { + switch b { + case '\t', '\n', '\v', '\f', '\r', ' ', 0x85, 0xA0: + return true + } + return false +} + +type headerType uint16 + +const ( + headerXML headerType = 0x0003 + headerStringPool = 0x0001 + headerResourceMap = 0x0180 + headerStartNamespace = 0x0100 + headerEndNamespace = 0x0101 + headerStartElement = 0x0102 + headerEndElement = 0x0103 + headerCharData = 0x0104 +) + +func appendU16(b []byte, v uint16) []byte { + return append(b, byte(v), byte(v>>8)) +} + +func appendU32(b []byte, v uint32) []byte { + return append(b, byte(v), byte(v>>8), byte(v>>16), byte(v>>24)) +} + +func appendHeader(b []byte, typ headerType, size int) []byte { + b = appendU16(b, uint16(typ)) + b = appendU16(b, 8) + b = appendU16(b, uint16(size)) + b = appendU16(b, 0) + return b +} + +// Attributes of the form android:key are mapped to resource IDs, which are +// embedded into the Binary XML format. +// +// http://developer.android.com/reference/android/R.attr.html +var resourceCodes = map[string]uint32{ + "versionCode": 0x0101021b, + "versionName": 0x0101021c, + "minSdkVersion": 0x0101020c, + "windowFullscreen": 0x0101020d, + "label": 0x01010001, + "hasCode": 0x0101000c, + "debuggable": 0x0101000f, + "name": 0x01010003, + "configChanges": 0x0101001f, + "value": 0x01010024, +} + +// http://developer.android.com/reference/android/R.attr.html#configChanges +var configChanges = map[string]uint32{ + "mcc": 0x0001, + "mnc": 0x0002, + "locale": 0x0004, + "touchscreen": 0x0008, + "keyboard": 0x0010, + "keyboardHidden": 0x0020, + "navigation": 0x0040, + "orientation": 0x0080, + "screenLayout": 0x0100, + "uiMode": 0x0200, + "screenSize": 0x0400, + "smallestScreenSize": 0x0800, + "layoutDirection": 0x2000, + "fontScale": 0x40000000, +} + +type lineReader struct { + off int64 + lines []int64 + r io.Reader +} + +func (r *lineReader) Read(p []byte) (n int, err error) { + n, err = r.r.Read(p) + for i := 0; i < n; i++ { + if p[i] == '\n' { + r.lines = append(r.lines, r.off+int64(i)) + } + } + r.off += int64(n) + return n, err +} + +func (r *lineReader) line(pos int64) int { + return sort.Search(len(r.lines), func(i int) bool { + return pos < r.lines[i] + }) + 1 +} + +type bstring struct { + ind uint32 + str string + enc []byte // 2-byte length, utf16le, 2-byte zero +} + +type chunk interface { + size() int + append([]byte) []byte +} + +type binResMap struct { + pool *binStringPool +} + +func (p *binResMap) append(b []byte) []byte { + b = appendHeader(b, headerResourceMap, p.size()) + for _, bstr := range p.pool.s { + c, ok := resourceCodes[bstr.str] + if !ok { + break + } + b = appendU32(b, c) + } + return b +} + +func (p *binResMap) size() int { + count := 0 + for _, bstr := range p.pool.s { + if _, ok := resourceCodes[bstr.str]; !ok { + break + } + count++ + } + return 8 + 4*count +} + +type binStringPool struct { + s []*bstring + m map[string]*bstring +} + +func (p *binStringPool) get(str string) *bstring { + if p.m == nil { + p.m = make(map[string]*bstring) + } + res := p.m[str] + if res != nil { + return res + } + res = &bstring{ + ind: uint32(len(p.s)), + str: str, + } + p.s = append(p.s, res) + p.m[str] = res + + if len(str)>>16 > 0 { + panic(fmt.Sprintf("string lengths over 1<<15 not yet supported, got len %d for string that starts %q", len(str), str[:100])) + } + strUTF16 := utf16.Encode([]rune(str)) + res.enc = appendU16(nil, uint16(len(strUTF16))) + for _, w := range strUTF16 { + res.enc = appendU16(res.enc, w) + } + res.enc = appendU16(res.enc, 0) + return res +} + +func (p *binStringPool) getNS(ns string) *bstring { + if ns == "" { + // Register empty string for inclusion in output (like aapt), + // but do not reference it from namespace elements. + p.get("") + return nil + } + return p.get(ns) +} + +func (p *binStringPool) getAttr(attr xml.Attr) (*binAttr, error) { + a := &binAttr{ + ns: p.getNS(attr.Name.Space), + name: p.get(attr.Name.Local), + } + if attr.Name.Space != "http://schemas.android.com/apk/res/android" { + a.data = p.get(attr.Value) + return a, nil + } + + // Some android attributes have interesting values. + switch attr.Name.Local { + case "versionCode", "minSdkVersion": + v, err := strconv.Atoi(attr.Value) + if err != nil { + return nil, err + } + a.data = int(v) + case "hasCode", "debuggable": + v, err := strconv.ParseBool(attr.Value) + if err != nil { + return nil, err + } + a.data = v + case "configChanges": + v := uint32(0) + for _, c := range strings.Split(attr.Value, "|") { + v |= configChanges[c] + } + a.data = v + default: + a.data = p.get(attr.Value) + } + return a, nil +} + +const stringPoolPreamble = 0 + + 8 + // chunk header + 4 + // string count + 4 + // style count + 4 + // flags + 4 + // strings start + 4 + // styles start + 0 + +func (p *binStringPool) unpaddedSize() int { + strLens := 0 + for _, s := range p.s { + strLens += len(s.enc) + } + return stringPoolPreamble + 4*len(p.s) + strLens +} + +func (p *binStringPool) size() int { + size := p.unpaddedSize() + size += size % 0x04 + return size +} + +// overloaded for testing. +var ( + sortPool = func(p *binStringPool) { + sort.Sort(p) + + // Move resourceCodes to the front. + s := make([]*bstring, 0) + m := make(map[string]*bstring) + for str := range resourceCodes { + bstr := p.m[str] + if bstr == nil { + continue + } + bstr.ind = uint32(len(s)) + s = append(s, bstr) + m[str] = bstr + delete(p.m, str) + } + for _, bstr := range p.m { + bstr.ind = uint32(len(s)) + s = append(s, bstr) + } + p.s = s + p.m = m + } + sortAttr = func(e *binStartElement, p *binStringPool) {} +) + +func (b *binStringPool) Len() int { return len(b.s) } +func (b *binStringPool) Less(i, j int) bool { return b.s[i].str < b.s[j].str } +func (b *binStringPool) Swap(i, j int) { + b.s[i], b.s[j] = b.s[j], b.s[i] + b.s[i].ind, b.s[j].ind = b.s[j].ind, b.s[i].ind +} + +func (p *binStringPool) append(b []byte) []byte { + stringsStart := uint32(stringPoolPreamble + 4*len(p.s)) + b = appendU16(b, uint16(headerStringPool)) + b = appendU16(b, 0x1c) // chunk header size + b = appendU16(b, uint16(p.size())) + b = appendU16(b, 0) + b = appendU32(b, uint32(len(p.s))) + b = appendU32(b, 0) // style count + b = appendU32(b, 0) // flags + b = appendU32(b, stringsStart) + b = appendU32(b, 0) // styles start + + off := 0 + for _, bstr := range p.s { + b = appendU32(b, uint32(off)) + off += len(bstr.enc) + } + for _, bstr := range p.s { + b = append(b, bstr.enc...) + } + + for i := p.unpaddedSize() % 0x04; i > 0; i-- { + b = append(b, 0) + } + return b +} + +type binStartElement struct { + line int + ns *bstring + name *bstring + attr []*binAttr +} + +func (e *binStartElement) size() int { + return 8 + // chunk header + 4 + // line number + 4 + // comment + 4 + // ns + 4 + // name + 2 + 2 + 2 + // attribute start, size, count + 2 + 2 + 2 + // id/class/style index + len(e.attr)*(4+4+4+4+4) +} + +func (e *binStartElement) append(b []byte) []byte { + b = appendU16(b, uint16(headerStartElement)) + b = appendU16(b, 0x10) // chunk header size + b = appendU16(b, uint16(e.size())) + b = appendU16(b, 0) + b = appendU32(b, uint32(e.line)) + b = appendU32(b, 0xffffffff) // comment + if e.ns == nil { + b = appendU32(b, 0xffffffff) + } else { + b = appendU32(b, e.ns.ind) + } + b = appendU32(b, e.name.ind) + b = appendU16(b, 0x14) // attribute start + b = appendU16(b, 0x14) // attribute size + b = appendU16(b, uint16(len(e.attr))) + b = appendU16(b, 0) // ID index (none) + b = appendU16(b, 0) // class index (none) + b = appendU16(b, 0) // style index (none) + for _, a := range e.attr { + b = a.append(b) + } + return b +} + +type binAttr struct { + ns *bstring + name *bstring + data interface{} // either int (INT_DEC) or *bstring (STRING) +} + +func (a *binAttr) append(b []byte) []byte { + if a.ns != nil { + b = appendU32(b, a.ns.ind) + } else { + b = appendU32(b, 0xffffffff) + } + b = appendU32(b, a.name.ind) + switch v := a.data.(type) { + case int: + b = appendU32(b, 0xffffffff) // raw value + b = appendU16(b, 8) // size + b = append(b, 0) // unused padding + b = append(b, 0x10) // INT_DEC + b = appendU32(b, uint32(v)) + case bool: + b = appendU32(b, 0xffffffff) // raw value + b = appendU16(b, 8) // size + b = append(b, 0) // unused padding + b = append(b, 0x12) // INT_BOOLEAN + if v { + b = appendU32(b, 0xffffffff) + } else { + b = appendU32(b, 0) + } + case uint32: + b = appendU32(b, 0xffffffff) // raw value + b = appendU16(b, 8) // size + b = append(b, 0) // unused padding + b = append(b, 0x11) // INT_HEX + b = appendU32(b, uint32(v)) + case *bstring: + b = appendU32(b, v.ind) // raw value + b = appendU16(b, 8) // size + b = append(b, 0) // unused padding + b = append(b, 0x03) // STRING + b = appendU32(b, v.ind) + default: + panic(fmt.Sprintf("unexpected attr type: %T (%v)", v, v)) + } + return b +} + +type binEndElement struct { + line int + ns *bstring + name *bstring + attr []*binAttr +} + +func (*binEndElement) size() int { + return 8 + // chunk header + 4 + // line number + 4 + // comment + 4 + // ns + 4 // name +} + +func (e *binEndElement) append(b []byte) []byte { + b = appendU16(b, uint16(headerEndElement)) + b = appendU16(b, 0x10) // chunk header size + b = appendU16(b, uint16(e.size())) + b = appendU16(b, 0) + b = appendU32(b, uint32(e.line)) + b = appendU32(b, 0xffffffff) // comment + if e.ns == nil { + b = appendU32(b, 0xffffffff) + } else { + b = appendU32(b, e.ns.ind) + } + b = appendU32(b, e.name.ind) + return b +} + +type binStartNamespace struct { + line int + prefix *bstring + url *bstring +} + +func (binStartNamespace) size() int { + return 8 + // chunk header + 4 + // line number + 4 + // comment + 4 + // prefix + 4 // url +} + +func (e binStartNamespace) append(b []byte) []byte { + b = appendU16(b, uint16(headerStartNamespace)) + b = appendU16(b, 0x10) // chunk header size + b = appendU16(b, uint16(e.size())) + b = appendU16(b, 0) + b = appendU32(b, uint32(e.line)) + b = appendU32(b, 0xffffffff) // comment + b = appendU32(b, e.prefix.ind) + b = appendU32(b, e.url.ind) + return b +} + +type binEndNamespace struct { + line int + prefix *bstring + url *bstring +} + +func (binEndNamespace) size() int { + return 8 + // chunk header + 4 + // line number + 4 + // comment + 4 + // prefix + 4 // url +} + +func (e binEndNamespace) append(b []byte) []byte { + b = appendU16(b, uint16(headerEndNamespace)) + b = appendU16(b, 0x10) // chunk header size + b = appendU16(b, uint16(e.size())) + b = appendU16(b, 0) + b = appendU32(b, uint32(e.line)) + b = appendU32(b, 0xffffffff) // comment + b = appendU32(b, e.prefix.ind) + b = appendU32(b, e.url.ind) + return b +} + +type binCharData struct { + line int + data *bstring +} + +func (*binCharData) size() int { + return 8 + // chunk header + 4 + // line number + 4 + // comment + 4 + // data + 8 // junk +} + +func (e *binCharData) append(b []byte) []byte { + b = appendU16(b, uint16(headerCharData)) + b = appendU16(b, 0x10) // chunk header size + b = appendU16(b, 0x1c) // size + b = appendU16(b, 0) + b = appendU32(b, uint32(e.line)) + b = appendU32(b, 0xffffffff) // comment + b = appendU32(b, e.data.ind) + b = appendU16(b, 0x08) + b = appendU16(b, 0) + b = appendU16(b, 0) + b = appendU16(b, 0) + return b +} diff --git a/src/golang.org/x/mobile/cmd/gomobile/binary_xml_test.go b/src/golang.org/x/mobile/cmd/gomobile/binary_xml_test.go new file mode 100644 index 0000000000..5f4b227976 --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/binary_xml_test.go @@ -0,0 +1,756 @@ +// Copyright 2015 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. + +package main + +import ( + "bytes" + "flag" + "io/ioutil" + "log" + "testing" +) + +var dump = flag.Bool("dump", false, "dump junk.bin binary output") + +var ( + origSortPool = sortPool + origSortAttr = sortAttr +) + +func TestBinaryXML(t *testing.T) { + sortPool, sortAttr = sortToMatchTest, sortAttrToMatchTest + defer func() { sortPool, sortAttr = origSortPool, origSortAttr }() + + got, err := binaryXML(bytes.NewBufferString(input)) + if err != nil { + t.Fatal(err) + } + if *dump { + if err := ioutil.WriteFile("junk.bin", got, 0660); err != nil { + t.Fatal(err) + } + } + + skipByte := map[int]bool{ + 0x04ec: true, // line number of fake off by one + 0x0610: true, // line number of fake off by one + 0x064c: true, // line number of CData off by one + 0x06a0: true, // line number of fake off by one + 0x06f0: true, // line number of fake off by one + 0x0768: true, // line number of fake *end namespace* off by one + } + + for i, o := range output { + if skipByte[i] { + continue + } + if i >= len(got) || o != got[i] { + t.Errorf("mismatch at %04x", i) + break + } + } +} + +// The output of the Android encoder seems to be arbitrary. So for testing, +// we sort the string pool order to match the output we have seen. +func sortToMatchTest(p *binStringPool) { + var names = []string{ + "versionCode", + "versionName", + "minSdkVersion", + "label", + "hasCode", + "debuggable", + "name", + "configChanges", + "value", + "android", + "http://schemas.android.com/apk/res/android", + "", + "package", + "manifest", + "com.zentus.balloon", + "1.0", + "uses-sdk", + "application", + "Balloon世界", + "activity", + "android.app.NativeActivity", + "Balloon", + "meta-data", + "android.app.lib_name", + "balloon", + "intent-filter", + "\there is some text\n", + "action", + "android.intent.action.MAIN", + "category", + "android.intent.category.LAUNCHER", + } + + s := make([]*bstring, 0) + m := make(map[string]*bstring) + + for _, str := range names { + bstr := p.m[str] + if bstr == nil { + log.Printf("missing %q", str) + continue + } + bstr.ind = uint32(len(s)) + s = append(s, bstr) + m[str] = bstr + delete(p.m, str) + } + // add unexpected strings + for str, bstr := range p.m { + log.Printf("unexpected %q", str) + bstr.ind = uint32(len(s)) + s = append(s, bstr) + } + p.s = s + p.m = m +} + +func sortAttrToMatchTest(e *binStartElement, p *binStringPool) { + order := []string{ + "versionCode", + "versionName", + "versionPackage", + + "label", + "name", + "configChanges", + } + ordered := make([]*binAttr, len(order)) + +outer: + for i, n := range order { + for j, a := range e.attr { + if a != nil && a.name.str == n { + ordered[i] = a + e.attr[j] = nil + continue outer + } + } + } + var attr []*binAttr + for _, a := range ordered { + if a != nil { + attr = append(attr, a) + } + } + for _, a := range e.attr { + if a != nil { + attr = append(attr, a) + } + } + e.attr = attr +} + +// Hexdump of output generated by the Android SDK's ant build system. +// Annotated after studying Android source code. +var output = []byte{ + /* 0000 */ 0x03, 0x00, 0x08, 0x00, // chunk header XML + /* 0004 */ 0x78, 0x07, 0x00, 0x00, // chunk size 1912 + + /* 0008 */ 0x01, 0x00, 0x1c, 0x00, // chunk header STRING_POOL + /* 000c */ 0x00, 0x04, 0x00, 0x00, // chunk size 1024 + /* 0010 */ 0x1f, 0x00, 0x00, 0x00, // string count 31 + /* 0014 */ 0x00, 0x00, 0x00, 0x00, // style count 0 + /* 0018 */ 0x00, 0x00, 0x00, 0x00, // flags (none set means UTF-16) + /* 001c */ 0x98, 0x00, 0x00, 0x00, // strings_start 0x98+0x08 = 0xa0 + /* 0020 */ 0x00, 0x00, 0x00, 0x00, // styles_start (none) + /* 0024 */ 0x00, 0x00, 0x00, 0x00, // string offset [0x00] (from strings_start) + /* 0028 */ 0x1a, 0x00, 0x00, 0x00, // string offset [0x01] + /* 002c */ 0x34, 0x00, 0x00, 0x00, // string offset [0x02] + /* 0030 */ 0x52, 0x00, 0x00, 0x00, // string offset [0x03] + /* 0034 */ 0x60, 0x00, 0x00, 0x00, // string offset [0x04] + /* 0038 */ 0x72, 0x00, 0x00, 0x00, // string offset [0x05] + /* 003c */ 0x8a, 0x00, 0x00, 0x00, // string offset [0x06] + /* 0040 */ 0x96, 0x00, 0x00, 0x00, // string offset [0x07] + /* 0044 */ 0xb4, 0x00, 0x00, 0x00, // string offset [0x08] + /* 0048 */ 0xc2, 0x00, 0x00, 0x00, // string offset [0x09] + /* 004c */ 0xd4, 0x00, 0x00, 0x00, // string offset [0x0a] + /* 0050 */ 0x2c, 0x01, 0x00, 0x00, // string offset [0x0b] + /* 0054 */ 0x30, 0x01, 0x00, 0x00, // string offset [0x0c] + /* 0058 */ 0x42, 0x01, 0x00, 0x00, // string offset [0x0d] + /* 005c */ 0x56, 0x01, 0x00, 0x00, // string offset [0x0e] + /* 0060 */ 0x7e, 0x01, 0x00, 0x00, // string offset [0x0f] + /* 0064 */ 0x88, 0x01, 0x00, 0x00, // string offset [0x10] + /* 0068 */ 0x9c, 0x01, 0x00, 0x00, // string offset [0x11] + /* 006c */ 0xb6, 0x01, 0x00, 0x00, // string offset [0x12] + /* 0070 */ 0xcc, 0x01, 0x00, 0x00, // string offset [0x13] + /* 0074 */ 0xe0, 0x01, 0x00, 0x00, // string offset [0x14] + /* 0078 */ 0x18, 0x02, 0x00, 0x00, // string offset [0x15] + /* 007c */ 0x2a, 0x02, 0x00, 0x00, // string offset [0x16] + /* 0080 */ 0x40, 0x02, 0x00, 0x00, // string offset [0x17] + /* 0084 */ 0x6c, 0x02, 0x00, 0x00, // string offset [0x18] + /* 0088 */ 0x7e, 0x02, 0x00, 0x00, // string offset [0x19] + /* 008c */ 0x9c, 0x02, 0x00, 0x00, // string offset [0x1a] + /* 0090 */ 0xc6, 0x02, 0x00, 0x00, // string offset [0x1b] + /* 0094 */ 0xd6, 0x02, 0x00, 0x00, // string offset [0x1c] + /* 0098 */ 0x0e, 0x03, 0x00, 0x00, // string offset [0x1d] + /* 009c */ 0x22, 0x03, 0x00, 0x00, // string offset [0x1e] + /* 00a0 */ 0x0b, 0x00, 0x76, 0x00, // [0x00] len=11 value="versionCode" + /* 00a4 */ 0x65, 0x00, 0x72, 0x00, + /* 00a8 */ 0x73, 0x00, 0x69, 0x00, + /* 00ac */ 0x6f, 0x00, 0x6e, 0x00, + /* 00b0 */ 0x43, 0x00, 0x6f, 0x00, + /* 00b4 */ 0x64, 0x00, 0x65, 0x00, + /* 00b8 */ 0x00, 0x00, + /* 00ba */ 0x0b, 0x00, // [0x01] len=11 value="versionName" + /* 00bc */ 0x76, 0x00, 0x65, 0x00, + /* 00c0 */ 0x72, 0x00, 0x73, 0x00, + /* 00c4 */ 0x69, 0x00, 0x6f, 0x00, + /* 00c8 */ 0x6e, 0x00, 0x4e, 0x00, + /* 00cc */ 0x61, 0x00, 0x6d, 0x00, + /* 00d0 */ 0x65, 0x00, 0x00, 0x00, + /* 00d4 */ 0x0d, 0x00, 0x6d, 0x00, // [0x02] len=13 value="minSdkVersion" + /* 00d8 */ 0x69, 0x00, 0x6e, 0x00, + /* 00dc */ 0x53, 0x00, 0x64, 0x00, + /* 00e0 */ 0x6b, 0x00, 0x56, 0x00, + /* 00e4 */ 0x65, 0x00, 0x72, 0x00, + /* 00e8 */ 0x73, 0x00, 0x69, 0x00, + /* 00ec */ 0x6f, 0x00, 0x6e, 0x00, + /* 00f0 */ 0x00, 0x00, + /* 00f2 */ 0x05, 0x00, // [0x03] len=5 value="label" + /* 00f4 */ 0x6c, 0x00, 0x61, 0x00, + /* 00f8 */ 0x62, 0x00, 0x65, 0x00, + /* 00fc */ 0x6c, 0x00, 0x00, 0x00, + /* 0100 */ 0x07, 0x00, 0x68, 0x00, // [0x04] len=7 value="hasCode" + /* 0104 */ 0x61, 0x00, 0x73, 0x00, + /* 0108 */ 0x43, 0x00, 0x6f, 0x00, + /* 010c */ 0x64, 0x00, 0x65, 0x00, + /* 0110 */ 0x00, 0x00, + /* 0112 */ 0x0a, 0x00, // [0x05] len=10 value="debuggable" + /* 0114 */ 0x64, 0x00, 0x65, 0x00, + /* 0118 */ 0x62, 0x00, 0x75, 0x00, + /* 011c */ 0x67, 0x00, 0x67, 0x00, + /* 0120 */ 0x61, 0x00, 0x62, 0x00, + /* 0124 */ 0x6c, 0x00, 0x65, 0x00, + /* 0128 */ 0x00, 0x00, + /* 012a */ 0x04, 0x00, // [0x06] len=4 value="name" + /* 012c */ 0x6e, 0x00, 0x61, 0x00, + /* 0130 */ 0x6d, 0x00, 0x65, 0x00, + /* 0134 */ 0x00, 0x00, + /* 0136 */ 0x0d, 0x00, // [0x07] len=13 value="configChanges" + /* 0138 */ 0x63, 0x00, 0x6f, 0x00, + /* 013c */ 0x6e, 0x00, 0x66, 0x00, + /* 0140 */ 0x69, 0x00, 0x67, 0x00, + /* 0144 */ 0x43, 0x00, 0x68, 0x00, + /* 0148 */ 0x61, 0x00, 0x6e, 0x00, + /* 014c */ 0x67, 0x00, 0x65, 0x00, + /* 0150 */ 0x73, 0x00, 0x00, 0x00, + /* 0154 */ 0x05, 0x00, 0x76, 0x00, // [0x08] len=5 value="value" + /* 0158 */ 0x61, 0x00, 0x6c, 0x00, + /* 015c */ 0x75, 0x00, 0x65, 0x00, + /* 0160 */ 0x00, 0x00, + /* 0162 */ 0x07, 0x00, // [0x09] len=7 value="android" + /* 0164 */ 0x61, 0x00, 0x6e, 0x00, + /* 0168 */ 0x64, 0x00, 0x72, 0x00, + /* 016c */ 0x6f, 0x00, 0x69, 0x00, + /* 0170 */ 0x64, 0x00, 0x00, 0x00, + /* 0174 */ 0x2a, 0x00, 0x68, 0x00, // [0x0a] len=42 value="http://schemas.android.com/apk/res/android" + /* 0178 */ 0x74, 0x00, 0x74, 0x00, + /* 017c */ 0x70, 0x00, 0x3a, 0x00, + /* 0180 */ 0x2f, 0x00, 0x2f, 0x00, + /* 0184 */ 0x73, 0x00, 0x63, 0x00, + /* 0188 */ 0x68, 0x00, 0x65, 0x00, + /* 018c */ 0x6d, 0x00, 0x61, 0x00, + /* 0190 */ 0x73, 0x00, 0x2e, 0x00, + /* 0194 */ 0x61, 0x00, 0x6e, 0x00, + /* 0198 */ 0x64, 0x00, 0x72, 0x00, + /* 019c */ 0x6f, 0x00, 0x69, 0x00, + /* 01a0 */ 0x64, 0x00, 0x2e, 0x00, + /* 01a4 */ 0x63, 0x00, 0x6f, 0x00, + /* 01a8 */ 0x6d, 0x00, 0x2f, 0x00, + /* 01ac */ 0x61, 0x00, 0x70, 0x00, + /* 01b0 */ 0x6b, 0x00, 0x2f, 0x00, + /* 01b4 */ 0x72, 0x00, 0x65, 0x00, + /* 01b8 */ 0x73, 0x00, 0x2f, 0x00, + /* 01bc */ 0x61, 0x00, 0x6e, 0x00, + /* 01c0 */ 0x64, 0x00, 0x72, 0x00, + /* 01c4 */ 0x6f, 0x00, 0x69, 0x00, + /* 01c8 */ 0x64, 0x00, 0x00, 0x00, + /* 01cc */ 0x00, 0x00, 0x00, 0x00, // [0x0b] len=0 (sigh) + /* 01d0 */ 0x07, 0x00, 0x70, 0x00, // [0x0c] len=7 value="package" + /* 01d4 */ 0x61, 0x00, 0x63, 0x00, + /* 01d8 */ 0x6b, 0x00, 0x61, 0x00, + /* 01dc */ 0x67, 0x00, 0x65, 0x00, + /* 01e0 */ 0x00, 0x00, + /* 01e2 */ 0x08, 0x00, // [0x0d] len=8 value="manifest" + /* 01e4 */ 0x6d, 0x00, 0x61, 0x00, + /* 01e8 */ 0x6e, 0x00, 0x69, 0x00, + /* 01ec */ 0x66, 0x00, 0x65, 0x00, + /* 01f0 */ 0x73, 0x00, 0x74, 0x00, + /* 01f4 */ 0x00, 0x00, + /* 01f6 */ 0x12, 0x00, // [0x0e] len=12 value="com.zentus.balloon" + /* 01f8 */ 0x63, 0x00, 0x6f, 0x00, + /* 01fc */ 0x6d, 0x00, 0x2e, 0x00, + /* 0200 */ 0x7a, 0x00, 0x65, 0x00, + /* 0204 */ 0x6e, 0x00, 0x74, 0x00, + /* 0208 */ 0x75, 0x00, 0x73, 0x00, + /* 020c */ 0x2e, 0x00, 0x62, 0x00, + /* 0210 */ 0x61, 0x00, 0x6c, 0x00, + /* 0214 */ 0x6c, 0x00, 0x6f, 0x00, + /* 0218 */ 0x6f, 0x00, 0x6e, 0x00, + /* 021c */ 0x00, 0x00, + /* 021e */ 0x03, 0x00, // [0x0f] len=3 value="1.0" + /* 0220 */ 0x31, 0x00, 0x2e, 0x00, + /* 0224 */ 0x30, 0x00, 0x00, 0x00, + /* 0228 */ 0x08, 0x00, 0x75, 0x00, // [0x10] len=8 value="uses-sdk" + /* 022c */ 0x73, 0x00, 0x65, 0x00, + /* 0230 */ 0x73, 0x00, 0x2d, 0x00, + /* 0234 */ 0x73, 0x00, 0x64, 0x00, + /* 0238 */ 0x6b, 0x00, 0x00, 0x00, + /* 023c */ 0x0b, 0x00, 0x61, 0x00, // [0x11] len=11 value="application" + /* 0240 */ 0x70, 0x00, 0x70, 0x00, + /* 0244 */ 0x6c, 0x00, 0x69, 0x00, + /* 0248 */ 0x63, 0x00, 0x61, 0x00, + /* 024c */ 0x74, 0x00, 0x69, 0x00, + /* 0250 */ 0x6f, 0x00, 0x6e, 0x00, + /* 0254 */ 0x00, 0x00, + /* 0256 */ 0x09, 0x00, // [0x12] len=9 value="Balloon世界" (UTF16-LE, 0x4e16 is "16 4e", etc) + /* 0258 */ 0x42, 0x00, 0x61, 0x00, + /* 025c */ 0x6c, 0x00, 0x6c, 0x00, + /* 0260 */ 0x6f, 0x00, 0x6f, 0x00, + /* 0264 */ 0x6e, 0x00, 0x16, 0x4e, + /* 0268 */ 0x4c, 0x75, 0x00, 0x00, + /* 026c */ 0x08, 0x00, 0x61, 0x00, // [0x13] len=8 value="activity" + /* 0270 */ 0x63, 0x00, 0x74, 0x00, + /* 0274 */ 0x69, 0x00, 0x76, 0x00, + /* 0278 */ 0x69, 0x00, 0x74, 0x00, + /* 027c */ 0x79, 0x00, 0x00, 0x00, + /* 0280 */ 0x1a, 0x00, 0x61, 0x00, // [0x14] len=26 value="android.app.NativeActivity" + /* 0284 */ 0x6e, 0x00, 0x64, 0x00, + /* 0288 */ 0x72, 0x00, 0x6f, 0x00, + /* 028c */ 0x69, 0x00, 0x64, 0x00, + /* 0290 */ 0x2e, 0x00, 0x61, 0x00, + /* 0294 */ 0x70, 0x00, 0x70, 0x00, + /* 0298 */ 0x2e, 0x00, 0x4e, 0x00, + /* 029c */ 0x61, 0x00, 0x74, 0x00, + /* 02a0 */ 0x69, 0x00, 0x76, 0x00, + /* 02a4 */ 0x65, 0x00, 0x41, 0x00, + /* 02a8 */ 0x63, 0x00, 0x74, 0x00, + /* 02ac */ 0x69, 0x00, 0x76, 0x00, + /* 02b0 */ 0x69, 0x00, 0x74, 0x00, + /* 02b4 */ 0x79, 0x00, 0x00, 0x00, + /* 02b8 */ 0x07, 0x00, 0x42, 0x00, // [0x15] len=7 value="Balloon" + /* 02bc */ 0x61, 0x00, 0x6c, 0x00, + /* 02c0 */ 0x6c, 0x00, 0x6f, 0x00, + /* 02c4 */ 0x6f, 0x00, 0x6e, 0x00, + /* 02c8 */ 0x00, 0x00, + /* 02ca */ 0x09, 0x00, // [0x16] len=9 value="meta-data" + /* 02cc */ 0x6d, 0x00, 0x65, 0x00, + /* 02d0 */ 0x74, 0x00, 0x61, 0x00, + /* 02d4 */ 0x2d, 0x00, 0x64, 0x00, + /* 02d8 */ 0x61, 0x00, 0x74, 0x00, + /* 02dc */ 0x61, 0x00, 0x00, 0x00, + /* 02e0 */ 0x14, 0x00, 0x61, 0x00, // [0x17] len=20 value="android.app.lib_name" + /* 02e4 */ 0x6e, 0x00, 0x64, 0x00, + /* 02e8 */ 0x72, 0x00, 0x6f, 0x00, + /* 02ec */ 0x69, 0x00, 0x64, 0x00, + /* 02f0 */ 0x2e, 0x00, 0x61, 0x00, + /* 02f4 */ 0x70, 0x00, 0x70, 0x00, + /* 02f8 */ 0x2e, 0x00, 0x6c, 0x00, + /* 02fc */ 0x69, 0x00, 0x62, 0x00, + /* 0300 */ 0x5f, 0x00, 0x6e, 0x00, + /* 0304 */ 0x61, 0x00, 0x6d, 0x00, + /* 0308 */ 0x65, 0x00, 0x00, 0x00, + /* 030c */ 0x07, 0x00, 0x62, 0x00, // [0x18] len=7 value="balloon" + /* 0310 */ 0x61, 0x00, 0x6c, 0x00, + /* 0314 */ 0x6c, 0x00, 0x6f, 0x00, + /* 0318 */ 0x6f, 0x00, 0x6e, 0x00, + /* 031c */ 0x00, 0x00, + /* 031e */ 0x0d, 0x00, // [0x19] len=13 value="intent-filter" + /* 0320 */ 0x69, 0x00, 0x6e, 0x00, + /* 0324 */ 0x74, 0x00, 0x65, 0x00, + /* 0328 */ 0x6e, 0x00, 0x74, 0x00, + /* 032c */ 0x2d, 0x00, 0x66, 0x00, + /* 0330 */ 0x69, 0x00, 0x6c, 0x00, + /* 0334 */ 0x74, 0x00, 0x65, 0x00, + /* 0338 */ 0x72, 0x00, 0x00, 0x00, + /* 033c */ 0x13, 0x00, 0x09, 0x00, // [0x1a] len=19 value="\there is some text\n" + /* 0340 */ 0x68, 0x00, 0x65, 0x00, + /* 0344 */ 0x72, 0x00, 0x65, 0x00, + /* 0348 */ 0x20, 0x00, 0x69, 0x00, + /* 034c */ 0x73, 0x00, 0x20, 0x00, + /* 0350 */ 0x73, 0x00, 0x6f, 0x00, + /* 0354 */ 0x6d, 0x00, 0x65, 0x00, + /* 0358 */ 0x20, 0x00, 0x74, 0x00, + /* 035c */ 0x65, 0x00, 0x78, 0x00, + /* 0360 */ 0x74, 0x00, 0x0a, 0x00, + /* 0364 */ 0x00, 0x00, + /* 0366 */ 0x06, 0x00, // [0x1b] len=6 value="action" + /* 0368 */ 0x61, 0x00, 0x63, 0x00, + /* 036c */ 0x74, 0x00, 0x69, 0x00, + /* 0370 */ 0x6f, 0x00, 0x6e, 0x00, + /* 0374 */ 0x00, 0x00, + /* 0376 */ 0x1a, 0x00, // [0x1c] len=26 value="android.intent.action.MAIN" + /* 0378 */ 0x61, 0x00, 0x6e, 0x00, + /* 037c */ 0x64, 0x00, 0x72, 0x00, + /* 0380 */ 0x6f, 0x00, 0x69, 0x00, + /* 0384 */ 0x64, 0x00, 0x2e, 0x00, + /* 0388 */ 0x69, 0x00, 0x6e, 0x00, + /* 038c */ 0x74, 0x00, 0x65, 0x00, + /* 0390 */ 0x6e, 0x00, 0x74, 0x00, + /* 0394 */ 0x2e, 0x00, 0x61, 0x00, + /* 0398 */ 0x63, 0x00, 0x74, 0x00, + /* 039c */ 0x69, 0x00, 0x6f, 0x00, + /* 03a0 */ 0x6e, 0x00, 0x2e, 0x00, + /* 03a4 */ 0x4d, 0x00, 0x41, 0x00, + /* 03a8 */ 0x49, 0x00, 0x4e, 0x00, + /* 03ac */ 0x00, 0x00, + /* 03ae */ 0x08, 0x00, // [0x1d] len=8 value="category" + /* 03b0 */ 0x63, 0x00, 0x61, 0x00, + /* 03b4 */ 0x74, 0x00, 0x65, 0x00, + /* 03b8 */ 0x67, 0x00, 0x6f, 0x00, + /* 03bc */ 0x72, 0x00, 0x79, 0x00, + /* 03c0 */ 0x00, 0x00, + /* 03c2 */ 0x20, 0x00, // [0x1e] len=32 value="android.intent.category.LAUNCHER" + /* 03c4 */ 0x61, 0x00, 0x6e, 0x00, + /* 03c8 */ 0x64, 0x00, 0x72, 0x00, + /* 03cc */ 0x6f, 0x00, 0x69, 0x00, + /* 03d0 */ 0x64, 0x00, 0x2e, 0x00, + /* 03d4 */ 0x69, 0x00, 0x6e, 0x00, + /* 03d8 */ 0x74, 0x00, 0x65, 0x00, + /* 03dc */ 0x6e, 0x00, 0x74, 0x00, + /* 03e0 */ 0x2e, 0x00, 0x63, 0x00, + /* 03e4 */ 0x61, 0x00, 0x74, 0x00, + /* 03e8 */ 0x65, 0x00, 0x67, 0x00, + /* 03ec */ 0x6f, 0x00, 0x72, 0x00, + /* 03f0 */ 0x79, 0x00, 0x2e, 0x00, + /* 03f4 */ 0x4c, 0x00, 0x41, 0x00, + /* 03f8 */ 0x55, 0x00, 0x4e, 0x00, + /* 03fc */ 0x43, 0x00, 0x48, 0x00, + /* 0400 */ 0x45, 0x00, 0x52, 0x00, + /* 0404 */ 0x00, 0x00, + /* 0406 */ 0x00, 0x00, + // End of STRING_POOL. + + /* 0408 */ 0x80, 0x01, 0x08, 0x00, // chunk header XML_RESOURCE_MAP + /* 040c */ 0x2c, 0x00, 0x00, 0x00, // chunk size 44 + /* 0410 */ 0x1b, 0x02, 0x01, 0x01, // 0x0101021b = versionCode + /* 0414 */ 0x1c, 0x02, 0x01, 0x01, // 0x0101021c = versionName + /* 0418 */ 0x0c, 0x02, 0x01, 0x01, // 0x0101020c = minSdkVersion + /* 041c */ 0x01, 0x00, 0x01, 0x01, // 0x01010001 = label + /* 0420 */ 0x0c, 0x00, 0x01, 0x01, // 0x0101000c = hasCode + /* 0424 */ 0x0f, 0x00, 0x01, 0x01, // 0x0101000f = debuggable + /* 0428 */ 0x03, 0x00, 0x01, 0x01, // 0x01010003 = name + /* 042c */ 0x1f, 0x00, 0x01, 0x01, // 0x0101001f = configChanges + /* 0430 */ 0x24, 0x00, 0x01, 0x01, // 0x01010024 = value + + /* 0434 */ 0x00, 0x01, 0x10, 0x00, // chunk header XML_START_NAMESPACE + /* 0438 */ 0x18, 0x00, 0x00, 0x00, // chunk size 24 + /* 043c */ 0x07, 0x00, 0x00, 0x00, // line number + /* 0440 */ 0xff, 0xff, 0xff, 0xff, // comment string reference + /* 0444 */ 0x09, 0x00, 0x00, 0x00, // prefix [0x09]="android" + /* 0448 */ 0x0a, 0x00, 0x00, 0x00, // url [0x0a]="http://schemas..." + + // Start XML_START_ELEMENT + /* 044c */ 0x02, 0x01, 0x10, 0x00, // chunk header XML_START_ELEMENT + /* 0450 */ 0x60, 0x00, 0x00, 0x00, // chunk size 96 + /* 0454 */ 0x07, 0x00, 0x00, 0x00, // line number + /* 0458 */ 0xff, 0xff, 0xff, 0xff, // comment ref + /* 045c */ 0xff, 0xff, 0xff, 0xff, // ns (start ResXMLTree_attrExt) + /* 0460 */ 0x0d, 0x00, 0x00, 0x00, // name [0x0d]="manifest" + /* 0464 */ 0x14, 0x00, // attribute start + /* 0466 */ 0x14, 0x00, // attribute size + /* 0468 */ 0x03, 0x00, // attribute count + /* 046a */ 0x00, 0x00, // ID index (1-based, 0 means none) + /* 046c */ 0x00, 0x00, // class index (1-based, 0 means none) + /* 046e */ 0x00, 0x00, // style index (1-based, 0 means none) + // ResXMLTree_attribute[0] + /* 0470 */ 0x0a, 0x00, 0x00, 0x00, // ns [0x0a]="http://schemas..." + /* 0474 */ 0x00, 0x00, 0x00, 0x00, // name [0x00]=="versionCode" + /* 0478 */ 0xff, 0xff, 0xff, 0xff, // rawValue + /* 047c */ 0x08, 0x00, // Res_value size + /* 047e */ 0x00, // Res_value padding + /* 047f */ 0x10, // Res_value dataType (INT_DEC) + /* 0480 */ 0x01, 0x00, 0x00, 0x00, // Res_value data + // ResXMLTree_attribute[1] + /* 0484 */ 0x0a, 0x00, 0x00, 0x00, // ns [0x0a]="http://schemas..." + /* 0488 */ 0x01, 0x00, 0x00, 0x00, // name [0x01]="versionName" + /* 048c */ 0x0f, 0x00, 0x00, 0x00, // rawValue + /* 0490 */ 0x08, 0x00, // Res_value size + /* 0492 */ 0x00, // Res_value padding + /* 0493 */ 0x03, // Res_value dataType (STRING) + /* 0494 */ 0x0f, 0x00, 0x00, 0x00, // Res_value data [0x0f]="1.0" + // ResXMLTree_attribute[2] + /* 0498 */ 0xff, 0xff, 0xff, 0xff, // ns none + /* 049c */ 0x0c, 0x00, 0x00, 0x00, // name [0x0c]="package" + /* 04a0 */ 0x0e, 0x00, 0x00, 0x00, // rawValue + /* 04a4 */ 0x08, 0x00, // Res_value size + /* 04a6 */ 0x00, // Res_value padding + /* 04a7 */ 0x03, // Res_value dataType (STRING) + /* 04a8 */ 0x0e, 0x00, 0x00, 0x00, // Res_value data [0x0e]="com.zentus..." + // End XML_START_ELEMENT + + // Start XML_START_ELEMENT + /* 04ac */ 0x02, 0x01, 0x10, 0x00, // chunk header XML_START_ELEMENT + /* 04b0 */ 0x38, 0x00, 0x00, 0x00, // chunk size 56 + /* 04b4 */ 0x0d, 0x00, 0x00, 0x00, // line number + /* 04b8 */ 0xff, 0xff, 0xff, 0xff, // comment + /* 04bc */ 0xff, 0xff, 0xff, 0xff, // ns + /* 04c0 */ 0x10, 0x00, 0x00, 0x00, // name [0x10]="uses-sdk" + /* 04c4 */ 0x14, 0x00, 0x14, 0x00, // attribute start + size + /* 04c8 */ 0x01, 0x00, // atrribute count + /* 04ca */ 0x00, 0x00, // ID index + /* 04cc */ 0x00, 0x00, 0x00, 0x00, // class index + style index + // ResXMLTree_attribute[0] + /* 04d0 */ 0x0a, 0x00, 0x00, 0x00, // ns [0x0a]="http://schemas..." + /* 04d4 */ 0x02, 0x00, 0x00, 0x00, // name [0x02]="minSdkVersion" + /* 04d8 */ 0xff, 0xff, 0xff, 0xff, // rawValue + /* 04dc */ 0x08, 0x00, 0x00, 0x10, // size+padding+type (INT_DEC) + /* 04e0 */ 0x09, 0x00, 0x00, 0x00, + // End XML_START_ELEMENT + + // Start XML_END_ELEMENT + /* 04e4 */ 0x03, 0x01, 0x10, 0x00, // chunk header XML_END_ELEMENT + /* 04e8 */ 0x18, 0x00, 0x00, 0x00, // chunk size 24 + /* 04ec */ 0x0d, 0x00, 0x00, 0x00, // line number + /* 04f0 */ 0xff, 0xff, 0xff, 0xff, // comment + /* 04f4 */ 0xff, 0xff, 0xff, 0xff, // ns + /* 04f8 */ 0x10, 0x00, 0x00, 0x00, // name [0x10]="uses-sdk" + // End XML_END_ELEMENT + + // Start XML_START_ELEMENT + /* 04fc */ 0x02, 0x01, 0x10, 0x00, // chunk header XML_START_ELEMENT + /* 0500 */ 0x60, 0x00, 0x00, 0x00, // chunk size 96 + /* 0504 */ 0x0e, 0x00, 0x00, 0x00, // line number + /* 0508 */ 0xff, 0xff, 0xff, 0xff, // comment + /* 050c */ 0xff, 0xff, 0xff, 0xff, // ns + /* 0510 */ 0x11, 0x00, 0x00, 0x00, // name [0x11]="application" + /* 0514 */ 0x14, 0x00, 0x14, 0x00, // attribute start + size + /* 0518 */ 0x03, 0x00, 0x00, 0x00, // attribute count + ID index + /* 051c */ 0x00, 0x00, 0x00, 0x00, // class index + style index + // ResXMLTree_attribute[0] + /* 0520 */ 0x0a, 0x00, 0x00, 0x00, // ns [0x0a]="http://schemas..." + /* 0524 */ 0x03, 0x00, 0x00, 0x00, // name [0x03]="label" + /* 0528 */ 0x12, 0x00, 0x00, 0x00, // rawValue + /* 052c */ 0x08, 0x00, 0x00, 0x03, // size+padding+type (STRING) + /* 0530 */ 0x12, 0x00, 0x00, 0x00, // [0x12]="Balloon世界" + // ResXMLTree_attribute[1] + /* 0534 */ 0x0a, 0x00, 0x00, 0x00, // ns [0x0a]="http://schemas..." + /* 0538 */ 0x04, 0x00, 0x00, 0x00, // name [0x04]="hasCode" + /* 053c */ 0xff, 0xff, 0xff, 0xff, // rawValue + /* 0540 */ 0x08, 0x00, 0x00, 0x12, // size+padding+type (BOOLEAN) + /* 0544 */ 0x00, 0x00, 0x00, 0x00, // false + // ResXMLTree_attribute[2] + /* 0548 */ 0x0a, 0x00, 0x00, 0x00, + /* 054c */ 0x05, 0x00, 0x00, 0x00, // name=[0x05]="debuggable" + /* 0550 */ 0xff, 0xff, 0xff, 0xff, // rawValue + /* 0554 */ 0x08, 0x00, 0x00, 0x12, // size+padding+type (BOOLEAN) + /* 0558 */ 0xff, 0xff, 0xff, 0xff, // true + // End XML_START_ELEMENT + + // Start XML_START_ELEMENT + /* 055c */ 0x02, 0x01, 0x10, 0x00, // chunk header XML_START_ELEMENT + /* 0560 */ 0x60, 0x00, 0x00, 0x00, // chunk size 96 + /* 0564 */ 0x0f, 0x00, 0x00, 0x00, // line number + /* 0568 */ 0xff, 0xff, 0xff, 0xff, // comment ref + /* 056c */ 0xff, 0xff, 0xff, 0xff, // ns + /* 0570 */ 0x13, 0x00, 0x00, 0x00, // name [0x13]="activity" + /* 0574 */ 0x14, 0x00, 0x14, 0x00, // attribute start + size + /* 0578 */ 0x03, 0x00, 0x00, 0x00, // atrribute count + ID index + /* 057c */ 0x00, 0x00, 0x00, 0x00, // class index + style index + // ResXMLTree_attribute[0] + /* 0580 */ 0x0a, 0x00, 0x00, 0x00, // ns [0x0a]="http://schemas..." + /* 0584 */ 0x03, 0x00, 0x00, 0x00, // name [0x03]="label" + /* 0588 */ 0x15, 0x00, 0x00, 0x00, // rawValue + /* 058c */ 0x08, 0x00, 0x00, 0x03, // size+padding+type (STRING) + /* 0590 */ 0x15, 0x00, 0x00, 0x00, // [0x15]="Balloon" + // ResXMLTree_attribute[1] + /* 0594 */ 0x0a, 0x00, 0x00, 0x00, // ns [0x0a]="http://schemas..." + /* 0598 */ 0x06, 0x00, 0x00, 0x00, // name [0x06]="name" + /* 059c */ 0x14, 0x00, 0x00, 0x00, // rawValue + /* 05a0 */ 0x08, 0x00, 0x00, 0x03, // size+padding+type (STRING) + /* 05a4 */ 0x14, 0x00, 0x00, 0x00, // [0x14]="android.app.NativeActivity" + // ResXMLTree_attribute[2] + /* 05a8 */ 0x0a, 0x00, 0x00, 0x00, + /* 05ac */ 0x07, 0x00, 0x00, 0x00, // name [0x07]="configChanges" + /* 05b0 */ 0xff, 0xff, 0xff, 0xff, // rawValue + /* 05b4 */ 0x08, 0x00, 0x00, 0x11, // size+padding+type (INT_HEX) + /* 05b8 */ 0xa0, 0x00, 0x00, 0x00, // orientation|keyboardHidden (0x80|0x0020=0xa0) + // End XML_START_ELEMENT + + // Start XML_START_ELEMENT + /* 05bc */ 0x02, 0x01, 0x10, 0x00, // chunk header XML_START_ELEMENT + /* 05c0 */ 0x4c, 0x00, 0x00, 0x00, // chunk size 76 + /* 05c4 */ 0x12, 0x00, 0x00, 0x00, // line number + /* 05c8 */ 0xff, 0xff, 0xff, 0xff, // comment ref + /* 05cc */ 0xff, 0xff, 0xff, 0xff, // ns + /* 05d0 */ 0x16, 0x00, 0x00, 0x00, // name [0x16]="meta-data" + /* 05d4 */ 0x14, 0x00, 0x14, 0x00, // atrribute start + size + /* 05d8 */ 0x02, 0x00, 0x00, 0x00, // attribute count + ID index + /* 05dc */ 0x00, 0x00, 0x00, 0x00, // class+style index + // ResXMLTree_attribute[0] + /* 05e0 */ 0x0a, 0x00, 0x00, 0x00, // ns [0x0a]="http://schemas..." + /* 05e4 */ 0x06, 0x00, 0x00, 0x00, // name [0x06]="name" + /* 05e8 */ 0x17, 0x00, 0x00, 0x00, // rawValue + /* 05ec */ 0x08, 0x00, 0x00, 0x03, // size + padding + type (STRING) + /* 05f0 */ 0x17, 0x00, 0x00, 0x00, // [0x17]="android.app.lib_name" + // ResXMLTree_attribute[1] + /* 05f4 */ 0x0a, 0x00, 0x00, 0x00, // ns [0x0a]="http://schemas..." + /* 05f8 */ 0x08, 0x00, 0x00, 0x00, + /* 05fc */ 0x18, 0x00, 0x00, 0x00, + /* 0600 */ 0x08, 0x00, 0x00, 0x03, // size + padding + type (STRING) + /* 0604 */ 0x18, 0x00, 0x00, 0x00, // [0x18]="balloon" + // End XML_START_ELEMENT + + // Start XML_END_ELEMENT + /* 0608 */ 0x03, 0x01, 0x10, 0x00, // chunk header XML_END_ELEMENT + /* 060c */ 0x18, 0x00, 0x00, 0x00, // chunk size 24 + /* 0610 */ 0x12, 0x00, 0x00, 0x00, // line-number + /* 0614 */ 0xff, 0xff, 0xff, 0xff, + /* 0618 */ 0xff, 0xff, 0xff, 0xff, + /* 061c */ 0x16, 0x00, 0x00, 0x00, // name [0x16]="meta-data" + // End XML_END_ELEMENT + + // Start XML_START_ELEMENT + /* 0620 */ 0x02, 0x01, 0x10, 0x00, // chunk header XML_START_ELEMENT + /* 0624 */ 0x24, 0x00, 0x00, 0x00, // chunk size 36 + /* 0628 */ 0x13, 0x00, 0x00, 0x00, // line number + /* 062c */ 0xff, 0xff, 0xff, 0xff, // comment + /* 0630 */ 0xff, 0xff, 0xff, 0xff, // ns + /* 0634 */ 0x19, 0x00, 0x00, 0x00, // name [0x19]="intent-filter" + /* 0638 */ 0x14, 0x00, 0x14, 0x00, // attribute start + size + /* 063c */ 0x00, 0x00, 0x00, 0x00, // attribute count + ID index + /* 0640 */ 0x00, 0x00, 0x00, 0x00, // class index + style index + // End XML_START_ELEMENT + + // Start XML_CDATA + /* 0644 */ 0x04, 0x01, 0x10, 0x00, // chunk header XML_CDATA + /* 0648 */ 0x1c, 0x00, 0x00, 0x00, // chunk size 28 + /* 064c */ 0x13, 0x00, 0x00, 0x00, // line number + /* 0650 */ 0xff, 0xff, 0xff, 0xff, // comment + /* 0654 */ 0x1a, 0x00, 0x00, 0x00, // data [0x1a]="\there is some text\n" + /* 0658 */ 0x08, 0x00, 0x00, 0x00, + /* 065c */ 0x00, 0x00, 0x00, 0x00, + // End XML_CDATA + + // Start XML_START_ELEMENT + /* 0660 */ 0x02, 0x01, 0x10, 0x00, + /* 0664 */ 0x38, 0x00, 0x00, 0x00, + /* 0668 */ 0x15, 0x00, 0x00, 0x00, + /* 066c */ 0xff, 0xff, 0xff, 0xff, + /* 0670 */ 0xff, 0xff, 0xff, 0xff, + /* 0674 */ 0x1b, 0x00, 0x00, 0x00, + /* 0678 */ 0x14, 0x00, 0x14, 0x00, + /* 067c */ 0x01, 0x00, 0x00, 0x00, + /* 0680 */ 0x00, 0x00, 0x00, 0x00, + /* 0684 */ 0x0a, 0x00, 0x00, 0x00, + /* 0688 */ 0x06, 0x00, 0x00, 0x00, + /* 068c */ 0x1c, 0x00, 0x00, 0x00, + /* 0690 */ 0x08, 0x00, 0x00, 0x03, + /* 0694 */ 0x1c, 0x00, 0x00, 0x00, + // End XML_START_ELEMENT + + // Start XML_END_ELEMENT + /* 0698 */ 0x03, 0x01, 0x10, 0x00, + /* 069c */ 0x18, 0x00, 0x00, 0x00, + /* 06a0 */ 0x15, 0x00, 0x00, 0x00, // line number + /* 06a4 */ 0xff, 0xff, 0xff, 0xff, + /* 06a8 */ 0xff, 0xff, 0xff, 0xff, + /* 06ac */ 0x1b, 0x00, 0x00, 0x00, // [0x1b]="action" + // End XML_END_ELEMENT + + // Start XML_START_ELEMENT + /* 06b0 */ 0x02, 0x01, 0x10, 0x00, + /* 06b4 */ 0x38, 0x00, 0x00, 0x00, + /* 06b8 */ 0x16, 0x00, 0x00, 0x00, + /* 06bc */ 0xff, 0xff, 0xff, 0xff, + /* 06c0 */ 0xff, 0xff, 0xff, 0xff, + /* 06c4 */ 0x1d, 0x00, 0x00, 0x00, + /* 06c8 */ 0x14, 0x00, 0x14, 0x00, + /* 06cc */ 0x01, 0x00, 0x00, 0x00, + /* 06d0 */ 0x00, 0x00, 0x00, 0x00, + /* 06d4 */ 0x0a, 0x00, 0x00, 0x00, + /* 06d8 */ 0x06, 0x00, 0x00, 0x00, + /* 06dc */ 0x1e, 0x00, 0x00, 0x00, + /* 06e0 */ 0x08, 0x00, 0x00, 0x03, + /* 06e4 */ 0x1e, 0x00, 0x00, 0x00, + // End XML_START_ELEMENT + + // Start XML_END_ELEMENT + /* 06e8 */ 0x03, 0x01, 0x10, 0x00, + /* 06ec */ 0x18, 0x00, 0x00, 0x00, + /* 06f0 */ 0x16, 0x00, 0x00, 0x00, // line number + /* 06f4 */ 0xff, 0xff, 0xff, 0xff, + /* 06f8 */ 0xff, 0xff, 0xff, 0xff, + /* 06fc */ 0x1d, 0x00, 0x00, 0x00, // name [0x1d]="category" + // End XML_END_ELEMENT + + // Start XML_END_ELEMENT + /* 0700 */ 0x03, 0x01, 0x10, 0x00, // chunk header XML_END_ELEMENT + /* 0704 */ 0x18, 0x00, 0x00, 0x00, // chunk size 24 + /* 0708 */ 0x17, 0x00, 0x00, 0x00, // line number + /* 070c */ 0xff, 0xff, 0xff, 0xff, // comment + /* 0710 */ 0xff, 0xff, 0xff, 0xff, // ns + /* 0714 */ 0x19, 0x00, 0x00, 0x00, // name [0x19]="intent-filter" + // End XML_END_ELEMENT + + // Start XML_END_ELEMENT + /* 0718 */ 0x03, 0x01, 0x10, 0x00, + /* 071c */ 0x18, 0x00, 0x00, 0x00, + /* 0720 */ 0x18, 0x00, 0x00, 0x00, // line number + /* 0724 */ 0xff, 0xff, 0xff, 0xff, + /* 0728 */ 0xff, 0xff, 0xff, 0xff, + /* 072c */ 0x13, 0x00, 0x00, 0x00, // name [0x13]="activity" + // End XML_END_ELEMENT + + // Start XML_END_ELEMENT + /* 0730 */ 0x03, 0x01, 0x10, 0x00, + /* 0734 */ 0x18, 0x00, 0x00, 0x00, + /* 0738 */ 0x19, 0x00, 0x00, 0x00, + /* 073c */ 0xff, 0xff, 0xff, 0xff, + /* 0740 */ 0xff, 0xff, 0xff, 0xff, + /* 0744 */ 0x11, 0x00, 0x00, 0x00, // name [0x11]="application" + // End XML_END_ELEMENT + + // Start XML_END_ELEMENT + /* 0748 */ 0x03, 0x01, 0x10, 0x00, + /* 074c */ 0x18, 0x00, 0x00, 0x00, + /* 0750 */ 0x1a, 0x00, 0x00, 0x00, // line number + /* 0754 */ 0xff, 0xff, 0xff, 0xff, + /* 0758 */ 0xff, 0xff, 0xff, 0xff, + /* 075c */ 0x0d, 0x00, 0x00, 0x00, // name [0x0d]="manifest" + // End XML_END_ELEMENT + + /* 0760 */ 0x01, 0x01, 0x10, 0x00, // chunk header XML_END_NAMESPACE + /* 0764 */ 0x18, 0x00, 0x00, 0x00, // chunk size 24 + /* 0768 */ 0x1a, 0x00, 0x00, 0x00, // line number + /* 076c */ 0xff, 0xff, 0xff, 0xff, // comment + /* 0770 */ 0x09, 0x00, 0x00, 0x00, // prefix [0x09]="android" + /* 0774 */ 0x0a, 0x00, 0x00, 0x00, // url +} + +const input = ` + + + + + + + + + here is some text + + + + + +` diff --git a/src/golang.org/x/mobile/cmd/gomobile/bind.go b/src/golang.org/x/mobile/cmd/gomobile/bind.go new file mode 100644 index 0000000000..585a9a9ead --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/bind.go @@ -0,0 +1,279 @@ +// Copyright 2015 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. + +package main + +import ( + "errors" + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/scanner" + "go/token" + "go/types" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "golang.org/x/mobile/bind" + "golang.org/x/mobile/internal/loader" +) + +// ctx, pkg, tmpdir in build.go + +var cmdBind = &command{ + run: runBind, + Name: "bind", + Usage: "[-target android|ios] [-o output] [build flags] [package]", + Short: "build a shared library for android APK and iOS app", + Long: ` +Bind generates language bindings for the package named by the import +path, and compiles a library for the named target system. + +The -target flag takes a target system name, either android (the +default) or ios. + +For -target android, the bind command produces an AAR (Android ARchive) +file that archives the precompiled Java API stub classes, the compiled +shared libraries, and all asset files in the /assets subdirectory under +the package directory. The output is named '.aar' by +default. This AAR file is commonly used for binary distribution of an +Android library project and most Android IDEs support AAR import. For +example, in Android Studio (1.2+), an AAR file can be imported using +the module import wizard (File > New > New Module > Import .JAR or +.AAR package), and setting it as a new dependency +(File > Project Structure > Dependencies). This requires 'javac' +(version 1.7+) and Android SDK (API level 9 or newer) to build the +library for Android. The environment variable ANDROID_HOME must be set +to the path to Android SDK. + +For -target ios, gomobile must be run on an OS X machine with Xcode +installed. Support is not complete. + +The -v flag provides verbose output, including the list of packages built. + +The build flags -a, -i, -n, -x, -gcflags, -ldflags, -tags, and -work +are shared with the build command. For documentation, see 'go help build'. +`, +} + +func runBind(cmd *command) error { + cleanup, err := buildEnvInit() + if err != nil { + return err + } + defer cleanup() + + args := cmd.flag.Args() + + ctx.GOARCH = "arm" + switch buildTarget { + case "android": + ctx.GOOS = "android" + case "ios": + ctx.GOOS = "darwin" + default: + return fmt.Errorf(`unknown -target, %q.`, buildTarget) + } + + var pkg *build.Package + switch len(args) { + case 0: + pkg, err = ctx.ImportDir(cwd, build.ImportComment) + case 1: + pkg, err = ctx.Import(args[0], cwd, build.ImportComment) + default: + cmd.usage() + os.Exit(1) + } + if err != nil { + return err + } + + switch buildTarget { + case "android": + return goAndroidBind(pkg) + case "ios": + return goIOSBind(pkg) + default: + return fmt.Errorf(`unknown -target, %q.`, buildTarget) + } +} + +type binder struct { + files []*ast.File + fset *token.FileSet + pkg *types.Package +} + +func (b *binder) GenObjc(outdir string) error { + name := strings.Title(b.pkg.Name()) + mfile := filepath.Join(outdir, "Go"+name+".m") + hfile := filepath.Join(outdir, "Go"+name+".h") + + if buildX { + printcmd("gobind -lang=objc %s > %s", b.pkg.Path(), mfile) + } + + generate := func(w io.Writer) error { + return bind.GenObjc(w, b.fset, b.pkg, false) + } + if err := writeFile(mfile, generate); err != nil { + return err + } + generate = func(w io.Writer) error { + return bind.GenObjc(w, b.fset, b.pkg, true) + } + if err := writeFile(hfile, generate); err != nil { + return err + } + + objcPkg, err := ctx.Import("golang.org/x/mobile/bind/objc", "", build.FindOnly) + if err != nil { + return err + } + return copyFile(filepath.Join(outdir, "seq.h"), filepath.Join(objcPkg.Dir, "seq.h")) +} + +func (b *binder) GenJava(outdir string) error { + className := strings.Title(b.pkg.Name()) + javaFile := filepath.Join(outdir, className+".java") + + if buildX { + printcmd("gobind -lang=java %s > %s", b.pkg.Path(), javaFile) + } + + generate := func(w io.Writer) error { + return bind.GenJava(w, b.fset, b.pkg) + } + if err := writeFile(javaFile, generate); err != nil { + return err + } + return nil +} + +func (b *binder) GenGo(outdir string) error { + pkgName := "go_" + b.pkg.Name() + goFile := filepath.Join(outdir, pkgName, pkgName+"main.go") + + if buildX { + printcmd("gobind -lang=go %s > %s", b.pkg.Path(), goFile) + } + + generate := func(w io.Writer) error { + return bind.GenGo(w, b.fset, b.pkg) + } + if err := writeFile(goFile, generate); err != nil { + return err + } + return nil +} + +func copyFile(dst, src string) error { + if buildX { + printcmd("cp %s %s", src, dst) + } + return writeFile(dst, func(w io.Writer) error { + if buildN { + return nil + } + f, err := os.Open(src) + if err != nil { + return err + } + defer f.Close() + + if _, err := io.Copy(w, f); err != nil { + return fmt.Errorf("cp %s %s failed: %v", src, dst, err) + } + return nil + }) +} + +func writeFile(filename string, generate func(io.Writer) error) error { + if buildV { + fmt.Fprintf(os.Stderr, "write %s\n", filename) + } + + err := mkdir(filepath.Dir(filename)) + if err != nil { + return err + } + + if buildN { + return generate(ioutil.Discard) + } + + f, err := os.Create(filename) + if err != nil { + return err + } + defer func() { + if cerr := f.Close(); err == nil { + err = cerr + } + }() + + return generate(f) +} + +func newBinder(bindPkg *build.Package) (*binder, error) { + if bindPkg.Name == "main" { + return nil, fmt.Errorf("package %q: can only bind a library package", bindPkg.Name) + } + + fset := token.NewFileSet() + + hasErr := false + var files []*ast.File + for _, filename := range bindPkg.GoFiles { + p := filepath.Join(bindPkg.Dir, filename) + file, err := parser.ParseFile(fset, p, nil, parser.AllErrors) + if err != nil { + hasErr = true + if list, _ := err.(scanner.ErrorList); len(list) > 0 { + for _, err := range list { + fmt.Fprintln(os.Stderr, err) + } + } else { + fmt.Fprintln(os.Stderr, err) + } + } + files = append(files, file) + } + + if hasErr { + return nil, errors.New("package parsing failed.") + } + + conf := loader.Config{ + Fset: fset, + AllowErrors: true, + } + conf.TypeChecker.IgnoreFuncBodies = true + conf.TypeChecker.FakeImportC = true + conf.TypeChecker.DisableUnusedImportCheck = true + var tcErrs []error + conf.TypeChecker.Error = func(err error) { + tcErrs = append(tcErrs, err) + } + + conf.CreateFromFiles(bindPkg.ImportPath, files...) + program, err := conf.Load() + if err != nil { + for _, err := range tcErrs { + fmt.Fprintln(os.Stderr, err) + } + return nil, err + } + b := &binder{ + files: files, + fset: fset, + pkg: program.Created[0].Pkg, + } + return b, nil +} diff --git a/src/golang.org/x/mobile/cmd/gomobile/bind_androidapp.go b/src/golang.org/x/mobile/cmd/gomobile/bind_androidapp.go new file mode 100644 index 0000000000..5a639be64a --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/bind_androidapp.go @@ -0,0 +1,373 @@ +// Copyright 2015 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. + +package main + +import ( + "archive/zip" + "fmt" + "go/build" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "text/template" +) + +func goAndroidBind(pkg *build.Package) error { + if sdkDir := os.Getenv("ANDROID_HOME"); sdkDir == "" { + return fmt.Errorf("this command requires ANDROID_HOME environment variable (path to the Android SDK)") + } + + binder, err := newBinder(pkg) + if err != nil { + return err + } + + if err := binder.GenGo(tmpdir); err != nil { + return err + } + + mainFile := filepath.Join(tmpdir, "androidlib/main.go") + err = writeFile(mainFile, func(w io.Writer) error { + return androidMainTmpl.Execute(w, "../go_"+binder.pkg.Name()) + }) + if err != nil { + return fmt.Errorf("failed to create the main package for android: %v", err) + } + + androidDir := filepath.Join(tmpdir, "android") + + err = goBuild( + mainFile, + androidArmEnv, + "-buildmode=c-shared", + "-o="+filepath.Join(androidDir, "src/main/jniLibs/armeabi-v7a/libgojni.so"), + ) + if err != nil { + return err + } + + p, err := ctx.Import("golang.org/x/mobile/bind", cwd, build.ImportComment) + if err != nil { + return fmt.Errorf(`"golang.org/x/mobile/bind" is not found; run go get golang.org/x/mobile/bind`) + } + repo := filepath.Clean(filepath.Join(p.Dir, "..")) // golang.org/x/mobile directory. + + // TODO(crawshaw): use a better package path derived from the go package. + if err := binder.GenJava(filepath.Join(androidDir, "src/main/java/go/"+binder.pkg.Name())); err != nil { + return err + } + + dst := filepath.Join(androidDir, "src/main/java/go/LoadJNI.java") + genLoadJNI := func(w io.Writer) error { + _, err := io.WriteString(w, loadSrc) + return err + } + if err := writeFile(dst, genLoadJNI); err != nil { + return err + } + + src := filepath.Join(repo, "bind/java/Seq.java") + dst = filepath.Join(androidDir, "src/main/java/go/Seq.java") + rm(dst) + if err := symlink(src, dst); err != nil { + return err + } + + return buildAAR(androidDir, pkg) +} + +var loadSrc = `package go; + +public class LoadJNI { + static { + System.loadLibrary("gojni"); + } +} +` + +var androidMainTmpl = template.Must(template.New("android.go").Parse(` +package main + +import ( + _ "golang.org/x/mobile/bind/java" + _ "{{.}}" +) + +func main() {} +`)) + +// AAR is the format for the binary distribution of an Android Library Project +// and it is a ZIP archive with extension .aar. +// http://tools.android.com/tech-docs/new-build-system/aar-format +// +// These entries are directly at the root of the archive. +// +// AndroidManifest.xml (mandatory) +// classes.jar (mandatory) +// assets/ (optional) +// jni//libgojni.so +// R.txt (mandatory) +// res/ (mandatory) +// libs/*.jar (optional, not relevant) +// proguard.txt (optional) +// lint.jar (optional, not relevant) +// aidl (optional, not relevant) +// +// javac and jar commands are needed to build classes.jar. +func buildAAR(androidDir string, pkg *build.Package) (err error) { + var out io.Writer = ioutil.Discard + if buildO == "" { + buildO = pkg.Name + ".aar" + } + if !strings.HasSuffix(buildO, ".aar") { + return fmt.Errorf("output file name %q does not end in '.aar'", buildO) + } + if !buildN { + f, err := os.Create(buildO) + if err != nil { + return err + } + defer func() { + if cerr := f.Close(); err == nil { + err = cerr + } + }() + out = f + } + + aarw := zip.NewWriter(out) + aarwcreate := func(name string) (io.Writer, error) { + if buildV { + fmt.Fprintf(os.Stderr, "aar: %s\n", name) + } + return aarw.Create(name) + } + w, err := aarwcreate("AndroidManifest.xml") + if err != nil { + return err + } + const manifestFmt = `` + fmt.Fprintf(w, manifestFmt, "go."+pkg.Name+".gojni") + + w, err = aarwcreate("proguard.txt") + if err != nil { + return err + } + fmt.Fprintln(w, `-keep class go.** { *; }`) + + w, err = aarwcreate("classes.jar") + if err != nil { + return err + } + src := filepath.Join(androidDir, "src/main/java") + if err := buildJar(w, src); err != nil { + return err + } + + assetsDir := filepath.Join(pkg.Dir, "assets") + assetsDirExists := false + if fi, err := os.Stat(assetsDir); err == nil { + assetsDirExists = fi.IsDir() + } else if !os.IsNotExist(err) { + return err + } + + if assetsDirExists { + err := filepath.Walk( + assetsDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + name := "assets/" + path[len(assetsDir)+1:] + w, err := aarwcreate(name) + if err != nil { + return nil + } + _, err = io.Copy(w, f) + return err + }) + if err != nil { + return err + } + } + + lib := "armeabi-v7a/libgojni.so" + w, err = aarwcreate("jni/" + lib) + if err != nil { + return err + } + if !buildN { + r, err := os.Open(filepath.Join(androidDir, "src/main/jniLibs/"+lib)) + if err != nil { + return err + } + defer r.Close() + if _, err := io.Copy(w, r); err != nil { + return err + } + } + + // TODO(hyangah): do we need to use aapt to create R.txt? + w, err = aarwcreate("R.txt") + if err != nil { + return err + } + + w, err = aarwcreate("res/") + if err != nil { + return err + } + + return aarw.Close() +} + +const ( + javacTargetVer = "1.7" + minAndroidAPI = 9 +) + +func buildJar(w io.Writer, srcDir string) error { + var srcFiles []string + if buildN { + srcFiles = []string{"*.java"} + } else { + err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if filepath.Ext(path) == ".java" { + srcFiles = append(srcFiles, filepath.Join(".", path[len(srcDir):])) + } + return nil + }) + if err != nil { + return err + } + } + + dst := filepath.Join(tmpdir, "javac-output") + if !buildN { + if err := os.MkdirAll(dst, 0700); err != nil { + return err + } + } + + apiPath, err := androidAPIPath() + if err != nil { + return err + } + + args := []string{ + "-d", dst, + "-source", javacTargetVer, + "-target", javacTargetVer, + "-bootclasspath", filepath.Join(apiPath, "android.jar"), + } + args = append(args, srcFiles...) + + javac := exec.Command("javac", args...) + javac.Dir = srcDir + if err := runCmd(javac); err != nil { + return err + } + + if buildX { + printcmd("jar c -C %s .", dst) + } + if buildN { + return nil + } + jarw := zip.NewWriter(w) + jarwcreate := func(name string) (io.Writer, error) { + if buildV { + fmt.Fprintf(os.Stderr, "jar: %s\n", name) + } + return jarw.Create(name) + } + f, err := jarwcreate("META-INF/MANIFEST.MF") + if err != nil { + return err + } + fmt.Fprintf(f, manifestHeader) + + err = filepath.Walk(dst, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + out, err := jarwcreate(filepath.ToSlash(path[len(dst)+1:])) + if err != nil { + return err + } + in, err := os.Open(path) + if err != nil { + return err + } + defer in.Close() + _, err = io.Copy(out, in) + return err + }) + if err != nil { + return err + } + return jarw.Close() +} + +// androidAPIPath returns an android SDK platform directory under ANDROID_HOME. +// If there are multiple platforms that satisfy the minimum version requirement +// androidAPIPath returns the latest one among them. +func androidAPIPath() (string, error) { + sdk := os.Getenv("ANDROID_HOME") + if sdk == "" { + return "", fmt.Errorf("ANDROID_HOME environment var is not set") + } + sdkDir, err := os.Open(filepath.Join(sdk, "platforms")) + if err != nil { + return "", fmt.Errorf("failed to find android SDK platform: %v", err) + } + defer sdkDir.Close() + fis, err := sdkDir.Readdir(-1) + if err != nil { + return "", fmt.Errorf("failed to find android SDK platform (min API level: %d): %v", minAndroidAPI, err) + } + + var apiPath string + var apiVer int + for _, fi := range fis { + name := fi.Name() + if !fi.IsDir() || !strings.HasPrefix(name, "android-") { + continue + } + n, err := strconv.Atoi(name[len("android-"):]) + if err != nil || n < minAndroidAPI { + continue + } + p := filepath.Join(sdkDir.Name(), name) + _, err = os.Stat(filepath.Join(p, "android.jar")) + if err == nil && apiVer < n { + apiPath = p + apiVer = n + } + } + if apiVer == 0 { + return "", fmt.Errorf("failed to find android SDK platform (min API level: %d) in %s", + minAndroidAPI, sdkDir.Name()) + } + return apiPath, nil +} diff --git a/src/golang.org/x/mobile/cmd/gomobile/bind_iosapp.go b/src/golang.org/x/mobile/cmd/gomobile/bind_iosapp.go new file mode 100644 index 0000000000..e2702f02cd --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/bind_iosapp.go @@ -0,0 +1,128 @@ +// Copyright 2015 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. + +package main + +import ( + "fmt" + "go/build" + "io" + "os/exec" + "path/filepath" + "strings" + "text/template" +) + +func goIOSBind(pkg *build.Package) error { + binder, err := newBinder(pkg) + if err != nil { + return err + } + name := binder.pkg.Name() + + if buildO != "" && !strings.HasSuffix(buildO, ".framework") { + return fmt.Errorf("static framework name %q missing .framework suffix", buildO) + } + if buildO == "" { + buildO = name + ".framework" + } + + if err := binder.GenGo(filepath.Join(tmpdir, "src")); err != nil { + return err + } + mainFile := filepath.Join(tmpdir, "src/iosbin/main.go") + err = writeFile(mainFile, func(w io.Writer) error { + return iosBindTmpl.Execute(w, "../go_"+name) + }) + if err != nil { + return fmt.Errorf("failed to create the binding package for iOS: %v", err) + } + if err := binder.GenObjc(filepath.Join(tmpdir, "objc")); err != nil { + return err + } + + cmd := exec.Command("xcrun", "lipo", "-create") + + for _, env := range [][]string{darwinArmEnv, darwinArm64Env, darwinAmd64Env} { + arch := archClang(getenv(env, "GOARCH")) + path, err := goIOSBindArchive(name, mainFile, env) + if err != nil { + return fmt.Errorf("darwin-%s: %v", arch, err) + } + cmd.Args = append(cmd.Args, "-arch", arch, path) + } + + // Build static framework output directory. + if err := removeAll(buildO); err != nil { + return err + } + headers := buildO + "/Versions/A/Headers" + if err := mkdir(headers); err != nil { + return err + } + if err := symlink("A", buildO+"/Versions/Current"); err != nil { + return err + } + if err := symlink("Versions/Current/Headers", buildO+"/Headers"); err != nil { + return err + } + if err := symlink("Versions/Current/"+strings.Title(name), buildO+"/"+strings.Title(name)); err != nil { + return err + } + + cmd.Args = append(cmd.Args, "-o", buildO+"/Versions/A/"+strings.Title(name)) + if err := runCmd(cmd); err != nil { + return err + } + + // Copy header file next to output archive. + return copyFile( + headers+"/"+strings.Title(name)+".h", + tmpdir+"/objc/Go"+strings.Title(name)+".h", + ) +} + +func goIOSBindArchive(name, path string, env []string) (string, error) { + arch := getenv(env, "GOARCH") + archive := filepath.Join(tmpdir, name+"-"+arch+".a") + err := goBuild(path, env, "-buildmode=c-archive", "-tags=ios", "-o", archive) + if err != nil { + return "", err + } + + obj := "gobind-" + name + "-" + arch + ".o" + cmd := exec.Command( + getenv(env, "CC"), + "-I", ".", + "-g", "-O2", + "-o", obj, + "-c", "Go"+strings.Title(name)+".m", + ) + cmd.Args = append(cmd.Args, strings.Split(getenv(env, "CGO_CFLAGS"), " ")...) + cmd.Dir = filepath.Join(tmpdir, "objc") + cmd.Env = append([]string{}, env...) + if err := runCmd(cmd); err != nil { + return "", err + } + + cmd = exec.Command("ar", "-q", "-s", archive, obj) + cmd.Dir = filepath.Join(tmpdir, "objc") + if err := runCmd(cmd); err != nil { + return "", err + } + return archive, nil +} + +var iosBindTmpl = template.Must(template.New("ios.go").Parse(` +package main + +import ( + _ "golang.org/x/mobile/bind/objc" + _ "{{.}}" +) + +import "C" + +func main() {} +`)) diff --git a/src/golang.org/x/mobile/cmd/gomobile/bind_test.go b/src/golang.org/x/mobile/cmd/gomobile/bind_test.go new file mode 100644 index 0000000000..9964d7ee4d --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/bind_test.go @@ -0,0 +1,66 @@ +// Copyright 2015 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. + +package main + +import ( + "bytes" + "os" + "path/filepath" + "testing" + "text/template" +) + +// TODO(crawshaw): TestBindIOS + +func TestBindAndroid(t *testing.T) { + if os.Getenv("ANDROID_HOME") == "" { + t.Skip("ANDROID_HOME not found, skipping bind") + } + + buf := new(bytes.Buffer) + defer func() { + xout = os.Stderr + buildN = false + buildX = false + }() + xout = buf + buildN = true + buildX = true + buildO = "asset.aar" + buildTarget = "android" + gopath = filepath.SplitList(os.Getenv("GOPATH"))[0] + if goos == "windows" { + os.Setenv("HOMEDRIVE", "C:") + } + cmdBind.flag.Parse([]string{"golang.org/x/mobile/asset"}) + err := runBind(cmdBind) + if err != nil { + t.Log(buf.String()) + t.Fatal(err) + } + + diff, err := diffOutput(buf.String(), bindAndroidTmpl) + if err != nil { + t.Fatalf("computing diff failed: %v", err) + } + if diff != "" { + t.Errorf("unexpected output:\n%s", diff) + } +} + +var bindAndroidTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile +WORK=$WORK +gobind -lang=go golang.org/x/mobile/asset > $WORK/go_asset/go_assetmain.go +mkdir -p $WORK/go_asset +mkdir -p $WORK/androidlib +GOOS=android GOARCH=arm GOARM=7 CC=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-gcc{{.EXE}} CXX=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-g++{{.EXE}} CGO_ENABLED=1 go build -p={{.NumCPU}} -pkgdir=$GOMOBILE/pkg_android_arm -tags="" -x -buildmode=c-shared -o=$WORK/android/src/main/jniLibs/armeabi-v7a/libgojni.so $WORK/androidlib/main.go +gobind -lang=java golang.org/x/mobile/asset > $WORK/android/src/main/java/go/asset/Asset.java +mkdir -p $WORK/android/src/main/java/go/asset +mkdir -p $WORK/android/src/main/java/go +rm $WORK/android/src/main/java/go/Seq.java +ln -s $GOPATH/src/golang.org/x/mobile/bind/java/Seq.java $WORK/android/src/main/java/go/Seq.java +PWD=$WORK/android/src/main/java javac -d $WORK/javac-output -source 1.7 -target 1.7 -bootclasspath $ANDROID_HOME/platforms/android-22/android.jar *.java +jar c -C $WORK/javac-output . +`)) diff --git a/src/golang.org/x/mobile/cmd/gomobile/build.go b/src/golang.org/x/mobile/cmd/gomobile/build.go new file mode 100644 index 0000000000..12b462be11 --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/build.go @@ -0,0 +1,282 @@ +// Copyright 2015 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. + +//go:generate go run gendex.go -o dex.go + +package main + +import ( + "bufio" + "fmt" + "go/build" + "io" + "os" + "os/exec" + "regexp" + "runtime" + "strconv" + "strings" +) + +var ctx = build.Default +var pkg *build.Package // TODO(crawshaw): remove global pkg variable +var tmpdir string + +var cmdBuild = &command{ + run: runBuild, + Name: "build", + Usage: "[-target android|ios] [-o output] [build flags] [package]", + Short: "compile android APK and iOS app", + Long: ` +Build compiles and encodes the app named by the import path. + +The named package must define a main function. + +The -target flag takes a target system name, either android (the +default) or ios. + +For -target android, if an AndroidManifest.xml is defined in the +package directory, it is added to the APK output. Otherwise, a default +manifest is generated. + +For -target ios, gomobile must be run on an OS X machine with Xcode +installed. Support is not complete. + +If the package directory contains an assets subdirectory, its contents +are copied into the output. + +The -o flag specifies the output file name. If not specified, the +output file name depends on the package built. + +The -v flag provides verbose output, including the list of packages built. + +The build flags -a, -i, -n, -x, -gcflags, -ldflags, -tags, and -work are +shared with the build command. For documentation, see 'go help build'. +`, +} + +func runBuild(cmd *command) (err error) { + cleanup, err := buildEnvInit() + if err != nil { + return err + } + defer cleanup() + + args := cmd.flag.Args() + + ctx.GOARCH = "arm" + switch buildTarget { + case "android": + ctx.GOOS = "android" + case "ios": + ctx.GOOS = "darwin" + default: + return fmt.Errorf(`unknown -target, %q.`, buildTarget) + } + + switch len(args) { + case 0: + pkg, err = ctx.ImportDir(cwd, build.ImportComment) + case 1: + pkg, err = ctx.Import(args[0], cwd, build.ImportComment) + default: + cmd.usage() + os.Exit(1) + } + if err != nil { + return err + } + + if pkg.Name != "main" && buildO != "" { + return fmt.Errorf("cannot set -o when building non-main package") + } + + var nmpkgs map[string]bool + switch buildTarget { + case "android": + if pkg.Name != "main" { + return goBuild(pkg.ImportPath, androidArmEnv) + } + nmpkgs, err = goAndroidBuild(pkg) + if err != nil { + return err + } + case "ios": + if runtime.GOOS != "darwin" { + return fmt.Errorf("-target=ios requires darwin host") + } + if pkg.Name != "main" { + if err := goBuild(pkg.ImportPath, darwinArmEnv); err != nil { + return err + } + return goBuild(pkg.ImportPath, darwinArm64Env) + } + nmpkgs, err = goIOSBuild(pkg) + if err != nil { + return err + } + } + + if !nmpkgs["golang.org/x/mobile/app"] { + return fmt.Errorf(`%s does not import "golang.org/x/mobile/app"`, pkg.ImportPath) + } + + return nil +} + +var nmRE = regexp.MustCompile(`[0-9a-f]{8} t (golang.org/x.*/[^.]*)`) + +func extractPkgs(nm string, path string) (map[string]bool, error) { + if buildN { + return map[string]bool{"golang.org/x/mobile/app": true}, nil + } + r, w := io.Pipe() + cmd := exec.Command(nm, path) + cmd.Stdout = w + cmd.Stderr = os.Stderr + + nmpkgs := make(map[string]bool) + errc := make(chan error, 1) + go func() { + s := bufio.NewScanner(r) + for s.Scan() { + if res := nmRE.FindStringSubmatch(s.Text()); res != nil { + nmpkgs[res[1]] = true + } + } + errc <- s.Err() + }() + + err := cmd.Run() + w.Close() + if err != nil { + return nil, fmt.Errorf("%s %s: %v", nm, path, err) + } + if err := <-errc; err != nil { + return nil, fmt.Errorf("%s %s: %v", nm, path, err) + } + return nmpkgs, nil +} + +func importsApp(pkg *build.Package) error { + // Building a program, make sure it is appropriate for mobile. + for _, path := range pkg.Imports { + if path == "golang.org/x/mobile/app" { + return nil + } + } + return fmt.Errorf(`%s does not import "golang.org/x/mobile/app"`, pkg.ImportPath) +} + +var xout io.Writer = os.Stderr + +func printcmd(format string, args ...interface{}) { + cmd := fmt.Sprintf(format+"\n", args...) + if tmpdir != "" { + cmd = strings.Replace(cmd, tmpdir, "$WORK", -1) + } + if androidHome := os.Getenv("ANDROID_HOME"); androidHome != "" { + cmd = strings.Replace(cmd, androidHome, "$ANDROID_HOME", -1) + } + if gomobilepath != "" { + cmd = strings.Replace(cmd, gomobilepath, "$GOMOBILE", -1) + } + if goroot := goEnv("GOROOT"); goroot != "" { + cmd = strings.Replace(cmd, goroot, "$GOROOT", -1) + } + if gopath := goEnv("GOPATH"); gopath != "" { + cmd = strings.Replace(cmd, gopath, "$GOPATH", -1) + } + if env := os.Getenv("HOME"); env != "" { + cmd = strings.Replace(cmd, env, "$HOME", -1) + } + if env := os.Getenv("HOMEPATH"); env != "" { + cmd = strings.Replace(cmd, env, "$HOMEPATH", -1) + } + fmt.Fprint(xout, cmd) +} + +// "Build flags", used by multiple commands. +var ( + buildA bool // -a + buildI bool // -i + buildN bool // -n + buildV bool // -v + buildX bool // -x + buildO string // -o + buildGcflags string // -gcflags + buildLdflags string // -ldflags + buildTarget string // -target + buildWork bool // -work +) + +func addBuildFlags(cmd *command) { + cmd.flag.StringVar(&buildO, "o", "", "") + cmd.flag.StringVar(&buildGcflags, "gcflags", "", "") + cmd.flag.StringVar(&buildLdflags, "ldflags", "", "") + cmd.flag.StringVar(&buildTarget, "target", "android", "") + + cmd.flag.BoolVar(&buildA, "a", false, "") + cmd.flag.BoolVar(&buildI, "i", false, "") + cmd.flag.Var((*stringsFlag)(&ctx.BuildTags), "tags", "") +} + +func addBuildFlagsNVXWork(cmd *command) { + cmd.flag.BoolVar(&buildN, "n", false, "") + cmd.flag.BoolVar(&buildV, "v", false, "") + cmd.flag.BoolVar(&buildX, "x", false, "") + cmd.flag.BoolVar(&buildWork, "work", false, "") +} + +type binInfo struct { + hasPkgApp bool + hasPkgAL bool +} + +func init() { + addBuildFlags(cmdBuild) + addBuildFlagsNVXWork(cmdBuild) + + addBuildFlags(cmdInstall) + addBuildFlagsNVXWork(cmdInstall) + + addBuildFlagsNVXWork(cmdInit) + + addBuildFlags(cmdBind) + addBuildFlagsNVXWork(cmdBind) +} + +func goBuild(src string, env []string, args ...string) error { + // The -p flag is to speed up darwin/arm builds. + // Remove when golang.org/issue/10477 is resolved. + cmd := exec.Command( + "go", + "build", + fmt.Sprintf("-p=%d", runtime.NumCPU()), + "-pkgdir="+pkgdir(env), + "-tags="+strconv.Quote(strings.Join(ctx.BuildTags, ",")), + ) + if buildV { + cmd.Args = append(cmd.Args, "-v") + } + if buildI { + cmd.Args = append(cmd.Args, "-i") + } + if buildX { + cmd.Args = append(cmd.Args, "-x") + } + if buildGcflags != "" { + cmd.Args = append(cmd.Args, "-gcflags", buildGcflags) + } + if buildLdflags != "" { + cmd.Args = append(cmd.Args, "-ldflags", buildLdflags) + } + if buildWork { + cmd.Args = append(cmd.Args, "-work") + } + cmd.Args = append(cmd.Args, args...) + cmd.Args = append(cmd.Args, src) + cmd.Env = append([]string{}, env...) + return runCmd(cmd) +} diff --git a/src/golang.org/x/mobile/cmd/gomobile/build_androidapp.go b/src/golang.org/x/mobile/cmd/gomobile/build_androidapp.go new file mode 100644 index 0000000000..d9aa54cdc4 --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/build_androidapp.go @@ -0,0 +1,252 @@ +// Copyright 2015 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. + +package main + +import ( + "bytes" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "fmt" + "go/build" + "io" + "io/ioutil" + "log" + "os" + "path" + "path/filepath" + "strings" +) + +func goAndroidBuild(pkg *build.Package) (map[string]bool, error) { + libName := path.Base(pkg.ImportPath) + manifestData, err := ioutil.ReadFile(filepath.Join(pkg.Dir, "AndroidManifest.xml")) + if err != nil { + if !os.IsNotExist(err) { + return nil, err + } + buf := new(bytes.Buffer) + buf.WriteString(``) + err := manifestTmpl.Execute(buf, manifestTmplData{ + // TODO(crawshaw): a better package path. + JavaPkgPath: "org.golang.todo." + libName, + Name: libName, + LibName: libName, + }) + if err != nil { + return nil, err + } + manifestData = buf.Bytes() + if buildV { + fmt.Fprintf(os.Stderr, "generated AndroidManifest.xml:\n%s\n", manifestData) + } + } else { + libName, err = manifestLibName(manifestData) + if err != nil { + return nil, err + } + } + libPath := filepath.Join(tmpdir, "lib"+libName+".so") + + err = goBuild( + pkg.ImportPath, + androidArmEnv, + "-buildmode=c-shared", + "-o", libPath, + ) + if err != nil { + return nil, err + } + + nmpkgs, err := extractPkgs(androidArmNM, libPath) + if err != nil { + return nil, err + } + + block, _ := pem.Decode([]byte(debugCert)) + if block == nil { + return nil, errors.New("no debug cert") + } + privKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + + if buildO == "" { + buildO = filepath.Base(pkg.Dir) + ".apk" + } + if !strings.HasSuffix(buildO, ".apk") { + return nil, fmt.Errorf("output file name %q does not end in '.apk'", buildO) + } + var out io.Writer + if !buildN { + f, err := os.Create(buildO) + if err != nil { + return nil, err + } + defer func() { + if cerr := f.Close(); err == nil { + err = cerr + } + }() + out = f + } + + var apkw *Writer + if !buildN { + apkw = NewWriter(out, privKey) + } + apkwcreate := func(name string) (io.Writer, error) { + if buildV { + fmt.Fprintf(os.Stderr, "apk: %s\n", name) + } + if buildN { + return ioutil.Discard, nil + } + return apkw.Create(name) + } + + w, err := apkwcreate("AndroidManifest.xml") + if err != nil { + return nil, err + } + if _, err := w.Write(manifestData); err != nil { + return nil, err + } + + w, err = apkwcreate("classes.dex") + if err != nil { + return nil, err + } + dexData, err := base64.StdEncoding.DecodeString(dexStr) + if err != nil { + log.Fatal("internal error bad dexStr: %v", err) + } + if _, err := w.Write(dexData); err != nil { + return nil, err + } + + w, err = apkwcreate("lib/armeabi/lib" + libName + ".so") + if err != nil { + return nil, err + } + if !buildN { + r, err := os.Open(libPath) + if err != nil { + return nil, err + } + if _, err := io.Copy(w, r); err != nil { + return nil, err + } + } + + if nmpkgs["golang.org/x/mobile/exp/audio/al"] { + alDir := filepath.Join(ndkccpath, "openal/lib") + filepath.Walk(alDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + name := "lib/" + path[len(alDir)+1:] + w, err := apkwcreate(name) + if err != nil { + return err + } + if !buildN { + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + _, err = io.Copy(w, f) + } + return err + }) + } + + // Add any assets. + assetsDir := filepath.Join(pkg.Dir, "assets") + assetsDirExists := true + fi, err := os.Stat(assetsDir) + if err != nil { + if os.IsNotExist(err) { + assetsDirExists = false + } else { + return nil, err + } + } else { + assetsDirExists = fi.IsDir() + } + if assetsDirExists { + err = filepath.Walk(assetsDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + name := "assets/" + path[len(assetsDir)+1:] + w, err := apkwcreate(name) + if err != nil { + return err + } + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + _, err = io.Copy(w, f) + return err + }) + if err != nil { + return nil, fmt.Errorf("asset %v", err) + } + } + + // TODO: add gdbserver to apk? + + if !buildN { + if err := apkw.Close(); err != nil { + return nil, err + } + } + + return nmpkgs, nil +} + +// A random uninteresting private key. +// Must be consistent across builds so newer app versions can be installed. +const debugCert = ` +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAy6ItnWZJ8DpX9R5FdWbS9Kr1U8Z7mKgqNByGU7No99JUnmyu +NQ6Uy6Nj0Gz3o3c0BXESECblOC13WdzjsH1Pi7/L9QV8jXOXX8cvkG5SJAyj6hcO +LOapjDiN89NXjXtyv206JWYvRtpexyVrmHJgRAw3fiFI+m4g4Qop1CxcIF/EgYh7 +rYrqh4wbCM1OGaCleQWaOCXxZGm+J5YNKQcWpjZRrDrb35IZmlT0bK46CXUKvCqK +x7YXHgfhC8ZsXCtsScKJVHs7gEsNxz7A0XoibFw6DoxtjKzUCktnT0w3wxdY7OTj +9AR8mobFlM9W3yirX8TtwekWhDNTYEu8dwwykwIDAQABAoIBAA2hjpIhvcNR9H9Z +BmdEecydAQ0ZlT5zy1dvrWI++UDVmIp+Ve8BSd6T0mOqV61elmHi3sWsBN4M1Rdz +3N38lW2SajG9q0fAvBpSOBHgAKmfGv3Ziz5gNmtHgeEXfZ3f7J95zVGhlHqWtY95 +JsmuplkHxFMyITN6WcMWrhQg4A3enKLhJLlaGLJf9PeBrvVxHR1/txrfENd2iJBH +FmxVGILL09fIIktJvoScbzVOneeWXj5vJGzWVhB17DHBbANGvVPdD5f+k/s5aooh +hWAy/yLKocr294C4J+gkO5h2zjjjSGcmVHfrhlXQoEPX+iW1TGoF8BMtl4Llc+jw +lKWKfpECgYEA9C428Z6CvAn+KJ2yhbAtuRo41kkOVoiQPtlPeRYs91Pq4+NBlfKO +2nWLkyavVrLx4YQeCeaEU2Xoieo9msfLZGTVxgRlztylOUR+zz2FzDBYGicuUD3s +EqC0Wv7tiX6dumpWyOcVVLmR9aKlOUzA9xemzIsWUwL3PpyONhKSq7kCgYEA1X2F +f2jKjoOVzglhtuX4/SP9GxS4gRf9rOQ1Q8DzZhyH2LZ6Dnb1uEQvGhiqJTU8CXxb +7odI0fgyNXq425Nlxc1Tu0G38TtJhwrx7HWHuFcbI/QpRtDYLWil8Zr7Q3BT9rdh +moo4m937hLMvqOG9pyIbyjOEPK2WBCtKW5yabqsCgYEAu9DkUBr1Qf+Jr+IEU9I8 +iRkDSMeusJ6gHMd32pJVCfRRQvIlG1oTyTMKpafmzBAd/rFpjYHynFdRcutqcShm +aJUq3QG68U9EAvWNeIhA5tr0mUEz3WKTt4xGzYsyWES8u4tZr3QXMzD9dOuinJ1N ++4EEumXtSPKKDG3M8Qh+KnkCgYBUEVSTYmF5EynXc2xOCGsuy5AsrNEmzJqxDUBI +SN/P0uZPmTOhJIkIIZlmrlW5xye4GIde+1jajeC/nG7U0EsgRAV31J4pWQ5QJigz +0+g419wxIUFryGuIHhBSfpP472+w1G+T2mAGSLh1fdYDq7jx6oWE7xpghn5vb9id +EKLjdwKBgBtz9mzbzutIfAW0Y8F23T60nKvQ0gibE92rnUbjPnw8HjL3AZLU05N+ +cSL5bhq0N5XHK77sscxW9vXjG0LJMXmFZPp9F6aV6ejkMIXyJ/Yz/EqeaJFwilTq +Mc6xR47qkdzu0dQ1aPm4XD7AWDtIvPo/GG2DKOucLBbQc2cOWtKS +-----END RSA PRIVATE KEY----- +` diff --git a/src/golang.org/x/mobile/cmd/gomobile/build_darwin_test.go b/src/golang.org/x/mobile/cmd/gomobile/build_darwin_test.go new file mode 100644 index 0000000000..817b0cad94 --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/build_darwin_test.go @@ -0,0 +1,58 @@ +// Copyright 2015 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. + +package main + +import ( + "bytes" + "os" + "path/filepath" + "testing" + "text/template" +) + +func TestIOSBuild(t *testing.T) { + buf := new(bytes.Buffer) + defer func() { + xout = os.Stderr + buildN = false + buildX = false + }() + xout = buf + buildN = true + buildX = true + buildO = "basic.app" + buildTarget = "ios" + gopath = filepath.SplitList(os.Getenv("GOPATH"))[0] + cmdBuild.flag.Parse([]string{"golang.org/x/mobile/example/basic"}) + err := runBuild(cmdBuild) + if err != nil { + t.Log(buf.String()) + t.Fatal(err) + } + + diff, err := diffOutput(buf.String(), iosBuildTmpl) + if err != nil { + t.Fatalf("computing diff failed: %v", err) + } + if diff != "" { + t.Errorf("unexpected output:\n%s", diff) + } +} + +var iosBuildTmpl = template.Must(infoplistTmpl.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile +WORK=$WORK +mkdir -p $WORK/main.xcodeproj +echo "{{.Xproj}}" > $WORK/main.xcodeproj/project.pbxproj +mkdir -p $WORK/main +echo "{{template "infoplist" .Xinfo}}" > $WORK/main/Info.plist +mkdir -p $WORK/main/Images.xcassets/AppIcon.appiconset +echo "{{.Xcontents}}" > $WORK/main/Images.xcassets/AppIcon.appiconset/Contents.json +GOOS=darwin GOARCH=arm GOARM=7 CC=clang-iphoneos CXX=clang-iphoneos CGO_CFLAGS=-isysroot=iphoneos -arch armv7 CGO_LDFLAGS=-isysroot=iphoneos -arch armv7 CGO_ENABLED=1 go build -p={{.NumCPU}} -pkgdir=$GOMOBILE/pkg_darwin_arm -tags="" -x -tags=ios -o=$WORK/arm golang.org/x/mobile/example/basic +GOOS=darwin GOARCH=arm64 CC=clang-iphoneos CXX=clang-iphoneos CGO_CFLAGS=-isysroot=iphoneos -arch arm64 CGO_LDFLAGS=-isysroot=iphoneos -arch arm64 CGO_ENABLED=1 go build -p={{.NumCPU}} -pkgdir=$GOMOBILE/pkg_darwin_arm64 -tags="" -x -tags=ios -o=$WORK/arm64 golang.org/x/mobile/example/basic +xcrun lipo -create $WORK/arm $WORK/arm64 -o $WORK/main/main +mkdir -p $WORK/main/assets +xcrun xcodebuild -configuration Release -project $WORK/main.xcodeproj +mv $WORK/build/Release-iphoneos/main.app basic.app +`)) diff --git a/src/golang.org/x/mobile/cmd/gomobile/build_iosapp.go b/src/golang.org/x/mobile/cmd/gomobile/build_iosapp.go new file mode 100644 index 0000000000..252db6b776 --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/build_iosapp.go @@ -0,0 +1,520 @@ +// Copyright 2015 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. + +package main + +import ( + "bytes" + "fmt" + "go/build" + "io/ioutil" + "os" + "os/exec" + "path" + "path/filepath" + "strings" + "text/template" +) + +func goIOSBuild(pkg *build.Package) (map[string]bool, error) { + src := pkg.ImportPath + if buildO != "" && !strings.HasSuffix(buildO, ".app") { + return nil, fmt.Errorf("-o must have an .app for target=ios") + } + + infoplist := new(bytes.Buffer) + if err := infoplistTmpl.Execute(infoplist, manifestTmplData{ + Name: strings.Title(path.Base(pkg.ImportPath)), + }); err != nil { + return nil, err + } + + files := []struct { + name string + contents []byte + }{ + {tmpdir + "/main.xcodeproj/project.pbxproj", []byte(projPbxproj)}, + {tmpdir + "/main/Info.plist", infoplist.Bytes()}, + {tmpdir + "/main/Images.xcassets/AppIcon.appiconset/Contents.json", []byte(contentsJSON)}, + } + + for _, file := range files { + if err := mkdir(filepath.Dir(file.name)); err != nil { + return nil, err + } + if buildX { + printcmd("echo \"%s\" > %s", file.contents, file.name) + } + if !buildN { + if err := ioutil.WriteFile(file.name, file.contents, 0644); err != nil { + return nil, err + } + } + } + + armPath := filepath.Join(tmpdir, "arm") + if err := goBuild(src, darwinArmEnv, "-tags=ios", "-o="+armPath); err != nil { + return nil, err + } + nmpkgs, err := extractPkgs(darwinArmNM, armPath) + if err != nil { + return nil, err + } + + arm64Path := filepath.Join(tmpdir, "arm64") + if err := goBuild(src, darwinArm64Env, "-tags=ios", "-o="+arm64Path); err != nil { + return nil, err + } + + // Apple requires builds to target both darwin/arm and darwin/arm64. + // We are using lipo tool to build multiarchitecture binaries. + // TODO(jbd): Investigate the new announcements about iO9's fat binary + // size limitations are breaking this feature. + cmd := exec.Command( + "xcrun", "lipo", + "-create", armPath, arm64Path, + "-o", filepath.Join(tmpdir, "main/main"), + ) + if err := runCmd(cmd); err != nil { + return nil, err + } + + // TODO(jbd): Set the launcher icon. + if err := iosCopyAssets(pkg, tmpdir); err != nil { + return nil, err + } + + // Build and move the release build to the output directory. + cmd = exec.Command( + "xcrun", "xcodebuild", + "-configuration", "Release", + "-project", tmpdir+"/main.xcodeproj", + ) + if err := runCmd(cmd); err != nil { + return nil, err + } + + // TODO(jbd): Fallback to copying if renaming fails. + if buildO == "" { + buildO = path.Base(pkg.ImportPath) + ".app" + } + if buildX { + printcmd("mv %s %s", tmpdir+"/build/Release-iphoneos/main.app", buildO) + } + if !buildN { + // if output already exists, remove. + if err := os.RemoveAll(buildO); err != nil { + return nil, err + } + if err := os.Rename(tmpdir+"/build/Release-iphoneos/main.app", buildO); err != nil { + return nil, err + } + } + return nmpkgs, nil +} + +func iosCopyAssets(pkg *build.Package, xcodeProjDir string) error { + dstAssets := xcodeProjDir + "/main/assets" + if err := mkdir(dstAssets); err != nil { + return err + } + + srcAssets := filepath.Join(pkg.Dir, "assets") + fi, err := os.Stat(srcAssets) + if err != nil { + if os.IsNotExist(err) { + // skip walking through the directory to deep copy. + return nil + } + return err + } + if !fi.IsDir() { + // skip walking through to deep copy. + return nil + } + + return filepath.Walk(srcAssets, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + dst := dstAssets + "/" + path[len(srcAssets)+1:] + return copyFile(dst, path) + }) +} + +type infoplistTmplData struct { + Name string +} + +var infoplistTmpl = template.Must(template.New("infoplist").Parse(` + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + main + CFBundleIdentifier + org.golang.todo.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + {{.Name}} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + +`)) + +const projPbxproj = `// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 254BB84F1B1FD08900C56DE9 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 254BB84E1B1FD08900C56DE9 /* Images.xcassets */; }; + 254BB8681B1FD16500C56DE9 /* main in Resources */ = {isa = PBXBuildFile; fileRef = 254BB8671B1FD16500C56DE9 /* main */; }; + 25FB30331B30FDEE0005924C /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 25FB30321B30FDEE0005924C /* assets */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 254BB83E1B1FD08900C56DE9 /* main.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = main.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 254BB8421B1FD08900C56DE9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 254BB84E1B1FD08900C56DE9 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + 254BB8671B1FD16500C56DE9 /* main */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = main; sourceTree = ""; }; + 25FB30321B30FDEE0005924C /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = assets; path = main/assets; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXGroup section */ + 254BB8351B1FD08900C56DE9 = { + isa = PBXGroup; + children = ( + 25FB30321B30FDEE0005924C /* assets */, + 254BB8401B1FD08900C56DE9 /* main */, + 254BB83F1B1FD08900C56DE9 /* Products */, + ); + sourceTree = ""; + usesTabs = 0; + }; + 254BB83F1B1FD08900C56DE9 /* Products */ = { + isa = PBXGroup; + children = ( + 254BB83E1B1FD08900C56DE9 /* main.app */, + ); + name = Products; + sourceTree = ""; + }; + 254BB8401B1FD08900C56DE9 /* main */ = { + isa = PBXGroup; + children = ( + 254BB8671B1FD16500C56DE9 /* main */, + 254BB84E1B1FD08900C56DE9 /* Images.xcassets */, + 254BB8411B1FD08900C56DE9 /* Supporting Files */, + ); + path = main; + sourceTree = ""; + }; + 254BB8411B1FD08900C56DE9 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 254BB8421B1FD08900C56DE9 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 254BB83D1B1FD08900C56DE9 /* main */ = { + isa = PBXNativeTarget; + buildConfigurationList = 254BB8611B1FD08900C56DE9 /* Build configuration list for PBXNativeTarget "main" */; + buildPhases = ( + 254BB83C1B1FD08900C56DE9 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = main; + productName = main; + productReference = 254BB83E1B1FD08900C56DE9 /* main.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 254BB8361B1FD08900C56DE9 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0630; + ORGANIZATIONNAME = Developer; + TargetAttributes = { + 254BB83D1B1FD08900C56DE9 = { + CreatedOnToolsVersion = 6.3.1; + }; + }; + }; + buildConfigurationList = 254BB8391B1FD08900C56DE9 /* Build configuration list for PBXProject "main" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 254BB8351B1FD08900C56DE9; + productRefGroup = 254BB83F1B1FD08900C56DE9 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 254BB83D1B1FD08900C56DE9 /* main */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 254BB83C1B1FD08900C56DE9 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 25FB30331B30FDEE0005924C /* assets in Resources */, + 254BB8681B1FD16500C56DE9 /* main in Resources */, + 254BB84F1B1FD08900C56DE9 /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 254BB85F1B1FD08900C56DE9 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.3; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 254BB8601B1FD08900C56DE9 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.3; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 254BB8621B1FD08900C56DE9 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = main/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 254BB8631B1FD08900C56DE9 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = main/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 254BB8391B1FD08900C56DE9 /* Build configuration list for PBXProject "main" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 254BB85F1B1FD08900C56DE9 /* Debug */, + 254BB8601B1FD08900C56DE9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 254BB8611B1FD08900C56DE9 /* Build configuration list for PBXNativeTarget "main" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 254BB8621B1FD08900C56DE9 /* Debug */, + 254BB8631B1FD08900C56DE9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 254BB8361B1FD08900C56DE9 /* Project object */; +} +` + +const contentsJSON = `{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} +` diff --git a/src/golang.org/x/mobile/cmd/gomobile/build_test.go b/src/golang.org/x/mobile/cmd/gomobile/build_test.go new file mode 100644 index 0000000000..dbee3eecb9 --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/build_test.go @@ -0,0 +1,50 @@ +// Copyright 2015 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. + +package main + +import ( + "bytes" + "os" + "path/filepath" + "testing" + "text/template" +) + +func TestAndroidBuild(t *testing.T) { + buf := new(bytes.Buffer) + defer func() { + xout = os.Stderr + buildN = false + buildX = false + }() + xout = buf + buildN = true + buildX = true + buildO = "basic.apk" + buildTarget = "android" + gopath = filepath.SplitList(os.Getenv("GOPATH"))[0] + if goos == "windows" { + os.Setenv("HOMEDRIVE", "C:") + } + cmdBuild.flag.Parse([]string{"golang.org/x/mobile/example/basic"}) + err := runBuild(cmdBuild) + if err != nil { + t.Log(buf.String()) + t.Fatal(err) + } + + diff, err := diffOutput(buf.String(), androidBuildTmpl) + if err != nil { + t.Fatalf("computing diff failed: %v", err) + } + if diff != "" { + t.Errorf("unexpected output:\n%s", diff) + } +} + +var androidBuildTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile +WORK=$WORK +GOOS=android GOARCH=arm GOARM=7 CC=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-gcc{{.EXE}} CXX=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-g++{{.EXE}} CGO_ENABLED=1 go build -p={{.NumCPU}} -pkgdir=$GOMOBILE/pkg_android_arm -tags="" -x -buildmode=c-shared -o $WORK/libbasic.so golang.org/x/mobile/example/basic +`)) diff --git a/src/golang.org/x/mobile/cmd/gomobile/cert.go b/src/golang.org/x/mobile/cmd/gomobile/cert.go new file mode 100644 index 0000000000..0aa00b3a49 --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/cert.go @@ -0,0 +1,154 @@ +// Copyright 2015 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. + +package main + +import ( + "crypto" + "crypto/rsa" + "crypto/sha1" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "io" + "math/big" + "time" +) + +// signPKCS7 does the minimal amount of work necessary to embed an RSA +// signature into a PKCS#7 certificate. +// +// We prepare the certificate using the x509 package, read it back in +// to our custom data type and then write it back out with the signature. +func signPKCS7(rand io.Reader, priv *rsa.PrivateKey, msg []byte) ([]byte, error) { + const serialNumber = 0x5462c4dd // arbitrary + name := pkix.Name{CommonName: "gomobile"} + + template := &x509.Certificate{ + SerialNumber: big.NewInt(serialNumber), + SignatureAlgorithm: x509.SHA1WithRSA, + Subject: name, + } + + b, err := x509.CreateCertificate(rand, template, template, priv.Public(), priv) + if err != nil { + return nil, err + } + + c := certificate{} + if _, err := asn1.Unmarshal(b, &c); err != nil { + return nil, err + } + + h := sha1.New() + h.Write(msg) + hashed := h.Sum(nil) + + signed, err := rsa.SignPKCS1v15(rand, priv, crypto.SHA1, hashed) + if err != nil { + return nil, err + } + + content := pkcs7SignedData{ + ContentType: oidSignedData, + Content: signedData{ + Version: 1, + DigestAlgorithms: []pkix.AlgorithmIdentifier{{ + Algorithm: oidSHA1, + Parameters: asn1.RawValue{Tag: 5}, + }}, + ContentInfo: contentInfo{Type: oidData}, + Certificates: c, + SignerInfos: []signerInfo{{ + Version: 1, + IssuerAndSerialNumber: issuerAndSerialNumber{ + Issuer: name.ToRDNSequence(), + SerialNumber: serialNumber, + }, + DigestAlgorithm: pkix.AlgorithmIdentifier{ + Algorithm: oidSHA1, + Parameters: asn1.RawValue{Tag: 5}, + }, + DigestEncryptionAlgorithm: pkix.AlgorithmIdentifier{ + Algorithm: oidRSAEncryption, + Parameters: asn1.RawValue{Tag: 5}, + }, + EncryptedDigest: signed, + }}, + }, + } + + return asn1.Marshal(content) +} + +type pkcs7SignedData struct { + ContentType asn1.ObjectIdentifier + Content signedData `asn1:"tag:0,explicit"` +} + +// signedData is defined in rfc2315, section 9.1. +type signedData struct { + Version int + DigestAlgorithms []pkix.AlgorithmIdentifier `asn1:"set"` + ContentInfo contentInfo + Certificates certificate `asn1:"tag0,explicit"` + SignerInfos []signerInfo `asn1:"set"` +} + +type contentInfo struct { + Type asn1.ObjectIdentifier + // Content is optional in PKCS#7 and not provided here. +} + +// certificate is defined in rfc2459, section 4.1. +type certificate struct { + TBSCertificate tbsCertificate + SignatureAlgorithm pkix.AlgorithmIdentifier + SignatureValue asn1.BitString +} + +// tbsCertificate is defined in rfc2459, section 4.1. +type tbsCertificate struct { + Version int `asn1:"tag:0,default:2,explicit"` + SerialNumber int + Signature pkix.AlgorithmIdentifier + Issuer pkix.RDNSequence // pkix.Name + Validity validity + Subject pkix.RDNSequence // pkix.Name + SubjectPKI subjectPublicKeyInfo +} + +// validity is defined in rfc2459, section 4.1. +type validity struct { + NotBefore time.Time + NotAfter time.Time +} + +// subjectPublicKeyInfo is defined in rfc2459, section 4.1. +type subjectPublicKeyInfo struct { + Algorithm pkix.AlgorithmIdentifier + SubjectPublicKey asn1.BitString +} + +type signerInfo struct { + Version int + IssuerAndSerialNumber issuerAndSerialNumber + DigestAlgorithm pkix.AlgorithmIdentifier + DigestEncryptionAlgorithm pkix.AlgorithmIdentifier + EncryptedDigest []byte +} + +type issuerAndSerialNumber struct { + Issuer pkix.RDNSequence // pkix.Name + SerialNumber int +} + +// Various ASN.1 Object Identifies, mostly from rfc3852. +var ( + oidPKCS7 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7} + oidData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1} + oidSignedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2} + oidSHA1 = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26} + oidRSAEncryption = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} +) diff --git a/src/golang.org/x/mobile/cmd/gomobile/cert_test.go b/src/golang.org/x/mobile/cmd/gomobile/cert_test.go new file mode 100644 index 0000000000..de1275f3e2 --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/cert_test.go @@ -0,0 +1,104 @@ +// Copyright 2015 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. + +package main + +import ( + "crypto/rand" + "crypto/x509" + "encoding/pem" + "io/ioutil" + "os" + "os/exec" + "testing" +) + +func TestSignPKCS7(t *testing.T) { + // Setup RSA key. + block, _ := pem.Decode([]byte(testKey)) + if block == nil { + t.Fatal("no cert") + } + privKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + t.Fatal(err) + } + + content := "Hello world,\nThis is signed." + cert, err := signPKCS7(rand.Reader, privKey, []byte(content)) + if err != nil { + t.Fatal(err) + } + sig, err := ioutil.TempFile("", "content.rsa") + if err != nil { + t.Fatal(err) + } + sigPath := sig.Name() + defer os.Remove(sigPath) + if _, err := sig.Write(cert); err != nil { + t.Fatal(err) + } + if err := sig.Close(); err != nil { + t.Fatal(err) + } + + if openssl, err := exec.LookPath("openssl"); err != nil { + t.Log("command openssl not found, skipping") + } else { + cmd := exec.Command( + openssl, "asn1parse", + "-inform", "DER", + "-i", + "-in", sigPath, + ) + out, err := cmd.CombinedOutput() + t.Logf("%v:\n%s", cmd.Args, out) + if err != nil { + t.Errorf("bad asn.1: %v", err) + } + } + + if keytool, err := exec.LookPath("keytool"); err != nil { + t.Log("command keytool not found, skipping") + } else if err := exec.Command(keytool, "-v").Run(); err != nil { + t.Log("command keytool not functioning: %s, skipping", err) + } else { + cmd := exec.Command(keytool, "-v", "-printcert", "-file", sigPath) + out, err := cmd.CombinedOutput() + t.Logf("%v:\n%s", cmd.Args, out) + if err != nil { + t.Errorf("keytool cannot parse signature: %v", err) + } + } +} + +const testKey = ` +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAy6ItnWZJ8DpX9R5FdWbS9Kr1U8Z7mKgqNByGU7No99JUnmyu +NQ6Uy6Nj0Gz3o3c0BXESECblOC13WdzjsH1Pi7/L9QV8jXOXX8cvkG5SJAyj6hcO +LOapjDiN89NXjXtyv206JWYvRtpexyVrmHJgRAw3fiFI+m4g4Qop1CxcIF/EgYh7 +rYrqh4wbCM1OGaCleQWaOCXxZGm+J5YNKQcWpjZRrDrb35IZmlT0bK46CXUKvCqK +x7YXHgfhC8ZsXCtsScKJVHs7gEsNxz7A0XoibFw6DoxtjKzUCktnT0w3wxdY7OTj +9AR8mobFlM9W3yirX8TtwekWhDNTYEu8dwwykwIDAQABAoIBAA2hjpIhvcNR9H9Z +BmdEecydAQ0ZlT5zy1dvrWI++UDVmIp+Ve8BSd6T0mOqV61elmHi3sWsBN4M1Rdz +3N38lW2SajG9q0fAvBpSOBHgAKmfGv3Ziz5gNmtHgeEXfZ3f7J95zVGhlHqWtY95 +JsmuplkHxFMyITN6WcMWrhQg4A3enKLhJLlaGLJf9PeBrvVxHR1/txrfENd2iJBH +FmxVGILL09fIIktJvoScbzVOneeWXj5vJGzWVhB17DHBbANGvVPdD5f+k/s5aooh +hWAy/yLKocr294C4J+gkO5h2zjjjSGcmVHfrhlXQoEPX+iW1TGoF8BMtl4Llc+jw +lKWKfpECgYEA9C428Z6CvAn+KJ2yhbAtuRo41kkOVoiQPtlPeRYs91Pq4+NBlfKO +2nWLkyavVrLx4YQeCeaEU2Xoieo9msfLZGTVxgRlztylOUR+zz2FzDBYGicuUD3s +EqC0Wv7tiX6dumpWyOcVVLmR9aKlOUzA9xemzIsWUwL3PpyONhKSq7kCgYEA1X2F +f2jKjoOVzglhtuX4/SP9GxS4gRf9rOQ1Q8DzZhyH2LZ6Dnb1uEQvGhiqJTU8CXxb +7odI0fgyNXq425Nlxc1Tu0G38TtJhwrx7HWHuFcbI/QpRtDYLWil8Zr7Q3BT9rdh +moo4m937hLMvqOG9pyIbyjOEPK2WBCtKW5yabqsCgYEAu9DkUBr1Qf+Jr+IEU9I8 +iRkDSMeusJ6gHMd32pJVCfRRQvIlG1oTyTMKpafmzBAd/rFpjYHynFdRcutqcShm +aJUq3QG68U9EAvWNeIhA5tr0mUEz3WKTt4xGzYsyWES8u4tZr3QXMzD9dOuinJ1N ++4EEumXtSPKKDG3M8Qh+KnkCgYBUEVSTYmF5EynXc2xOCGsuy5AsrNEmzJqxDUBI +SN/P0uZPmTOhJIkIIZlmrlW5xye4GIde+1jajeC/nG7U0EsgRAV31J4pWQ5QJigz +0+g419wxIUFryGuIHhBSfpP472+w1G+T2mAGSLh1fdYDq7jx6oWE7xpghn5vb9id +EKLjdwKBgBtz9mzbzutIfAW0Y8F23T60nKvQ0gibE92rnUbjPnw8HjL3AZLU05N+ +cSL5bhq0N5XHK77sscxW9vXjG0LJMXmFZPp9F6aV6ejkMIXyJ/Yz/EqeaJFwilTq +Mc6xR47qkdzu0dQ1aPm4XD7AWDtIvPo/GG2DKOucLBbQc2cOWtKS +-----END RSA PRIVATE KEY----- +` diff --git a/src/golang.org/x/mobile/cmd/gomobile/dex.go b/src/golang.org/x/mobile/cmd/gomobile/dex.go new file mode 100644 index 0000000000..b1a191a8f7 --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/dex.go @@ -0,0 +1,44 @@ +// Copyright 2015 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. + +// File is automatically generated by gendex.go. DO NOT EDIT. + +package main + +var dexStr = `ZGV4CjAzNQBgMPcbh5xnMiJMhJmv7LSwWLsd8n8QqVIUBwAAcAAAAHhWNBIAAAAAAAAAAH` + + `QGAAApAAAAcAAAAA8AAAAUAQAADAAAAFABAAACAAAA4AEAABAAAADwAQAAAQAAAHACAACE` + + `BAAAkAIAAJ4DAACmAwAAqgMAAMEDAADEAwAAyQMAAM8DAADSAwAA1gMAANsDAAD5AwAAGg` + + `QAADQEAABXBAAAfAQAAJEEAAClBAAAtQQAAMwEAADgBAAA9AQAAAsFAAAuBQAAMQUAADUF` + + `AABLBQAATgUAAF8FAABwBQAAfQUAAIsFAACWBQAAqQUAALQFAAC/BQAA0QUAANcFAADkBQ` + + `AA+AUAACEGAAArBgAAAwAAAAkAAAAKAAAACwAAAAwAAAANAAAADgAAAA8AAAAQAAAAEQAA` + + `ABIAAAATAAAAFAAAABUAAAAWAAAABAAAAAAAAAB0AwAABQAAAAAAAAB8AwAABgAAAAIAAA` + + `AAAAAABgAAAAMAAAAAAAAACAAAAAQAAACIAwAABgAAAAUAAAAAAAAABgAAAAgAAAAAAAAA` + + `BgAAAAoAAAAAAAAABwAAAAoAAACQAwAAFgAAAA4AAAAAAAAAFwAAAA4AAACYAwAAFwAAAA` + + `4AAACQAwAABAAGACcAAAANAA0AIgAAAAEACQAAAAAAAQAKACgAAAADAAIAHQAAAAUABAAb` + + `AAAABgAIACAAAAAHAAAAGQAAAAcAAQAZAAAACAAHABoAAAALAAsAJAAAAA0ACQAAAAAADQ` + + `AGABwAAAANAAMAHgAAAA0ABQAfAAAADQAHACEAAAANAAkAIwAAAA0ACgAoAAAADQAAAAEA` + + `AAABAAAAAAAAAAIAAAAAAAAAWQYAAAAAAAABAAEAAQAAADUGAAAGAAAAcBAAAAAAaQABAA` + + `4ABAABAAMAAQA8BgAAMwAAAG4QDAADAAwAbhALAAMADAFuEAIAAQAMARMCgABuMAMAEAIM` + + `AFQBAAA5AQoAGgABABoBJgBxIAUAEAAOAFQAAAAaARgAbiAEABAADABxEAgAAAAo9A0AGg` + + `EBABoCJQBxMAYAIQAo6wAAAAAAACkAAQABAQkqAgABAAEAAABMBgAACQAAAG4QCgABAAwA` + + `bhAHAAAADAARAAAAAgACAAIAAABRBgAABwAAAHAQDgAAAG8gAQAQAA4AAAACAAAACgAKAA` + + `MAAAAKAAoADAAAAAIAAAACAAAAAQAAAAoAAAABAAAABgAGPGluaXQ+AAJHbwAVR29OYXRp` + + `dmVBY3Rpdml0eS5qYXZhAAFJAANJTEwABElMTEwAAUwAAkxMAANMTEkAHExhbmRyb2lkL2` + + `FwcC9OYXRpdmVBY3Rpdml0eTsAH0xhbmRyb2lkL2NvbnRlbnQvQ29tcG9uZW50TmFtZTsA` + + `GExhbmRyb2lkL2NvbnRlbnQvSW50ZW50OwAhTGFuZHJvaWQvY29udGVudC9wbS9BY3Rpdm` + + `l0eUluZm87ACNMYW5kcm9pZC9jb250ZW50L3BtL1BhY2thZ2VNYW5hZ2VyOwATTGFuZHJv` + + `aWQvb3MvQnVuZGxlOwASTGFuZHJvaWQvdXRpbC9Mb2c7AA5MamF2YS9pby9GaWxlOwAVTG` + + `phdmEvbGFuZy9FeGNlcHRpb247ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xhbmcv` + + `U3lzdGVtOwAVTGphdmEvbGFuZy9UaHJvd2FibGU7ACFMb3JnL2dvbGFuZy9hcHAvR29OYX` + + `RpdmVBY3Rpdml0eTsAAVYAAlZMABRhbmRyb2lkLmFwcC5saWJfbmFtZQABZQAPZ2V0QWJz` + + `b2x1dGVQYXRoAA9nZXRBY3Rpdml0eUluZm8AC2dldENhY2hlRGlyAAxnZXRDb21wb25lbn` + + `QACWdldEludGVudAARZ2V0UGFja2FnZU1hbmFnZXIACWdldFN0cmluZwAJZ2V0VG1wZGly` + + `ABBnb05hdGl2ZUFjdGl2aXR5AARsb2FkAAtsb2FkTGlicmFyeQASbG9hZExpYnJhcnkgZm` + + `FpbGVkACdsb2FkTGlicmFyeTogbm8gbWFuaWZlc3QgbWV0YWRhdGEgZm91bmQACG1ldGFE` + + `YXRhAAhvbkNyZWF0ZQAPAAcOPC0AIQAHDgESEEt/Ansdh0seABQABw4AMAEABw48PAABAA` + + `ICAQoJgYAEkAUFAqwFDQCwBgIB1AYAAAANAAAAAAAAAAEAAAAAAAAAAQAAACkAAABwAAAA` + + `AgAAAA8AAAAUAQAAAwAAAAwAAABQAQAABAAAAAIAAADgAQAABQAAABAAAADwAQAABgAAAA` + + `EAAABwAgAAASAAAAQAAACQAgAAARAAAAUAAAB0AwAAAiAAACkAAACeAwAAAyAAAAQAAAA1` + + `BgAAACAAAAEAAABZBgAAABAAAAEAAAB0BgAA` + + `` diff --git a/src/golang.org/x/mobile/cmd/gomobile/doc.go b/src/golang.org/x/mobile/cmd/gomobile/doc.go new file mode 100644 index 0000000000..0abf357578 --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/doc.go @@ -0,0 +1,143 @@ +// Copyright 2015 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. + +// DO NOT EDIT. GENERATED BY 'gomobile help documentation doc.go'. + +/* +Gomobile is a tool for building and running mobile apps written in Go. + +To install: + + $ go get golang.org/x/mobile/cmd/gomobile + $ gomobile init + +At least Go 1.5 is required. Until it is released, build tip from +source: http://golang.org/doc/install/source + +Initialization rebuilds the standard library and may download +the Android NDK compiler. + +Usage: + + gomobile command [arguments] + +Commands: + + bind build a shared library for android APK and iOS app + build compile android APK and iOS app + init install android compiler toolchain + install compile android APK and install on device + version print version + +Use 'gomobile help [command]' for more information about that command. + + +Build a shared library for android APK and iOS app + +Usage: + + gomobile bind [-target android|ios] [-o output] [build flags] [package] + +Bind generates language bindings for the package named by the import +path, and compiles a library for the named target system. + +The -target flag takes a target system name, either android (the +default) or ios. + +For -target android, the bind command produces an AAR (Android ARchive) +file that archives the precompiled Java API stub classes, the compiled +shared libraries, and all asset files in the /assets subdirectory under +the package directory. The output is named '.aar' by +default. This AAR file is commonly used for binary distribution of an +Android library project and most Android IDEs support AAR import. For +example, in Android Studio (1.2+), an AAR file can be imported using +the module import wizard (File > New > New Module > Import .JAR or +.AAR package), and setting it as a new dependency +(File > Project Structure > Dependencies). This requires 'javac' +(version 1.7+) and Android SDK (API level 9 or newer) to build the +library for Android. The environment variable ANDROID_HOME must be set +to the path to Android SDK. + +For -target ios, gomobile must be run on an OS X machine with Xcode +installed. Support is not complete. + +The -v flag provides verbose output, including the list of packages built. + +The build flags -a, -i, -n, -x, -gcflags, -ldflags, -tags, and -work +are shared with the build command. For documentation, see 'go help build'. + + +Compile android APK and iOS app + +Usage: + + gomobile build [-target android|ios] [-o output] [build flags] [package] + +Build compiles and encodes the app named by the import path. + +The named package must define a main function. + +The -target flag takes a target system name, either android (the +default) or ios. + +For -target android, if an AndroidManifest.xml is defined in the +package directory, it is added to the APK output. Otherwise, a default +manifest is generated. + +For -target ios, gomobile must be run on an OS X machine with Xcode +installed. Support is not complete. + +If the package directory contains an assets subdirectory, its contents +are copied into the output. + +The -o flag specifies the output file name. If not specified, the +output file name depends on the package built. + +The -v flag provides verbose output, including the list of packages built. + +The build flags -a, -i, -n, -x, -gcflags, -ldflags, -tags, and -work are +shared with the build command. For documentation, see 'go help build'. + + +Install android compiler toolchain + +Usage: + + gomobile init [-u] + +Init downloads and installs the Android C++ compiler toolchain. + +The toolchain is installed in $GOPATH/pkg/gomobile. +If the Android C++ compiler toolchain already exists in the path, +it skips download and uses the existing toolchain. + +The -u option forces download and installation of the new toolchain +even when the toolchain exists. + + +Compile android APK and install on device + +Usage: + + gomobile install [-target android] [build flags] [package] + +Install compiles and installs the app named by the import path on the +attached mobile device. + +Only -target android is supported. The 'adb' tool must be on the PATH. + +The build flags -a, -i, -n, -x, -gcflags, -ldflags, -tags, and -work are +shared with the build command. +For documentation, see 'go help build'. + + +Print version + +Usage: + + gomobile version + +Version prints versions of the gomobile binary and tools +*/ +package main diff --git a/src/golang.org/x/mobile/cmd/gomobile/env.go b/src/golang.org/x/mobile/cmd/gomobile/env.go new file mode 100644 index 0000000000..f98dc06461 --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/env.go @@ -0,0 +1,257 @@ +package main + +import ( + "bytes" + "errors" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" +) + +// General mobile build environment. Initialized by envInit. +var ( + cwd string + gomobilepath string // $GOPATH/pkg/gomobile + ndkccpath string // $GOPATH/pkg/gomobile/android-{{.NDK}} + + androidArmEnv []string + darwinArmEnv []string + darwinArm64Env []string + darwin386Env []string + darwinAmd64Env []string + + androidArmNM string + darwinArmNM string +) + +func buildEnvInit() (cleanup func(), err error) { + // Find gomobilepath. + gopath := goEnv("GOPATH") + for _, p := range filepath.SplitList(gopath) { + gomobilepath = filepath.Join(p, "pkg", "gomobile") + if _, err := os.Stat(gomobilepath); buildN || err == nil { + break + } + } + + if err := envInit(); err != nil { + return nil, err + } + + if buildX { + fmt.Fprintln(xout, "GOMOBILE="+gomobilepath) + } + + // Check the toolchain is in a good state. + // Pick a temporary directory for assembling an apk/app. + version, err := goVersion() + if err != nil { + return nil, err + } + if gomobilepath == "" { + return nil, errors.New("toolchain not installed, run `gomobile init`") + } + + cleanupFn := func() { + if buildWork { + fmt.Printf("WORK=%s\n", tmpdir) + return + } + removeAll(tmpdir) + } + if buildN { + tmpdir = "$WORK" + cleanupFn = func() {} + } else { + verpath := filepath.Join(gomobilepath, "version") + installedVersion, err := ioutil.ReadFile(verpath) + if err != nil { + return nil, errors.New("toolchain partially installed, run `gomobile init`") + } + if !bytes.Equal(installedVersion, version) { + return nil, errors.New("toolchain out of date, run `gomobile init`") + } + + tmpdir, err = ioutil.TempDir("", "gomobile-work-") + if err != nil { + return nil, err + } + } + if buildX { + fmt.Fprintln(xout, "WORK="+tmpdir) + } + + return cleanupFn, nil +} + +func envInit() (err error) { + // TODO(crawshaw): cwd only used by ctx.Import, which can take "." + cwd, err = os.Getwd() + if err != nil { + return err + } + + // Setup the cross-compiler environments. + + // TODO(crawshaw): Remove ndkccpath global. + ndkccpath = filepath.Join(gomobilepath, "android-"+ndkVersion) + ndkccbin := filepath.Join(ndkccpath, "arm", "bin") + + exe := "" + if goos == "windows" { + exe = ".exe" + } + androidArmEnv = []string{ + "GOOS=android", + "GOARCH=arm", + "GOARM=7", + "CC=" + filepath.Join(ndkccbin, "arm-linux-androideabi-gcc"+exe), + "CXX=" + filepath.Join(ndkccbin, "arm-linux-androideabi-g++"+exe), + "CGO_ENABLED=1", + } + androidArmNM = filepath.Join(ndkccbin, "arm-linux-androideabi-nm"+exe) + + if runtime.GOOS != "darwin" { + return nil + } + + clang, cflags, err := envClang("iphoneos") + if err != nil { + return err + } + darwinArmEnv = []string{ + "GOOS=darwin", + "GOARCH=arm", + "GOARM=7", + "CC=" + clang, + "CXX=" + clang, + "CGO_CFLAGS=" + cflags + " -arch " + archClang("arm"), + "CGO_LDFLAGS=" + cflags + " -arch " + archClang("arm"), + "CGO_ENABLED=1", + } + darwinArmNM = "nm" + darwinArm64Env = []string{ + "GOOS=darwin", + "GOARCH=arm64", + "CC=" + clang, + "CXX=" + clang, + "CGO_CFLAGS=" + cflags + " -arch " + archClang("arm64"), + "CGO_LDFLAGS=" + cflags + " -arch " + archClang("arm64"), + "CGO_ENABLED=1", + } + + clang, cflags, err = envClang("iphonesimulator") + if err != nil { + return err + } + darwin386Env = []string{ + "GOOS=darwin", + "GOARCH=386", + "CC=" + clang, + "CXX=" + clang, + "CGO_CFLAGS=" + cflags + " -mios-simulator-version-min=6.1 -arch " + archClang("386"), + "CGO_LDFLAGS=" + cflags + " -mios-simulator-version-min=6.1 -arch " + archClang("386"), + "CGO_ENABLED=1", + } + darwinAmd64Env = []string{ + "GOOS=darwin", + "GOARCH=amd64", + "CC=" + clang, + "CXX=" + clang, + "CGO_CFLAGS=" + cflags + " -mios-simulator-version-min=6.1 -arch x86_64", + "CGO_LDFLAGS=" + cflags + " -mios-simulator-version-min=6.1 -arch x86_64", + "CGO_ENABLED=1", + } + + return nil +} + +func envClang(sdkName string) (clang, cflags string, err error) { + if buildN { + return "clang-" + sdkName, "-isysroot=" + sdkName, nil + } + cmd := exec.Command("xcrun", "--sdk", sdkName, "--find", "clang") + out, err := cmd.Output() + if err != nil { + return "", "", fmt.Errorf("xcrun --find: %v\n%s", err, out) + } + clang = strings.TrimSpace(string(out)) + + cmd = exec.Command("xcrun", "--sdk", sdkName, "--show-sdk-path") + out, err = cmd.Output() + if err != nil { + return "", "", fmt.Errorf("xcrun --show-sdk-path: %v\n%s", err, out) + } + sdk := strings.TrimSpace(string(out)) + + return clang, "-isysroot " + sdk, nil +} + +func archClang(goarch string) string { + switch goarch { + case "arm": + return "armv7" + case "arm64": + return "arm64" + case "386": + return "i386" + case "amd64": + return "x86_64" + default: + panic(fmt.Sprintf("unknown GOARCH: %q", goarch)) + } +} + +// environ merges os.Environ and the given "key=value" pairs. +// If a key is in both os.Environ and kv, kv takes precedence. +func environ(kv []string) []string { + cur := os.Environ() + new := make([]string, 0, len(cur)+len(kv)) + + envs := make(map[string]string, len(cur)) + for _, ev := range cur { + elem := strings.SplitN(ev, "=", 2) + if len(elem) != 2 || elem[0] == "" { + // pass the env var of unusual form untouched. + // e.g. Windows may have env var names starting with "=". + new = append(new, ev) + continue + } + if goos == "windows" { + elem[0] = strings.ToUpper(elem[0]) + } + envs[elem[0]] = elem[1] + } + for _, ev := range kv { + elem := strings.SplitN(ev, "=", 2) + if len(elem) != 2 || elem[0] == "" { + panic(fmt.Sprintf("malformed env var %q from input", ev)) + } + if goos == "windows" { + elem[0] = strings.ToUpper(elem[0]) + } + envs[elem[0]] = elem[1] + } + for k, v := range envs { + new = append(new, k+"="+v) + } + return new +} + +func getenv(env []string, key string) string { + prefix := key + "=" + for _, kv := range env { + if strings.HasPrefix(kv, prefix) { + return kv[len(prefix):] + } + } + return "" +} + +func pkgdir(env []string) string { + return gomobilepath + "/pkg_" + getenv(env, "GOOS") + "_" + getenv(env, "GOARCH") +} diff --git a/src/golang.org/x/mobile/cmd/gomobile/gendex.go b/src/golang.org/x/mobile/cmd/gomobile/gendex.go new file mode 100644 index 0000000000..eb90d40c36 --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/gendex.go @@ -0,0 +1,157 @@ +// Copyright 2015 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 ignore + +// Gendex generates a dex file used by Go apps created with gomobile. +// +// The dex is a thin extension of NativeActivity, providing access to +// a few platform features (not the SDK UI) not easily accessible from +// NDK headers. Long term these could be made part of the standard NDK, +// however that would limit gomobile to working with newer versions of +// the Android OS, so we do this while we wait. +// +// Requires ANDROID_HOME be set to the path of the Android SDK, and +// javac must be on the PATH. +package main + +import ( + "bytes" + "encoding/base64" + "errors" + "flag" + "fmt" + "go/format" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" +) + +var outfile = flag.String("o", "", "result will be written file") + +var tmpdir string + +func main() { + flag.Parse() + + var err error + tmpdir, err = ioutil.TempDir("", "gendex-") + if err != nil { + log.Fatal(err) + } + + err = gendex() + os.RemoveAll(tmpdir) + if err != nil { + log.Fatal(err) + } +} + +func gendex() error { + androidHome := os.Getenv("ANDROID_HOME") + if androidHome == "" { + return errors.New("ANDROID_HOME not set") + } + if err := os.MkdirAll(tmpdir+"/work/org/golang/app", 0775); err != nil { + return err + } + javaFiles, err := filepath.Glob("../../app/*.java") + if err != nil { + return err + } + if len(javaFiles) == 0 { + return errors.New("could not find ../../app/*.java files") + } + platform, err := findLast(androidHome + "/platforms") + if err != nil { + return err + } + cmd := exec.Command( + "javac", + "-source", "1.7", + "-target", "1.7", + "-bootclasspath", platform+"/android.jar", + "-d", tmpdir+"/work", + ) + cmd.Args = append(cmd.Args, javaFiles...) + if out, err := cmd.CombinedOutput(); err != nil { + fmt.Println(cmd.Args) + os.Stderr.Write(out) + return err + } + buildTools, err := findLast(androidHome + "/build-tools") + if err != nil { + return err + } + cmd = exec.Command( + buildTools+"/dx", + "--dex", + "--output="+tmpdir+"/classes.dex", + tmpdir+"/work", + ) + if out, err := cmd.CombinedOutput(); err != nil { + os.Stderr.Write(out) + return err + } + src, err := ioutil.ReadFile(tmpdir + "/classes.dex") + if err != nil { + return err + } + data := base64.StdEncoding.EncodeToString(src) + + buf := new(bytes.Buffer) + fmt.Fprint(buf, header) + + var piece string + for len(data) > 0 { + l := 70 + if l > len(data) { + l = len(data) + } + piece, data = data[:l], data[l:] + fmt.Fprintf(buf, "\t`%s` + \n", piece) + } + fmt.Fprintf(buf, "\t``") + out, err := format.Source(buf.Bytes()) + if err != nil { + buf.WriteTo(os.Stderr) + return err + } + + w, err := os.Create(*outfile) + if err != nil { + return err + } + if _, err := w.Write(out); err != nil { + return err + } + if err := w.Close(); err != nil { + return err + } + return nil +} + +func findLast(path string) (string, error) { + dir, err := os.Open(path) + if err != nil { + return "", err + } + children, err := dir.Readdirnames(-1) + if err != nil { + return "", err + } + return path + "/" + children[len(children)-1], nil +} + +var header = `// Copyright 2015 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. + +// File is automatically generated by gendex.go. DO NOT EDIT. + +package main + +var dexStr = ` diff --git a/src/golang.org/x/mobile/cmd/gomobile/init.go b/src/golang.org/x/mobile/cmd/gomobile/init.go new file mode 100644 index 0000000000..2f05af6d25 --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/init.go @@ -0,0 +1,629 @@ +// Copyright 2015 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. + +package main + +// TODO(crawshaw): android/{386,arm64} + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "os/exec" + "path" + "path/filepath" + "runtime" + "strings" + "time" +) + +// useStrippedNDK determines whether the init subcommand fetches the GCC +// toolchain from the original Android NDK, or from the stripped-down NDK +// hosted specifically for the gomobile tool. +// +// There is a significant size different (400MB compared to 30MB). +var useStrippedNDK = true + +const ndkVersion = "ndk-r10e" +const openALVersion = "openal-soft-1.16.0.1" + +var ( + goos = runtime.GOOS + goarch = runtime.GOARCH + ndkarch string +) + +func init() { + switch runtime.GOARCH { + case "amd64": + ndkarch = "x86_64" + case "386": + ndkarch = "x86" + default: + ndkarch = runtime.GOARCH + } +} + +var cmdInit = &command{ + run: runInit, + Name: "init", + Usage: "[-u]", + Short: "install android compiler toolchain", + Long: ` +Init downloads and installs the Android C++ compiler toolchain. + +The toolchain is installed in $GOPATH/pkg/gomobile. +If the Android C++ compiler toolchain already exists in the path, +it skips download and uses the existing toolchain. + +The -u option forces download and installation of the new toolchain +even when the toolchain exists. +`, +} + +var initU bool // -u + +func init() { + cmdInit.flag.BoolVar(&initU, "u", false, "force toolchain download") +} + +func runInit(cmd *command) error { + version, err := goVersion() + if err != nil { + return fmt.Errorf("%v: %s", err, version) + } + + gopaths := filepath.SplitList(goEnv("GOPATH")) + if len(gopaths) == 0 { + return fmt.Errorf("GOPATH is not set") + } + gomobilepath = filepath.Join(gopaths[0], "pkg/gomobile") + ndkccpath = filepath.Join(gopaths[0], "pkg/gomobile/android-"+ndkVersion) + verpath := filepath.Join(gopaths[0], "pkg/gomobile/version") + if buildX || buildN { + fmt.Fprintln(xout, "GOMOBILE="+gomobilepath) + } + removeGomobilepkg() + + if err := mkdir(ndkccpath); err != nil { + return err + } + + if buildN { + tmpdir = filepath.Join(gomobilepath, "work") + } else { + var err error + tmpdir, err = ioutil.TempDir(gomobilepath, "work-") + if err != nil { + return err + } + } + if buildX || buildN { + fmt.Fprintln(xout, "WORK="+tmpdir) + } + defer func() { + if buildWork { + fmt.Printf("WORK=%s\n", tmpdir) + return + } + removeAll(tmpdir) + }() + + if err := fetchNDK(); err != nil { + return err + } + if err := fetchOpenAL(); err != nil { + return err + } + + if err := envInit(); err != nil { + return err + } + + // Install standard libraries for cross compilers. + start := time.Now() + if err := installStd(androidArmEnv); err != nil { + return err + } + if err := installDarwin(); err != nil { + return err + } + + if buildX || buildN { + printcmd("go version > %s", verpath) + } + if !buildN { + if err := ioutil.WriteFile(verpath, version, 0644); err != nil { + return err + } + } + if buildV { + took := time.Since(start) / time.Second * time.Second + fmt.Fprintf(os.Stderr, "\nDone, build took %s.\n", took) + } + return nil +} + +func installDarwin() error { + if goos != "darwin" { + return nil // Only build iOS compilers on OS X. + } + if err := installStd(darwinArmEnv); err != nil { + return err + } + if err := installStd(darwinArm64Env); err != nil { + return err + } + // TODO(crawshaw): darwin/386 for the iOS simulator? + if err := installStd(darwinAmd64Env, "-tags=ios"); err != nil { + return err + } + return nil +} + +func installStd(env []string, args ...string) error { + tOS := getenv(env, "GOOS") + tArch := getenv(env, "GOARCH") + if buildV { + fmt.Fprintf(os.Stderr, "\n# Building standard library for %s/%s.\n", tOS, tArch) + } + + // The -p flag is to speed up darwin/arm builds. + // Remove when golang.org/issue/10477 is resolved. + cmd := exec.Command("go", "install", fmt.Sprintf("-p=%d", runtime.NumCPU()), "-pkgdir="+pkgdir(env)) + cmd.Args = append(cmd.Args, args...) + if buildV { + cmd.Args = append(cmd.Args, "-v") + } + if buildX { + cmd.Args = append(cmd.Args, "-x") + } + if buildWork { + cmd.Args = append(cmd.Args, "-work") + } + cmd.Args = append(cmd.Args, "std") + cmd.Env = append([]string{}, env...) + return runCmd(cmd) +} + +func removeGomobilepkg() { + dir, err := os.Open(gomobilepath) + if err != nil { + return + } + names, err := dir.Readdirnames(-1) + if err != nil { + return + } + for _, name := range names { + if name == "dl" { + continue + } + removeAll(filepath.Join(gomobilepath, name)) + } +} + +func move(dst, src string, names ...string) error { + for _, name := range names { + srcf := filepath.Join(src, name) + dstf := filepath.Join(dst, name) + if buildX || buildN { + printcmd("mv %s %s", srcf, dstf) + } + if buildN { + continue + } + if goos == "windows" { + // os.Rename fails if dstf already exists. + os.Remove(dstf) + } + if err := os.Rename(srcf, dstf); err != nil { + return err + } + } + return nil +} + +func mkdir(dir string) error { + if buildX || buildN { + printcmd("mkdir -p %s", dir) + } + if buildN { + return nil + } + return os.MkdirAll(dir, 0755) +} + +func symlink(src, dst string) error { + if buildX || buildN { + printcmd("ln -s %s %s", src, dst) + } + if buildN { + return nil + } + if goos == "windows" { + return doCopyAll(dst, src) + } + return os.Symlink(src, dst) +} + +func rm(name string) error { + if buildX || buildN { + printcmd("rm %s", name) + } + if buildN { + return nil + } + return os.Remove(name) +} + +func goVersion() ([]byte, error) { + gobin, err := exec.LookPath("go") + if err != nil { + return nil, fmt.Errorf(`no Go tool on $PATH`) + } + buildHelp, err := exec.Command(gobin, "help", "build").CombinedOutput() + if err != nil { + return nil, fmt.Errorf("bad Go tool: %v (%s)", err, buildHelp) + } + // TODO(crawshaw): this is a crude test for Go 1.5. After release, + // remove this and check it is not an old release version. + if !bytes.Contains(buildHelp, []byte("-pkgdir")) { + return nil, fmt.Errorf("installed Go tool does not support -pkgdir") + } + return exec.Command(gobin, "version").CombinedOutput() +} + +func fetchOpenAL() error { + url := "https://dl.google.com/go/mobile/gomobile-" + openALVersion + ".tar.gz" + archive, err := fetch(url) + if err != nil { + return err + } + if err := extract("openal", archive); err != nil { + return err + } + dst := filepath.Join(ndkccpath, "arm", "sysroot", "usr", "include") + src := filepath.Join(tmpdir, "openal", "include") + if err := move(dst, src, "AL"); err != nil { + return err + } + libDst := filepath.Join(ndkccpath, "openal") + libSrc := filepath.Join(tmpdir, "openal") + if err := mkdir(libDst); err != nil { + return nil + } + if err := move(libDst, libSrc, "lib"); err != nil { + return err + } + return nil +} + +func extract(dst, src string) error { + if buildX || buildN { + printcmd("tar xfz %s", src) + } + if buildN { + return nil + } + tf, err := os.Open(src) + if err != nil { + return err + } + defer tf.Close() + zr, err := gzip.NewReader(tf) + if err != nil { + return err + } + tr := tar.NewReader(zr) + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + dst := filepath.Join(tmpdir, dst+"/"+hdr.Name) + if hdr.Typeflag == tar.TypeSymlink { + if err := symlink(hdr.Linkname, dst); err != nil { + return err + } + continue + } + if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil { + return err + } + f, err := os.OpenFile(dst, os.O_CREATE|os.O_EXCL|os.O_WRONLY, os.FileMode(hdr.Mode)&0777) + if err != nil { + return err + } + if _, err := io.Copy(f, tr); err != nil { + return err + } + if err := f.Close(); err != nil { + return err + } + } + return nil +} + +func fetchNDK() error { + if useStrippedNDK { + if err := fetchStrippedNDK(); err != nil { + return err + } + } else { + if err := fetchFullNDK(); err != nil { + return err + } + } + + dst := filepath.Join(ndkccpath, "arm") + dstSysroot := filepath.Join(dst, "sysroot/usr") + if err := mkdir(dstSysroot); err != nil { + return err + } + + srcSysroot := filepath.Join(tmpdir, "android-"+ndkVersion+"/platforms/android-15/arch-arm/usr") + if err := move(dstSysroot, srcSysroot, "include", "lib"); err != nil { + return err + } + + ndkpath := filepath.Join(tmpdir, "android-"+ndkVersion+"/toolchains/arm-linux-androideabi-4.8/prebuilt") + if goos == "windows" && ndkarch == "x86" { + ndkpath = filepath.Join(ndkpath, "windows") + } else { + ndkpath = filepath.Join(ndkpath, goos+"-"+ndkarch) + } + if err := move(dst, ndkpath, "bin", "lib", "libexec"); err != nil { + return err + } + + linkpath := filepath.Join(dst, "arm-linux-androideabi/bin") + if err := mkdir(linkpath); err != nil { + return err + } + for _, name := range []string{"ld", "as", "gcc", "g++"} { + if goos == "windows" { + name += ".exe" + } + if err := symlink(filepath.Join(dst, "bin", "arm-linux-androideabi-"+name), filepath.Join(linkpath, name)); err != nil { + return err + } + } + return nil +} + +func fetchStrippedNDK() error { + url := "https://dl.google.com/go/mobile/gomobile-" + ndkVersion + "-" + goos + "-" + ndkarch + ".tar.gz" + archive, err := fetch(url) + if err != nil { + return err + } + return extract("", archive) +} + +func fetchFullNDK() error { + url := "https://dl.google.com/android/ndk/android-" + ndkVersion + "-" + goos + "-" + ndkarch + "." + if goos == "windows" { + url += "exe" + } else { + url += "bin" + } + archive, err := fetch(url) + if err != nil { + return err + } + + // The self-extracting ndk dist file for Windows terminates + // with an error (error code 2 - corrupted or incomplete file) + // but there are no details on what caused this. + // + // Strangely, if the file is launched from file browser or + // unzipped with 7z.exe no error is reported. + // + // In general we use the stripped NDK, so this code path + // is not used, and 7z.exe is not a normal dependency. + var inflate *exec.Cmd + if goos != "windows" { + inflate = exec.Command(archive) + } else { + inflate = exec.Command("7z.exe", "x", archive) + } + inflate.Dir = tmpdir + return runCmd(inflate) +} + +// fetch reads a URL into $GOPATH/pkg/gomobile/dl and returns the path +// to the downloaded file. Downloading is skipped if the file is +// already present. +func fetch(url string) (dst string, err error) { + if err := mkdir(filepath.Join(gomobilepath, "dl")); err != nil { + return "", err + } + name := path.Base(url) + dst = filepath.Join(gomobilepath, "dl", name) + + // Use what's in the cache if force update is not required. + if !initU { + if buildX { + printcmd("stat %s", dst) + } + if _, err = os.Stat(dst); err == nil { + return dst, nil + } + } + if buildX { + printcmd("curl -o%s %s", dst, url) + } + if buildN { + return dst, nil + } + + if buildV { + fmt.Fprintf(os.Stderr, "Downloading %s.\n", url) + } + + f, err := ioutil.TempFile(tmpdir, "partial-"+name) + if err != nil { + return "", err + } + defer func() { + if err != nil { + f.Close() + os.Remove(f.Name()) + } + }() + + resp, err := http.Get(url) + if err != nil { + return "", err + } + if resp.StatusCode != http.StatusOK { + err = fmt.Errorf("error fetching %v, status: %v", url, resp.Status) + } else { + _, err = io.Copy(f, resp.Body) + } + if err2 := resp.Body.Close(); err == nil { + err = err2 + } + if err != nil { + return "", err + } + if err = f.Close(); err != nil { + return "", err + } + if err = os.Rename(f.Name(), dst); err != nil { + return "", err + } + return dst, nil +} + +func doCopyAll(dst, src string) error { + return filepath.Walk(src, func(path string, info os.FileInfo, errin error) (err error) { + if errin != nil { + return errin + } + prefixLen := len(src) + if len(path) > prefixLen { + prefixLen++ // file separator + } + outpath := filepath.Join(dst, path[prefixLen:]) + if info.IsDir() { + return os.Mkdir(outpath, 0755) + } + in, err := os.Open(path) + if err != nil { + return err + } + defer in.Close() + out, err := os.OpenFile(outpath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, info.Mode()) + if err != nil { + return err + } + defer func() { + if errc := out.Close(); err == nil { + err = errc + } + }() + _, err = io.Copy(out, in) + return err + }) +} + +func removeAll(path string) error { + if buildX || buildN { + printcmd(`rm -r -f "%s"`, path) + } + if buildN { + return nil + } + + // os.RemoveAll behaves differently in windows. + // http://golang.org/issues/9606 + if goos == "windows" { + resetReadOnlyFlagAll(path) + } + + return os.RemoveAll(path) +} + +func resetReadOnlyFlagAll(path string) error { + fi, err := os.Stat(path) + if err != nil { + return err + } + if !fi.IsDir() { + return os.Chmod(path, 0666) + } + fd, err := os.Open(path) + if err != nil { + return err + } + defer fd.Close() + + names, _ := fd.Readdirnames(-1) + for _, name := range names { + resetReadOnlyFlagAll(path + string(filepath.Separator) + name) + } + return nil +} + +func goEnv(name string) string { + if val := os.Getenv(name); val != "" { + return val + } + val, err := exec.Command("go", "env", name).Output() + if err != nil { + panic(err) // the Go tool was tested to work earlier + } + return strings.TrimSpace(string(val)) +} + +func runCmd(cmd *exec.Cmd) error { + if buildX || buildN { + dir := "" + if cmd.Dir != "" { + dir = "PWD=" + cmd.Dir + " " + } + env := strings.Join(cmd.Env, " ") + if env != "" { + env += " " + } + printcmd("%s%s%s", dir, env, strings.Join(cmd.Args, " ")) + } + + buf := new(bytes.Buffer) + buf.WriteByte('\n') + if buildV { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } else { + cmd.Stdout = buf + cmd.Stderr = buf + } + + if buildWork { + if goos == "windows" { + cmd.Env = append(cmd.Env, `TEMP=`+tmpdir) + cmd.Env = append(cmd.Env, `TMP=`+tmpdir) + } else { + cmd.Env = append(cmd.Env, `TMPDIR=`+tmpdir) + } + } + + if !buildN { + cmd.Env = environ(cmd.Env) + if err := cmd.Run(); err != nil { + return fmt.Errorf("%s failed: %v%s", strings.Join(cmd.Args, " "), err, buf) + } + } + return nil +} diff --git a/src/golang.org/x/mobile/cmd/gomobile/init_test.go b/src/golang.org/x/mobile/cmd/gomobile/init_test.go new file mode 100644 index 0000000000..8d90eae805 --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/init_test.go @@ -0,0 +1,129 @@ +// Copyright 2015 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. + +package main + +import ( + "bytes" + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + "testing" + "text/template" +) + +var gopath string + +func TestInit(t *testing.T) { + buf := new(bytes.Buffer) + gopathorig := os.Getenv("GOPATH") + defer func() { + xout = os.Stderr + buildN = false + buildX = false + initU = false + os.Setenv("GOPATH", gopathorig) + }() + xout = buf + buildN = true + buildX = true + initU = true + + // Test that first GOPATH element is chosen correctly. + gopath = "/GOPATH1" + paths := []string{"/GOPATH1", "/path2", "/path3"} + os.Setenv("GOPATH", strings.Join(paths, string(os.PathListSeparator))) + os.Setenv("GOROOT_BOOTSTRAP", "go1.4") + if goos == "windows" { + os.Setenv("HOMEDRIVE", "C:") + } + + err := runInit(cmdInit) + if err != nil { + t.Log(buf.String()) + t.Fatal(err) + } + + diff, err := diffOutput(buf.String(), initTmpl) + if err != nil { + t.Fatalf("computing diff failed: %v", err) + } + if diff != "" { + t.Errorf("unexpected output:\n%s", diff) + } +} + +func diffOutput(got string, wantTmpl *template.Template) (string, error) { + got = filepath.ToSlash(got) + + wantBuf := new(bytes.Buffer) + data := outputData{ + NDK: ndkVersion, + GOOS: goos, + GOARCH: goarch, + GOPATH: gopath, + NDKARCH: ndkarch, + Xproj: projPbxproj, + Xcontents: contentsJSON, + Xinfo: infoplistTmplData{Name: "Basic"}, + NumCPU: strconv.Itoa(runtime.NumCPU()), + } + if goos == "windows" { + data.EXE = ".exe" + } + if err := wantTmpl.Execute(wantBuf, data); err != nil { + return "", err + } + want := wantBuf.String() + if got != want { + return diff(got, want) + } + return "", nil +} + +type outputData struct { + NDK string + GOOS string + GOARCH string + GOPATH string + NDKARCH string + EXE string // .extension for executables. (ex. ".exe" for windows) + Xproj string + Xcontents string + Xinfo infoplistTmplData + NumCPU string +} + +var initTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile +mkdir -p $GOMOBILE/android-{{.NDK}} +WORK={{.GOPATH}}/pkg/gomobile/work +mkdir -p $GOMOBILE/dl +curl -o$GOMOBILE/dl/gomobile-{{.NDK}}-{{.GOOS}}-{{.NDKARCH}}.tar.gz https://dl.google.com/go/mobile/gomobile-{{.NDK}}-{{.GOOS}}-{{.NDKARCH}}.tar.gz +tar xfz $GOMOBILE/dl/gomobile-{{.NDK}}-{{.GOOS}}-{{.NDKARCH}}.tar.gz +mkdir -p $GOMOBILE/android-{{.NDK}}/arm/sysroot/usr +mv $WORK/android-{{.NDK}}/platforms/android-15/arch-arm/usr/include $GOMOBILE/android-{{.NDK}}/arm/sysroot/usr/include +mv $WORK/android-{{.NDK}}/platforms/android-15/arch-arm/usr/lib $GOMOBILE/android-{{.NDK}}/arm/sysroot/usr/lib +mv $WORK/android-{{.NDK}}/toolchains/arm-linux-androideabi-4.8/prebuilt/{{.GOOS}}-{{.NDKARCH}}/bin $GOMOBILE/android-{{.NDK}}/arm/bin +mv $WORK/android-{{.NDK}}/toolchains/arm-linux-androideabi-4.8/prebuilt/{{.GOOS}}-{{.NDKARCH}}/lib $GOMOBILE/android-{{.NDK}}/arm/lib +mv $WORK/android-{{.NDK}}/toolchains/arm-linux-androideabi-4.8/prebuilt/{{.GOOS}}-{{.NDKARCH}}/libexec $GOMOBILE/android-{{.NDK}}/arm/libexec +mkdir -p $GOMOBILE/android-{{.NDK}}/arm/arm-linux-androideabi/bin +ln -s $GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-ld{{.EXE}} $GOMOBILE/android-{{.NDK}}/arm/arm-linux-androideabi/bin/ld{{.EXE}} +ln -s $GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-as{{.EXE}} $GOMOBILE/android-{{.NDK}}/arm/arm-linux-androideabi/bin/as{{.EXE}} +ln -s $GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-gcc{{.EXE}} $GOMOBILE/android-{{.NDK}}/arm/arm-linux-androideabi/bin/gcc{{.EXE}} +ln -s $GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-g++{{.EXE}} $GOMOBILE/android-{{.NDK}}/arm/arm-linux-androideabi/bin/g++{{.EXE}} +mkdir -p $GOMOBILE/dl +curl -o$GOMOBILE/dl/gomobile-openal-soft-1.16.0.1.tar.gz https://dl.google.com/go/mobile/gomobile-openal-soft-1.16.0.1.tar.gz +tar xfz $GOMOBILE/dl/gomobile-openal-soft-1.16.0.1.tar.gz +mv $WORK/openal/include/AL $GOMOBILE/android-{{.NDK}}/arm/sysroot/usr/include/AL +mkdir -p $GOMOBILE/android-{{.NDK}}/openal +mv $WORK/openal/lib $GOMOBILE/android-{{.NDK}}/openal/lib +GOOS=android GOARCH=arm GOARM=7 CC=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-gcc{{.EXE}} CXX=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-g++ CGO_ENABLED=1 go install -p={{.NumCPU}} -pkgdir=$GOMOBILE/pkg_android_arm -x std +{{if eq .GOOS "darwin"}}GOOS=darwin GOARCH=arm GOARM=7 CC=clang-iphoneos CXX=clang-iphoneos CGO_CFLAGS=-isysroot=iphoneos -arch armv7 CGO_LDFLAGS=-isysroot=iphoneos -arch armv7 CGO_ENABLED=1 go install -p={{.NumCPU}} -pkgdir=$GOMOBILE/pkg_darwin_arm -x std +GOOS=darwin GOARCH=arm64 CC=clang-iphoneos CXX=clang-iphoneos CGO_CFLAGS=-isysroot=iphoneos -arch arm64 CGO_LDFLAGS=-isysroot=iphoneos -arch arm64 CGO_ENABLED=1 go install -p={{.NumCPU}} -pkgdir=$GOMOBILE/pkg_darwin_arm64 -x std +GOOS=darwin GOARCH=amd64 CC=clang-iphonesimulator CXX=clang-iphonesimulator CGO_CFLAGS=-isysroot=iphonesimulator -mios-simulator-version-min=6.1 -arch x86_64 CGO_LDFLAGS=-isysroot=iphonesimulator -mios-simulator-version-min=6.1 -arch x86_64 CGO_ENABLED=1 go install -p={{.NumCPU}} -pkgdir=$GOMOBILE/pkg_darwin_amd64 -tags=ios -x std +{{end}}go version > $GOMOBILE/version +rm -r -f "$WORK" +`)) diff --git a/src/golang.org/x/mobile/cmd/gomobile/install.go b/src/golang.org/x/mobile/cmd/gomobile/install.go new file mode 100644 index 0000000000..f791f47b2a --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/install.go @@ -0,0 +1,43 @@ +// Copyright 2015 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. + +package main + +import ( + "fmt" + "os/exec" + "path/filepath" +) + +var cmdInstall = &command{ + run: runInstall, + Name: "install", + Usage: "[-target android] [build flags] [package]", + Short: "compile android APK and install on device", + Long: ` +Install compiles and installs the app named by the import path on the +attached mobile device. + +Only -target android is supported. The 'adb' tool must be on the PATH. + +The build flags -a, -i, -n, -x, -gcflags, -ldflags, -tags, and -work are +shared with the build command. +For documentation, see 'go help build'. +`, +} + +func runInstall(cmd *command) error { + if buildTarget != "android" { + return fmt.Errorf("install is not supported for -target=%s", buildTarget) + } + if err := runBuild(cmd); err != nil { + return err + } + return runCmd(exec.Command( + `adb`, + `install`, + `-r`, + filepath.Base(pkg.Dir)+`.apk`, + )) +} diff --git a/src/golang.org/x/mobile/cmd/gomobile/main.go b/src/golang.org/x/mobile/cmd/gomobile/main.go new file mode 100644 index 0000000000..a0d998cf48 --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/main.go @@ -0,0 +1,180 @@ +// Copyright 2015 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. + +package main + +//go:generate gomobile help documentation doc.go + +import ( + "bufio" + "bytes" + "flag" + "fmt" + "html/template" + "io" + "io/ioutil" + "log" + "os" + "unicode" + "unicode/utf8" +) + +func printUsage(w io.Writer) { + bufw := bufio.NewWriter(w) + if err := usageTmpl.Execute(bufw, commands); err != nil { + panic(err) + } + bufw.Flush() +} + +var gomobileName = "gomobile" + +func main() { + gomobileName = os.Args[0] + flag.Usage = func() { + printUsage(os.Stderr) + os.Exit(2) + } + flag.Parse() + log.SetFlags(0) + + args := flag.Args() + if len(args) < 1 { + flag.Usage() + } + + if args[0] == "help" { + if len(args) == 3 && args[1] == "documentation" { + helpDocumentation(args[2]) + return + } + help(args[1:]) + return + } + + for _, cmd := range commands { + if cmd.Name == args[0] { + cmd.flag.Usage = func() { + cmd.usage() + os.Exit(1) + } + cmd.flag.Parse(args[1:]) + if err := cmd.run(cmd); err != nil { + msg := err.Error() + if msg != "" { + fmt.Fprintf(os.Stderr, "%s: %v\n", os.Args[0], err) + } + os.Exit(1) + } + return + } + } + fmt.Fprintf(os.Stderr, "%s: unknown subcommand %q\nRun 'gomobile help' for usage.\n", os.Args[0], args[0]) + os.Exit(2) +} + +func help(args []string) { + if len(args) == 0 { + printUsage(os.Stdout) + return // succeeded at helping + } + if len(args) != 1 { + fmt.Fprintf(os.Stderr, "usage: %s help command\n\nToo many arguments given.\n", gomobileName) + os.Exit(2) // failed to help + } + + arg := args[0] + for _, cmd := range commands { + if cmd.Name == arg { + cmd.usage() + return // succeeded at helping + } + } + + fmt.Fprintf(os.Stderr, "Unknown help topic %#q. Run '%s help'.\n", arg, gomobileName) + os.Exit(2) +} + +const documentationHeader = `// Copyright 2015 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. + +// DO NOT EDIT. GENERATED BY 'gomobile help documentation doc.go'. +` + +func helpDocumentation(path string) { + w := new(bytes.Buffer) + w.WriteString(documentationHeader) + w.WriteString("\n/*\n") + if err := usageTmpl.Execute(w, commands); err != nil { + log.Fatal(err) + } + + for _, cmd := range commands { + r, rlen := utf8.DecodeRuneInString(cmd.Short) + w.WriteString("\n\n") + w.WriteRune(unicode.ToUpper(r)) + w.WriteString(cmd.Short[rlen:]) + w.WriteString("\n\nUsage:\n\n\tgomobile " + cmd.Name) + if cmd.Usage != "" { + w.WriteRune(' ') + w.WriteString(cmd.Usage) + } + w.WriteRune('\n') + w.WriteString(cmd.Long) + } + + w.WriteString("*/\npackage main\n") + + if err := ioutil.WriteFile(path, w.Bytes(), 0666); err != nil { + log.Fatal(err) + } +} + +var commands = []*command{ + // TODO(crawshaw): cmdRun + cmdBind, + cmdBuild, + cmdInit, + cmdInstall, + cmdVersion, +} + +type command struct { + run func(*command) error + flag flag.FlagSet + Name string + Usage string + Short string + Long string +} + +func (cmd *command) usage() { + fmt.Fprintf(os.Stdout, "usage: %s %s %s\n%s", gomobileName, cmd.Name, cmd.Usage, cmd.Long) +} + +var usageTmpl = template.Must(template.New("usage").Parse( + `Gomobile is a tool for building and running mobile apps written in Go. + +To install: + + $ go get golang.org/x/mobile/cmd/gomobile + $ gomobile init + +At least Go 1.5 is required. Until it is released, build tip from +source: http://golang.org/doc/install/source + +Initialization rebuilds the standard library and may download +the Android NDK compiler. + +Usage: + + gomobile command [arguments] + +Commands: +{{range .}} + {{.Name | printf "%-11s"}} {{.Short}}{{end}} + +Use 'gomobile help [command]' for more information about that command. +`)) diff --git a/src/golang.org/x/mobile/cmd/gomobile/manifest.go b/src/golang.org/x/mobile/cmd/gomobile/manifest.go new file mode 100644 index 0000000000..75f39682b8 --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/manifest.go @@ -0,0 +1,76 @@ +// Copyright 2015 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. + +package main + +import ( + "encoding/xml" + "errors" + "fmt" + "html/template" +) + +type manifestXML struct { + Activity activityXML `xml:"application>activity"` +} + +type activityXML struct { + Name string `xml:"name,attr"` + MetaData []metaDataXML `xml:"meta-data"` +} + +type metaDataXML struct { + Name string `xml:"name,attr"` + Value string `xml:"value,attr"` +} + +// manifestLibName parses the AndroidManifest.xml and finds the library +// name of the NativeActivity. +func manifestLibName(data []byte) (string, error) { + manifest := new(manifestXML) + if err := xml.Unmarshal(data, manifest); err != nil { + return "", err + } + if manifest.Activity.Name != "org.golang.app.GoNativeActivity" { + return "", fmt.Errorf("can only build an .apk for GoNativeActivity, not %q", manifest.Activity.Name) + } + libName := "" + for _, md := range manifest.Activity.MetaData { + if md.Name == "android.app.lib_name" { + libName = md.Value + break + } + } + if libName == "" { + return "", errors.New("AndroidManifest.xml missing meta-data android.app.lib_name") + } + return libName, nil +} + +type manifestTmplData struct { + JavaPkgPath string + Name string + LibName string +} + +var manifestTmpl = template.Must(template.New("manifest").Parse(` + + + + + + + + + + + + +`)) diff --git a/src/golang.org/x/mobile/cmd/gomobile/release.go b/src/golang.org/x/mobile/cmd/gomobile/release.go new file mode 100644 index 0000000000..9b63eedeb6 --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/release.go @@ -0,0 +1,349 @@ +// Copyright 2015 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 ignore + +// Release is a tool for building the NDK tarballs hosted on dl.google.com. +// +// The Go toolchain only needs the gcc compiler and headers, which are ~10MB. +// The entire NDK is ~400MB. Building smaller toolchain binaries reduces the +// run time of gomobile init significantly. +package main + +import ( + "archive/tar" + "bufio" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "os/exec" + "path/filepath" + "runtime" +) + +const ndkVersion = "ndk-r10e" + +type version struct { + os string + arch string +} + +var hosts = []version{ + {"darwin", "x86_64"}, + {"linux", "x86"}, + {"linux", "x86_64"}, + {"windows", "x86"}, + {"windows", "x86_64"}, +} + +var tmpdir string + +func main() { + var err error + tmpdir, err = ioutil.TempDir("", "gomobile-release-") + if err != nil { + log.Fatal(err) + } + defer os.RemoveAll(tmpdir) + + for _, host := range hosts { + if err := mkpkg(host); err != nil { + log.Fatal(err) + } + } + + if err := mkALPkg(); err != nil { + log.Fatal(err) + } +} + +func run(dir, path string, args ...string) error { + cmd := exec.Command(path, args...) + cmd.Dir = dir + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +func mkALPkg() (err error) { + alTmpDir, err := ioutil.TempDir("", "openal-release-") + if err != nil { + return err + } + defer os.RemoveAll(alTmpDir) + + if err := run(alTmpDir, "git", "clone", "-v", "git://repo.or.cz/openal-soft.git", alTmpDir); err != nil { + return err + } + if err := run(alTmpDir, "git", "checkout", "19f79be57b8e768f44710b6d26017bc1f8c8fbda"); err != nil { + return err + } + if err := run(filepath.Join(alTmpDir, "cmake"), "cmake", "..", "-DCMAKE_TOOLCHAIN_FILE=../XCompile-Android.txt", "-DHOST=arm-linux-androideabi"); err != nil { + return err + } + if err := run(filepath.Join(alTmpDir, "cmake"), "make"); err != nil { + return err + } + + // Build the tarball. + f, err := os.Create("gomobile-openal-soft-1.16.0.1.tar.gz") + if err != nil { + return err + } + bw := bufio.NewWriter(f) + zw, err := gzip.NewWriterLevel(bw, gzip.BestCompression) + if err != nil { + return err + } + tw := tar.NewWriter(zw) + defer func() { + err2 := f.Close() + if err == nil { + err = err2 + } + }() + defer func() { + err2 := bw.Flush() + if err == nil { + err = err2 + } + }() + defer func() { + err2 := zw.Close() + if err == nil { + err = err2 + } + }() + defer func() { + err2 := tw.Close() + if err == nil { + err = err2 + } + }() + + files := map[string]string{ + "cmake/libopenal.so": "lib/armeabi/libopenal.so", + "include/AL/al.h": "include/AL/al.h", + "include/AL/alc.h": "include/AL/alc.h", + "COPYING": "include/AL/COPYING", + } + for src, dst := range files { + f, err := os.Open(filepath.Join(alTmpDir, src)) + if err != nil { + return err + } + fi, err := f.Stat() + if err != nil { + return err + } + if err := tw.WriteHeader(&tar.Header{ + Name: dst, + Mode: int64(fi.Mode()), + Size: fi.Size(), + }); err != nil { + return err + } + _, err = io.Copy(tw, f) + if err != nil { + return err + } + f.Close() + } + return nil +} + +func mkpkg(host version) (err error) { + ndkName := "android-" + ndkVersion + "-" + host.os + "-" + host.arch + "." + if host.os == "windows" { + ndkName += "exe" + } else { + ndkName += "bin" + } + url := "http://dl.google.com/android/ndk/" + ndkName + log.Printf("%s\n", url) + binPath := tmpdir + "/" + ndkName + if err := fetch(binPath, url); err != nil { + log.Fatal(err) + } + + src := tmpdir + "/" + host.os + "-" + host.arch + "-src" + dst := tmpdir + "/" + host.os + "-" + host.arch + "-dst" + if err := os.Mkdir(src, 0755); err != nil { + return err + } + if err := inflate(src, binPath); err != nil { + return err + } + + // The NDK is unpacked into tmpdir/linux-x86_64-src/android-{{ndkVersion}}. + // Move the files we want into tmpdir/linux-x86_64-dst/android-{{ndkVersion}}. + // We preserve the same file layout to make the full NDK interchangable + // with the cut down file. + usr := "android-" + ndkVersion + "/platforms/android-15/arch-arm/usr" + gcc := "android-" + ndkVersion + "/toolchains/arm-linux-androideabi-4.8/prebuilt/" + if host.os == "windows" && host.arch == "x86" { + gcc += "windows" + } else { + gcc += host.os + "-" + host.arch + } + + if err := os.MkdirAll(dst+"/"+usr, 0755); err != nil { + return err + } + if err := os.MkdirAll(dst+"/"+gcc, 0755); err != nil { + return err + } + if err := move(dst+"/"+usr, src+"/"+usr, "include", "lib"); err != nil { + return err + } + if err := move(dst+"/"+gcc, src+"/"+gcc, "bin", "lib", "libexec", "COPYING", "COPYING.LIB"); err != nil { + return err + } + + // Build the tarball. + f, err := os.Create("gomobile-" + ndkVersion + "-" + host.os + "-" + host.arch + ".tar.gz") + if err != nil { + return err + } + bw := bufio.NewWriter(f) + zw, err := gzip.NewWriterLevel(bw, gzip.BestCompression) + if err != nil { + return err + } + tw := tar.NewWriter(zw) + defer func() { + err2 := f.Close() + if err == nil { + err = err2 + } + }() + defer func() { + err2 := bw.Flush() + if err == nil { + err = err2 + } + }() + defer func() { + err2 := zw.Close() + if err == nil { + err = err2 + } + }() + defer func() { + err2 := tw.Close() + if err == nil { + err = err2 + } + }() + + readme := "Stripped down copy of:\n\n\t" + url + "\n\nGenerated by golang.org/x/mobile/cmd/gomobile/release.go." + err = tw.WriteHeader(&tar.Header{ + Name: "README", + Mode: 0644, + Size: int64(len(readme)), + }) + if err != nil { + return err + } + _, err = tw.Write([]byte(readme)) + if err != nil { + return err + } + + return filepath.Walk(dst, func(path string, fi os.FileInfo, err error) error { + defer func() { + if err != nil { + err = fmt.Errorf("%s: %v", path, err) + } + }() + if err != nil { + return err + } + if path == dst { + return nil + } + name := path[len(dst)+1:] + if fi.IsDir() { + return nil + } + if fi.Mode()&os.ModeSymlink != 0 { + dst, err := os.Readlink(path) + if err != nil { + log.Printf("bad symlink: %s", name) + return nil + } + //log.Printf("linking %s to %s", name, dst) + return tw.WriteHeader(&tar.Header{ + Name: name, + Linkname: dst, + Typeflag: tar.TypeSymlink, + }) + } + //log.Printf("writing %s (%d)", name, fi.Size()) + err = tw.WriteHeader(&tar.Header{ + Name: name, + Mode: int64(fi.Mode()), + Size: fi.Size(), + }) + if err != nil { + return err + } + f, err := os.Open(path) + if err != nil { + return err + } + _, err = io.Copy(tw, f) + f.Close() + return err + }) +} + +func fetch(dst, url string) error { + f, err := os.OpenFile(dst, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0755) + if err != nil { + return err + } + resp, err := http.Get(url) + if err != nil { + return err + } + _, err = io.Copy(f, resp.Body) + err2 := resp.Body.Close() + err3 := f.Close() + if err != nil { + return err + } + if err2 != nil { + return err2 + } + return err3 +} + +func inflate(dst, path string) error { + p7zip := "7z" + if runtime.GOOS == "darwin" { + p7zip = "/Applications/Keka.app/Contents/Resources/keka7z" + } + cmd := exec.Command(p7zip, "x", path) + cmd.Dir = dst + out, err := cmd.CombinedOutput() + if err != nil { + os.Stderr.Write(out) + return err + } + return nil +} + +func move(dst, src string, names ...string) error { + for _, name := range names { + if err := os.Rename(src+"/"+name, dst+"/"+name); err != nil { + return err + } + } + return nil +} diff --git a/src/golang.org/x/mobile/cmd/gomobile/strings_flag.go b/src/golang.org/x/mobile/cmd/gomobile/strings_flag.go new file mode 100644 index 0000000000..330833e70a --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/strings_flag.go @@ -0,0 +1,62 @@ +// Copyright 2015 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. + +package main + +import "fmt" + +type stringsFlag []string + +func (v *stringsFlag) Set(s string) error { + var err error + *v, err = splitQuotedFields(s) + if *v == nil { + *v = []string{} + } + return err +} + +func isSpaceByte(c byte) bool { + return c == ' ' || c == '\t' || c == '\n' || c == '\r' +} + +func splitQuotedFields(s string) ([]string, error) { + // Split fields allowing '' or "" around elements. + // Quotes further inside the string do not count. + var f []string + for len(s) > 0 { + for len(s) > 0 && isSpaceByte(s[0]) { + s = s[1:] + } + if len(s) == 0 { + break + } + // Accepted quoted string. No unescaping inside. + if s[0] == '"' || s[0] == '\'' { + quote := s[0] + s = s[1:] + i := 0 + for i < len(s) && s[i] != quote { + i++ + } + if i >= len(s) { + return nil, fmt.Errorf("unterminated %c string", quote) + } + f = append(f, s[:i]) + s = s[i+1:] + continue + } + i := 0 + for i < len(s) && !isSpaceByte(s[i]) { + i++ + } + f = append(f, s[:i]) + s = s[i:] + } + return f, nil +} + +func (v *stringsFlag) String() string { + return "" +} diff --git a/src/golang.org/x/mobile/cmd/gomobile/version.go b/src/golang.org/x/mobile/cmd/gomobile/version.go new file mode 100644 index 0000000000..d2290a31a7 --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/version.go @@ -0,0 +1,80 @@ +// Copyright 2015 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. + +package main + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "path/filepath" +) + +var cmdVersion = &command{ + run: runVersion, + Name: "version", + Usage: "", + Short: "print version", + Long: ` +Version prints versions of the gomobile binary and tools +`, +} + +func runVersion(cmd *command) (err error) { + // Check this binary matches the version in golang.org/x/mobile/cmd/gomobile + // source code in GOPATH. If they don't match, currently there is no + // way to reliably identify the revision number this binary was built + // against. + version := func() string { + bin, err := exec.LookPath(os.Args[0]) + if err != nil { + return "" + } + bindir := filepath.Dir(bin) + cmd := exec.Command("go", "install", "-x", "-n", "golang.org/x/mobile/cmd/gomobile") + cmd.Env = append(os.Environ(), "GOBIN="+bindir) + out, err := cmd.CombinedOutput() + if err != nil || len(out) != 0 { + return "" + } + if rev, err := mobileRepoRevision(); err == nil { + return rev + } + return "" + }() + if version == "" { + fmt.Println("gomobile version unknown") + return nil + } + + // Supported platforms + platforms := "android" + if goos == "darwin" { + platforms = "android,ios" + } + + // ANDROID_HOME, sdk build tool version + androidapi, _ := androidAPIPath() + + fmt.Printf("gomobile version %s (%s); androidSDK=%s\n", version, platforms, androidapi) + return nil +} + +func mobileRepoRevision() (rev string, err error) { + b, err := exec.Command("go", "list", "-f", "{{.Dir}}", "golang.org/x/mobile/app").CombinedOutput() + if err != nil { + return "", fmt.Errorf("mobile repo not found: %v", err) + } + + repo := filepath.Dir(string(b)) + if err := os.Chdir(repo); err != nil { + return "", fmt.Errorf("mobile repo %q not accessible: %v", repo, err) + } + revision, err := exec.Command("git", "log", "-n", "1", "--format=format: +%h %cd", "HEAD").CombinedOutput() + if err != nil { + return "", err + } + return string(bytes.Trim(revision, " \t\r\n")), nil +} diff --git a/src/golang.org/x/mobile/cmd/gomobile/writer.go b/src/golang.org/x/mobile/cmd/gomobile/writer.go new file mode 100644 index 0000000000..6224f277d7 --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/writer.go @@ -0,0 +1,291 @@ +// Copyright 2015 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. + +package main + +// APK is the archival format used for Android apps. It is a ZIP archive with +// three extra files: +// +// META-INF/MANIFEST.MF +// META-INF/CERT.SF +// META-INF/CERT.RSA +// +// The MANIFEST.MF comes from the Java JAR archive format. It is a list of +// files included in the archive along with a SHA1 hash, for example: +// +// Name: lib/armeabi/libbasic.so +// SHA1-Digest: ntLSc1eLCS2Tq1oB4Vw6jvkranw= +// +// For debugging, the equivalent SHA1-Digest can be generated with OpenSSL: +// +// cat lib/armeabi/libbasic.so | openssl sha1 -binary | openssl base64 +// +// CERT.SF is a similar manifest. It begins with a SHA1 digest of the entire +// manifest file: +// +// Signature-Version: 1.0 +// Created-By: 1.0 (Android) +// SHA1-Digest-Manifest: aJw+u+10C3Enbg8XRCN6jepluYA= +// +// Then for each entry in the manifest it has a SHA1 digest of the manfiest's +// hash combined with the file name: +// +// Name: lib/armeabi/libbasic.so +// SHA1-Digest: Q7NAS6uzrJr6WjePXSGT+vvmdiw= +// +// This can also be generated with openssl: +// +// echo -en "Name: lib/armeabi/libbasic.so\r\nSHA1-Digest: ntLSc1eLCS2Tq1oB4Vw6jvkranw=\r\n\r\n" | openssl sha1 -binary | openssl base64 +// +// Note the \r\n line breaks. +// +// CERT.RSA is an RSA signature block made of CERT.SF. Verify it with: +// +// openssl smime -verify -in CERT.RSA -inform DER -content CERT.SF cert.pem +// +// The APK format imposes two extra restrictions on the ZIP format. First, +// it is uncompressed. Second, each contained file is 4-byte aligned. This +// allows the Android OS to mmap contents without unpacking the archive. + +// Note: to make life a little harder, Android Studio stores the RSA key used +// for signing in an Oracle Java proprietary keystore format, JKS. For example, +// the generated debug key is in ~/.android/debug.keystore, and can be +// extracted using the JDK's keytool utility: +// +// keytool -importkeystore -srckeystore ~/.android/debug.keystore -destkeystore ~/.android/debug.p12 -deststoretype PKCS12 +// +// Once in standard PKCS12, the key can be converted to PEM for use in the +// Go crypto packages: +// +// openssl pkcs12 -in ~/.android/debug.p12 -nocerts -nodes -out ~/.android/debug.pem +// +// Fortunately for debug builds, all that matters is that the APK is signed. +// The choice of key is unimportant, so we can generate one for normal builds. +// For production builds, we can ask users to provide a PEM file. + +import ( + "archive/zip" + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "encoding/base64" + "fmt" + "hash" + "io" +) + +// NewWriter returns a new Writer writing an APK file to w. +// The APK will be signed with key. +func NewWriter(w io.Writer, priv *rsa.PrivateKey) *Writer { + apkw := &Writer{priv: priv} + apkw.w = zip.NewWriter(&countWriter{apkw: apkw, w: w}) + return apkw +} + +// Writer implements an APK file writer. +type Writer struct { + offset int + w *zip.Writer + priv *rsa.PrivateKey + manifest []manifestEntry + cur *fileWriter +} + +// Create adds a file to the APK archive using the provided name. +// +// The name must be a relative path. The file's contents must be written to +// the returned io.Writer before the next call to Create or Close. +func (w *Writer) Create(name string) (io.Writer, error) { + if err := w.clearCur(); err != nil { + return nil, fmt.Errorf("apk: Create(%s): %v", name, err) + } + if name == "AndroidManifest.xml" { + w.cur = &fileWriter{ + name: name, + w: new(bytes.Buffer), + sha1: sha1.New(), + } + return w.cur, nil + } + res, err := w.create(name) + if err != nil { + return nil, fmt.Errorf("apk: Create(%s): %v", name, err) + } + return res, nil +} + +func (w *Writer) create(name string) (io.Writer, error) { + // Align start of file contents by using Extra as padding. + if err := w.w.Flush(); err != nil { // for exact offset + return nil, err + } + const fileHeaderLen = 30 // + filename + extra + start := w.offset + fileHeaderLen + len(name) + extra := start % 4 + + zipfw, err := w.w.CreateHeader(&zip.FileHeader{ + Name: name, + Extra: make([]byte, extra), + }) + if err != nil { + return nil, err + } + w.cur = &fileWriter{ + name: name, + w: zipfw, + sha1: sha1.New(), + } + return w.cur, nil +} + +// Close finishes writing the APK. This includes writing the manifest and +// signing the archive, and writing the ZIP central directory. +// +// It does not close the underlying writer. +func (w *Writer) Close() error { + if err := w.clearCur(); err != nil { + return fmt.Errorf("apk: %v", err) + } + + hasDex := false + for _, entry := range w.manifest { + if entry.name == "classes.dex" { + hasDex = true + break + } + } + + manifest := new(bytes.Buffer) + if hasDex { + fmt.Fprint(manifest, manifestDexHeader) + } else { + fmt.Fprint(manifest, manifestHeader) + } + certBody := new(bytes.Buffer) + + for _, entry := range w.manifest { + n := entry.name + h := base64.StdEncoding.EncodeToString(entry.sha1.Sum(nil)) + fmt.Fprintf(manifest, "Name: %s\nSHA1-Digest: %s\n\n", n, h) + cHash := sha1.New() + fmt.Fprintf(cHash, "Name: %s\r\nSHA1-Digest: %s\r\n\r\n", n, h) + ch := base64.StdEncoding.EncodeToString(cHash.Sum(nil)) + fmt.Fprintf(certBody, "Name: %s\nSHA1-Digest: %s\n\n", n, ch) + } + + mHash := sha1.New() + mHash.Write(manifest.Bytes()) + cert := new(bytes.Buffer) + fmt.Fprint(cert, certHeader) + fmt.Fprintf(cert, "SHA1-Digest-Manifest: %s\n\n", base64.StdEncoding.EncodeToString(mHash.Sum(nil))) + cert.Write(certBody.Bytes()) + + mw, err := w.Create("META-INF/MANIFEST.MF") + if err != nil { + return err + } + if _, err := mw.Write(manifest.Bytes()); err != nil { + return err + } + + cw, err := w.Create("META-INF/CERT.SF") + if err != nil { + return err + } + if _, err := cw.Write(cert.Bytes()); err != nil { + return err + } + + rsa, err := signPKCS7(rand.Reader, w.priv, cert.Bytes()) + if err != nil { + return fmt.Errorf("apk: %v", err) + } + rw, err := w.Create("META-INF/CERT.RSA") + if err != nil { + return err + } + if _, err := rw.Write(rsa); err != nil { + return err + } + + return w.w.Close() +} + +const manifestHeader = `Manifest-Version: 1.0 +Created-By: 1.0 (Go) + +` + +const manifestDexHeader = `Manifest-Version: 1.0 +Dex-Location: classes.dex +Created-By: 1.0 (Go) + +` + +const certHeader = `Signature-Version: 1.0 +Created-By: 1.0 (Go) +` + +func (w *Writer) clearCur() error { + if w.cur == nil { + return nil + } + if w.cur.name == "AndroidManifest.xml" { + buf := w.cur.w.(*bytes.Buffer) + b, err := binaryXML(buf) + if err != nil { + return err + } + f, err := w.create("AndroidManifest.xml") + if err != nil { + return err + } + if _, err := f.Write(b); err != nil { + return err + } + } + w.manifest = append(w.manifest, manifestEntry{ + name: w.cur.name, + sha1: w.cur.sha1, + }) + w.cur.closed = true + w.cur = nil + return nil +} + +type manifestEntry struct { + name string + sha1 hash.Hash +} + +type countWriter struct { + apkw *Writer + w io.Writer +} + +func (c *countWriter) Write(p []byte) (n int, err error) { + n, err = c.w.Write(p) + c.apkw.offset += n + return n, err +} + +type fileWriter struct { + name string + w io.Writer + sha1 hash.Hash + closed bool +} + +func (w *fileWriter) Write(p []byte) (n int, err error) { + if w.closed { + return 0, fmt.Errorf("apk: write to closed file %q", w.name) + } + w.sha1.Write(p) + n, err = w.w.Write(p) + if err != nil { + err = fmt.Errorf("apk: %v", err) + } + return n, err +} diff --git a/src/golang.org/x/mobile/cmd/gomobile/writer_test.go b/src/golang.org/x/mobile/cmd/gomobile/writer_test.go new file mode 100644 index 0000000000..81ab63e921 --- /dev/null +++ b/src/golang.org/x/mobile/cmd/gomobile/writer_test.go @@ -0,0 +1,172 @@ +// Copyright 2015 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. + +package main + +import ( + "crypto/x509" + "encoding/pem" + "io" + "io/ioutil" + "os" + "os/exec" + "testing" +) + +func TestWriter(t *testing.T) { + block, _ := pem.Decode([]byte(debugCert)) + if block == nil { + t.Fatal("no cert") + } + privKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + t.Fatal(err) + } + + f, err := ioutil.TempFile("", "testapk-") + if err != nil { + t.Fatal(err) + } + f.Close() + defer os.Remove(f.Name()) + apkPath := f.Name() + ".apk" + + f, err = os.Create(apkPath) + if err != nil { + t.Fatal(err) + } + defer os.Remove(apkPath) + + apkw := NewWriter(f, privKey) + + w, err := apkw.Create("AndroidManifest.xml") + if err != nil { + t.Fatalf("could not create AndroidManifest.xml: %v", err) + } + if _, err := w.Write([]byte(androidManifest)); err != nil { + t.Errorf("could not write AndroidManifest.xml: %v", err) + } + + w, err = apkw.Create("assets/hello_world.txt") + if err != nil { + t.Fatalf("could not create assets/hello_world.txt: %v", err) + } + if _, err := w.Write([]byte("Hello, 世界")); err != nil { + t.Errorf("could not write assets/hello_world.txt: %v", err) + } + + if err := apkw.Close(); err != nil { + t.Fatal(err) + } + + if exec.Command("which", "aapt").Run() != nil { + t.Skip("command aapt not found, skipping") + } + + out, err := exec.Command("aapt", "list", "-a", apkPath).CombinedOutput() + aaptGot := string(out) + if err != nil { + t.Logf("aapt:\n%s", aaptGot) + t.Fatalf("aapt failed: %v", err) + } + + if aaptGot != aaptWant { + t.Errorf("unexpected output from aapt") + d, err := diff(aaptGot, aaptWant) + if err != nil { + t.Errorf("diff failed: %v", err) + } else { + t.Logf("%s", d) + } + } +} + +const aaptWant = `AndroidManifest.xml +assets/hello_world.txt +META-INF/MANIFEST.MF +META-INF/CERT.SF +META-INF/CERT.RSA + +Resource table: +Package Groups (0) + +Android manifest: +N: android=http://schemas.android.com/apk/res/android + E: manifest (line=2) + A: package="org.golang.fakeapp" (Raw: "org.golang.fakeapp") + A: android:versionCode(0x0101021b)=(type 0x10)0x1 + A: android:versionName(0x0101021c)="1.0" (Raw: "1.0") + E: uses-sdk (line=8) + A: android:minSdkVersion(0x0101020c)=(type 0x10)0x9 + E: application (line=9) + A: android:label(0x01010001)="FakeApp" (Raw: "FakeApp") + A: android:hasCode(0x0101000c)=(type 0x12)0x0 + A: android:debuggable(0x0101000f)=(type 0x12)0xffffffff + E: activity (line=10) + A: android:name(0x01010003)="android.app.NativeActivity" (Raw: "android.app.NativeActivity") + A: android:label(0x01010001)="FakeApp" (Raw: "FakeApp") + A: android:configChanges(0x0101001f)=(type 0x11)0xa0 + E: intent-filter (line=14) + E: action (line=15) + A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN") + E: category (line=16) + A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER") +` + +const androidManifest = ` + + + + + + + + + + + + + +` + +func writeTempFile(data string) (string, error) { + f, err := ioutil.TempFile("", "gofmt") + if err != nil { + return "", err + } + _, err = io.WriteString(f, data) + errc := f.Close() + if err == nil { + return f.Name(), errc + } + return f.Name(), err +} + +func diff(got, want string) (string, error) { + wantPath, err := writeTempFile(want) + if err != nil { + return "", err + } + defer os.Remove(wantPath) + + gotPath, err := writeTempFile(got) + if err != nil { + return "", err + } + defer os.Remove(gotPath) + + data, err := exec.Command("diff", "-u", wantPath, gotPath).CombinedOutput() + if len(data) > 0 { + // diff exits with a non-zero status when the files don't match. + // Ignore that failure as long as we get output. + err = nil + } + return string(data), err +} diff --git a/src/golang.org/x/mobile/codereview.cfg b/src/golang.org/x/mobile/codereview.cfg new file mode 100644 index 0000000000..3f8b14b64e --- /dev/null +++ b/src/golang.org/x/mobile/codereview.cfg @@ -0,0 +1 @@ +issuerepo: golang/go diff --git a/src/golang.org/x/mobile/doc/caution.png b/src/golang.org/x/mobile/doc/caution.png new file mode 100644 index 0000000000..cdb9faa932 Binary files /dev/null and b/src/golang.org/x/mobile/doc/caution.png differ diff --git a/src/golang.org/x/mobile/event/config/config.go b/src/golang.org/x/mobile/event/config/config.go new file mode 100644 index 0000000000..256844e4d2 --- /dev/null +++ b/src/golang.org/x/mobile/event/config/config.go @@ -0,0 +1,42 @@ +// Copyright 2015 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. + +// Package config defines an event for the dimensions and physical resolution +// of the app's window. +// +// See the golang.org/x/mobile/app package for details on the event model. +package config // import "golang.org/x/mobile/event/config" + +import ( + "image" + + "golang.org/x/mobile/geom" +) + +// Event holds the dimensions and physical resolution of the app's window. +type Event struct { + // WidthPx and HeightPx are the window's dimensions in pixels. + WidthPx, HeightPx int + + // WidthPt and HeightPt are the window's dimensions in points (1/72 of an + // inch). + WidthPt, HeightPt geom.Pt + + // PixelsPerPt is the window's physical resolution. It is the number of + // pixels in a single geom.Pt, from the golang.org/x/mobile/geom package. + // + // There are a wide variety of pixel densities in existing phones and + // tablets, so apps should be written to expect various non-integer + // PixelsPerPt values. In general, work in geom.Pt. + PixelsPerPt float32 +} + +// Bounds returns the window's bounds in pixels, at the time this configuration +// event was sent. +// +// The top-left pixel is always (0, 0). The bottom-right pixel is given by the +// width and height. +func (e *Event) Bounds() image.Rectangle { + return image.Rectangle{Max: image.Point{e.WidthPx, e.HeightPx}} +} diff --git a/src/golang.org/x/mobile/event/key/key.go b/src/golang.org/x/mobile/event/key/key.go new file mode 100644 index 0000000000..c0df940902 --- /dev/null +++ b/src/golang.org/x/mobile/event/key/key.go @@ -0,0 +1,250 @@ +// Copyright 2015 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. + +// Package key defines an event for physical keyboard keys. +// +// On-screen software keyboards do not send key events. +// +// See the golang.org/x/mobile/app package for details on the event model. +package key + +import ( + "fmt" + "strings" +) + +// Event is a key event. +type Event struct { + // Rune is the meaning of the key event as determined by the + // operating system. The mapping is determined by system-dependent + // current layout, modifiers, lock-states, etc. + // + // If non-negative, it is a Unicode codepoint: pressing the 'a' key + // generates different Runes 'a' or 'A' (but the same Code) depending on + // the state of the shift key. + // + // If -1, the key does not generate a Unicode codepoint. To distinguish + // them, look at Code. + Rune rune + + // Code is the identity of the physical key relative to a notional + // "standard" keyboard, independent of current layout, modifiers, + // lock-states, etc + // + // For standard key codes, its value matches USB HID key codes. + // Compare its value to uint32-typed constants in this package, such + // as CodeLeftShift and CodeEscape. + // + // Pressing the regular '2' key and number-pad '2' key (with Num-Lock) + // generate different Codes (but the same Rune). + Code Code + + // Modifiers is a bitmask representing a set of modifier keys: ModShift, + // ModAlt, etc. + Modifiers Modifiers + + // Direction is the direction of the key event: DirPress, DirRelease, + // or DirNone (for key repeats). + Direction Direction + + // TODO: add a Device ID, for multiple input devices? + // TODO: add a time.Time? +} + +// Direction is the direction of the key event. +type Direction uint8 + +const ( + DirNone Direction = 0 + DirPress Direction = 1 + DirRelease Direction = 2 +) + +// Modifiers is a bitmask representing a set of modifier keys. +type Modifiers uint32 + +const ( + ModShift Modifiers = 1 << 0 + ModControl Modifiers = 1 << 1 + ModAlt Modifiers = 1 << 2 + ModMeta Modifiers = 1 << 3 // called "Command" on OS X +) + +// Code is the identity of a key relative to a notional "standard" keyboard. +type Code uint32 + +// Physical key codes. +// +// For standard key codes, its value matches USB HID key codes. +// TODO: add missing codes. +const ( + CodeUnknown Code = 0 + + CodeA Code = 4 + CodeB Code = 5 + CodeC Code = 6 + CodeD Code = 7 + CodeE Code = 8 + CodeF Code = 9 + CodeG Code = 10 + CodeH Code = 11 + CodeI Code = 12 + CodeJ Code = 13 + CodeK Code = 14 + CodeL Code = 15 + CodeM Code = 16 + CodeN Code = 17 + CodeO Code = 18 + CodeP Code = 19 + CodeQ Code = 20 + CodeR Code = 21 + CodeS Code = 22 + CodeT Code = 23 + CodeU Code = 24 + CodeV Code = 25 + CodeW Code = 26 + CodeX Code = 27 + CodeY Code = 28 + CodeZ Code = 29 + + Code1 Code = 30 + Code2 Code = 31 + Code3 Code = 32 + Code4 Code = 33 + Code5 Code = 34 + Code6 Code = 35 + Code7 Code = 36 + Code8 Code = 37 + Code9 Code = 38 + Code0 Code = 39 + + CodeReturnEnter Code = 40 + CodeEscape Code = 41 + CodeDeleteBackspace Code = 42 + CodeTab Code = 43 + CodeSpacebar Code = 44 + CodeHyphenMinus Code = 45 // - + CodeEqualSign Code = 46 // = + CodeLeftSquareBracket Code = 47 // [ + CodeRightSquareBracket Code = 48 // ] + CodeBackslash Code = 49 // \ + CodeSemicolon Code = 51 // ; + CodeApostrophe Code = 52 // ' + CodeGraveAccent Code = 53 // ` + CodeComma Code = 54 // , + CodeFullStop Code = 55 // . + CodeSlash Code = 56 // / + CodeCapsLock Code = 57 + + CodeF1 Code = 58 + CodeF2 Code = 59 + CodeF3 Code = 60 + CodeF4 Code = 61 + CodeF5 Code = 62 + CodeF6 Code = 63 + CodeF7 Code = 64 + CodeF8 Code = 65 + CodeF9 Code = 66 + CodeF10 Code = 67 + CodeF11 Code = 68 + CodeF12 Code = 69 + + CodePause Code = 72 + CodeInsert Code = 73 + CodeHome Code = 74 + CodePageUp Code = 75 + CodeDeleteForward Code = 76 + CodeEnd Code = 77 + CodePageDown Code = 78 + + CodeRightArrow Code = 79 + CodeLeftArrow Code = 80 + CodeDownArrow Code = 81 + CodeUpArrow Code = 82 + + CodeKeypadNumLock Code = 83 + CodeKeypadSlash Code = 84 // / + CodeKeypadAsterisk Code = 85 // * + CodeKeypadHyphenMinus Code = 86 // - + CodeKeypadPlusSign Code = 87 // + + CodeKeypadEnter Code = 88 + CodeKeypad1 Code = 89 + CodeKeypad2 Code = 90 + CodeKeypad3 Code = 91 + CodeKeypad4 Code = 92 + CodeKeypad5 Code = 93 + CodeKeypad6 Code = 94 + CodeKeypad7 Code = 95 + CodeKeypad8 Code = 96 + CodeKeypad9 Code = 97 + CodeKeypad0 Code = 98 + CodeKeypadFullStop Code = 99 // . + CodeKeypadEqualSign Code = 103 // = + + CodeF13 Code = 104 + CodeF14 Code = 105 + CodeF15 Code = 106 + CodeF16 Code = 107 + CodeF17 Code = 108 + CodeF18 Code = 109 + CodeF19 Code = 110 + CodeF20 Code = 111 + CodeF21 Code = 112 + CodeF22 Code = 113 + CodeF23 Code = 114 + CodeF24 Code = 115 + + CodeHelp Code = 117 + + CodeMute Code = 127 + CodeVolumeUp Code = 128 + CodeVolumeDown Code = 129 + + CodeLeftControl Code = 224 + CodeLeftShift Code = 225 + CodeLeftAlt Code = 226 + CodeLeftGUI Code = 227 + CodeRightControl Code = 228 + CodeRightShift Code = 229 + CodeRightAlt Code = 230 + CodeRightGUI Code = 231 +) + +// TODO: Given we use runes outside the unicode space, should we provide a +// printing function? Related: it's a little unfortunate that printing a +// key.Event with %v gives not very readable output like: +// {100 7 key.Modifiers() Press} + +var mods = [...]struct { + m Modifiers + s string +}{ + {ModShift, "Shift"}, + {ModControl, "Control"}, + {ModAlt, "Alt"}, + {ModMeta, "Meta"}, +} + +func (m Modifiers) String() string { + var match []string + for _, mod := range mods { + if mod.m&m != 0 { + match = append(match, mod.s) + } + } + return "key.Modifiers(" + strings.Join(match, "|") + ")" +} + +func (d Direction) String() string { + switch d { + case DirNone: + return "None" + case DirPress: + return "Press" + case DirRelease: + return "Release" + default: + return fmt.Sprintf("key.Direction(%d)", d) + } +} diff --git a/src/golang.org/x/mobile/event/lifecycle/lifecycle.go b/src/golang.org/x/mobile/event/lifecycle/lifecycle.go new file mode 100644 index 0000000000..9376fe5b6a --- /dev/null +++ b/src/golang.org/x/mobile/event/lifecycle/lifecycle.go @@ -0,0 +1,124 @@ +// Copyright 2015 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. + +// Package lifecycle defines an event for an app's lifecycle. +// +// The app lifecycle consists of moving back and forth between an ordered +// sequence of stages. For example, being at a stage greater than or equal to +// StageVisible means that the app is visible on the screen. +// +// A lifecycle event is a change from one stage to another, which crosses every +// intermediate stage. For example, changing from StageAlive to StageFocused +// implicitly crosses StageVisible. +// +// Crosses can be in a positive or negative direction. A positive crossing of +// StageFocused means that the app has gained the focus. A negative crossing +// means it has lost the focus. +// +// See the golang.org/x/mobile/app package for details on the event model. +package lifecycle // import "golang.org/x/mobile/event/lifecycle" + +import ( + "fmt" +) + +// Cross is whether a lifecycle stage was crossed. +type Cross uint32 + +func (c Cross) String() string { + switch c { + case CrossOn: + return "on" + case CrossOff: + return "off" + } + return "none" +} + +const ( + CrossNone Cross = 0 + CrossOn Cross = 1 + CrossOff Cross = 2 +) + +// Event is a lifecycle change from an old stage to a new stage. +type Event struct { + From, To Stage +} + +// Crosses returns whether the transition from From to To crosses the stage s: +// - It returns CrossOn if it does, and the lifecycle change is positive. +// - It returns CrossOff if it does, and the lifecycle change is negative. +// - Otherwise, it returns CrossNone. +// See the documentation for Stage for more discussion of positive and negative +// crosses. +func (e Event) Crosses(s Stage) Cross { + switch { + case e.From < s && e.To >= s: + return CrossOn + case e.From >= s && e.To < s: + return CrossOff + } + return CrossNone +} + +// Stage is a stage in the app's lifecycle. The values are ordered, so that a +// lifecycle change from stage From to stage To implicitly crosses every stage +// in the range (min, max], exclusive on the low end and inclusive on the high +// end, where min is the minimum of From and To, and max is the maximum. +// +// The documentation for individual stages talk about positive and negative +// crosses. A positive lifecycle change is one where its From stage is less +// than its To stage. Similarly, a negative lifecycle change is one where From +// is greater than To. Thus, a positive lifecycle change crosses every stage in +// the range (From, To] in increasing order, and a negative lifecycle change +// crosses every stage in the range (To, From] in decreasing order. +type Stage uint32 + +// TODO: how does iOS map to these stages? What do cross-platform mobile +// abstractions do? + +const ( + // StageDead is the zero stage. No lifecycle change crosses this stage, + // but: + // - A positive change from this stage is the very first lifecycle change. + // - A negative change to this stage is the very last lifecycle change. + StageDead Stage = iota + + // StageAlive means that the app is alive. + // - A positive cross means that the app has been created. + // - A negative cross means that the app is being destroyed. + // Each cross, either from or to StageDead, will occur only once. + // On Android, these correspond to onCreate and onDestroy. + StageAlive + + // StageVisible means that the app window is visible. + // - A positive cross means that the app window has become visible. + // - A negative cross means that the app window has become invisible. + // On Android, these correspond to onStart and onStop. + // On Desktop, an app window can become invisible if e.g. it is minimized, + // unmapped, or not on a visible workspace. + StageVisible + + // StageFocused means that the app window has the focus. + // - A positive cross means that the app window has gained the focus. + // - A negative cross means that the app window has lost the focus. + // On Android, these correspond to onResume and onFreeze. + StageFocused +) + +func (s Stage) String() string { + switch s { + case StageDead: + return "StageDead" + case StageAlive: + return "StageAlive" + case StageVisible: + return "StageVisible" + case StageFocused: + return "StageFocused" + default: + return fmt.Sprintf("lifecycle.Stage(%d)", s) + } +} diff --git a/src/golang.org/x/mobile/event/mouse/mouse.go b/src/golang.org/x/mobile/event/mouse/mouse.go new file mode 100644 index 0000000000..4e9ab585fe --- /dev/null +++ b/src/golang.org/x/mobile/event/mouse/mouse.go @@ -0,0 +1,75 @@ +// Copyright 2015 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. + +// Package mouse defines an event for mouse input. +// +// See the golang.org/x/mobile/app package for details on the event model. +package mouse // import "golang.org/x/mobile/event/mouse" + +import ( + "fmt" + + "golang.org/x/mobile/event/key" +) + +// Event is a mouse event. +type Event struct { + // X and Y are the mouse location, in pixels. + X, Y float32 + + // Button is the mouse button being pressed or released. Its value may be + // zero, for a mouse move or drag without any button change. + Button Button + + // TODO: have a field to hold what other buttons are down, for detecting + // drags or button-chords. + + // Modifiers is a bitmask representing a set of modifier keys: + // key.ModShift, key.ModAlt, etc. + Modifiers key.Modifiers + + // Direction is the direction of the mouse event: DirPress, DirRelease, + // or DirNone (for mouse moves or drags). + Direction Direction + + // TODO: add a Device ID, for multiple input devices? + // TODO: add a time.Time? +} + +// Button is a mouse button. +type Button int32 + +// TODO: have a separate axis concept for wheel up/down? How does that relate +// to joystick events? + +const ( + ButtonNone Button = +0 + ButtonLeft Button = +1 + ButtonMiddle Button = +2 + ButtonRight Button = +3 + ButtonWheelUp Button = -1 + ButtonWheelDown Button = -2 +) + +// Direction is the direction of the mouse event. +type Direction uint8 + +const ( + DirNone Direction = 0 + DirPress Direction = 1 + DirRelease Direction = 2 +) + +func (d Direction) String() string { + switch d { + case DirNone: + return "None" + case DirPress: + return "Press" + case DirRelease: + return "Release" + default: + return fmt.Sprintf("mouse.Direction(%d)", d) + } +} diff --git a/src/golang.org/x/mobile/event/paint/paint.go b/src/golang.org/x/mobile/event/paint/paint.go new file mode 100644 index 0000000000..f63f75804c --- /dev/null +++ b/src/golang.org/x/mobile/event/paint/paint.go @@ -0,0 +1,15 @@ +// Copyright 2015 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. + +// Package paint defines an event for the app being ready to paint. +// +// See the golang.org/x/mobile/app package for details on the event model. +package paint // import "golang.org/x/mobile/event/paint" + +// Event indicates that the app is ready to paint the next frame of the GUI. A +// frame is completed by calling the App's EndPaint method. +type Event struct { + // Generation is a monotonically increasing generation number. + Generation uint32 +} diff --git a/src/golang.org/x/mobile/event/touch/touch.go b/src/golang.org/x/mobile/event/touch/touch.go new file mode 100644 index 0000000000..c91967be2a --- /dev/null +++ b/src/golang.org/x/mobile/event/touch/touch.go @@ -0,0 +1,72 @@ +// Copyright 2015 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. + +// Package touch defines an event for touch input. +// +// See the golang.org/x/mobile/app package for details on the event model. +package touch // import "golang.org/x/mobile/event/touch" + +// The best source on android input events is the NDK: include/android/input.h +// +// iOS event handling guide: +// https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS + +import ( + "fmt" +) + +// Event is a touch event. +type Event struct { + // X and Y are the touch location, in pixels. + X, Y float32 + + // Sequence is the sequence number. The same number is shared by all events + // in a sequence. A sequence begins with a single TypeBegin, is followed by + // zero or more TypeMoves, and ends with a single TypeEnd. A Sequence + // distinguishes concurrent sequences but its value is subsequently reused. + Sequence Sequence + + // Type is the touch type. + Type Type +} + +// Sequence identifies a sequence of touch events. +type Sequence int64 + +// Type describes the type of a touch event. +type Type byte + +const ( + // TypeBegin is a user first touching the device. + // + // On Android, this is a AMOTION_EVENT_ACTION_DOWN. + // On iOS, this is a call to touchesBegan. + TypeBegin Type = iota + + // TypeMove is a user dragging across the device. + // + // A TypeMove is delivered between a TypeBegin and TypeEnd. + // + // On Android, this is a AMOTION_EVENT_ACTION_MOVE. + // On iOS, this is a call to touchesMoved. + TypeMove + + // TypeEnd is a user no longer touching the device. + // + // On Android, this is a AMOTION_EVENT_ACTION_UP. + // On iOS, this is a call to touchesEnded. + TypeEnd +) + +func (t Type) String() string { + switch t { + case TypeBegin: + return "begin" + case TypeMove: + return "move" + case TypeEnd: + return "end" + } + return fmt.Sprintf("touch.Type(%d)", t) +} diff --git a/src/golang.org/x/mobile/example/audio/assets/boing.wav b/src/golang.org/x/mobile/example/audio/assets/boing.wav new file mode 100644 index 0000000000..f641bf1e0d Binary files /dev/null and b/src/golang.org/x/mobile/example/audio/assets/boing.wav differ diff --git a/src/golang.org/x/mobile/example/audio/assets/gopher.jpeg b/src/golang.org/x/mobile/example/audio/assets/gopher.jpeg new file mode 100644 index 0000000000..ebb3eb15dd Binary files /dev/null and b/src/golang.org/x/mobile/example/audio/assets/gopher.jpeg differ diff --git a/src/golang.org/x/mobile/example/audio/main.go b/src/golang.org/x/mobile/example/audio/main.go new file mode 100644 index 0000000000..bbe7486033 --- /dev/null +++ b/src/golang.org/x/mobile/example/audio/main.go @@ -0,0 +1,195 @@ +// Copyright 2015 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 darwin linux + +// An app that makes a sound as the gopher hits the walls of the screen. +// +// Note: This demo is an early preview of Go 1.5. In order to build this +// program as an Android APK using the gomobile tool. +// +// See http://godoc.org/golang.org/x/mobile/cmd/gomobile to install gomobile. +// +// Get the audio example and use gomobile to build or install it on your device. +// +// $ go get -d golang.org/x/mobile/example/audio +// $ gomobile build golang.org/x/mobile/example/audio # will build an APK +// +// # plug your Android device to your computer or start an Android emulator. +// # if you have adb installed on your machine, use gomobile install to +// # build and deploy the APK to an Android target. +// $ gomobile install golang.org/x/mobile/example/audio +// +// Additionally, you can run the sample on your desktop environment +// by using the go tool. +// +// $ go install golang.org/x/mobile/example/audio && audio +// +// On Linux, you need to install OpenAL developer library by +// running the command below. +// +// $ apt-get install libopenal-dev +package main + +import ( + "image" + "log" + "time" + + _ "image/jpeg" + + "golang.org/x/mobile/app" + "golang.org/x/mobile/asset" + "golang.org/x/mobile/event/config" + "golang.org/x/mobile/event/lifecycle" + "golang.org/x/mobile/event/paint" + "golang.org/x/mobile/exp/app/debug" + "golang.org/x/mobile/exp/audio" + "golang.org/x/mobile/exp/f32" + "golang.org/x/mobile/exp/sprite" + "golang.org/x/mobile/exp/sprite/clock" + "golang.org/x/mobile/exp/sprite/glsprite" + "golang.org/x/mobile/gl" +) + +const ( + width = 72 + height = 60 +) + +var ( + startTime = time.Now() + + eng = glsprite.Engine() + scene *sprite.Node + + player *audio.Player + + cfg config.Event +) + +func main() { + app.Main(func(a app.App) { + for e := range a.Events() { + switch e := app.Filter(e).(type) { + case lifecycle.Event: + switch e.Crosses(lifecycle.StageVisible) { + case lifecycle.CrossOn: + onStart() + case lifecycle.CrossOff: + onStop() + } + case config.Event: + cfg = e + case paint.Event: + onPaint() + a.EndPaint(e) + } + } + }) +} + +func onStart() { + rc, err := asset.Open("boing.wav") + if err != nil { + log.Fatal(err) + } + player, err = audio.NewPlayer(rc, 0, 0) + if err != nil { + log.Fatal(err) + } +} + +func onStop() { + player.Close() +} + +func onPaint() { + if scene == nil { + loadScene() + } + gl.ClearColor(1, 1, 1, 1) + gl.Clear(gl.COLOR_BUFFER_BIT) + now := clock.Time(time.Since(startTime) * 60 / time.Second) + eng.Render(scene, now, cfg) + debug.DrawFPS(cfg) +} + +func newNode() *sprite.Node { + n := &sprite.Node{} + eng.Register(n) + scene.AppendChild(n) + return n +} + +func loadScene() { + gopher := loadGopher() + scene = &sprite.Node{} + eng.Register(scene) + eng.SetTransform(scene, f32.Affine{ + {1, 0, 0}, + {0, 1, 0}, + }) + + var x, y float32 + dx, dy := float32(1), float32(1) + + n := newNode() + // TODO: Shouldn't arranger pass the config.Event? + n.Arranger = arrangerFunc(func(eng sprite.Engine, n *sprite.Node, t clock.Time) { + eng.SetSubTex(n, gopher) + + if x < 0 { + dx = 1 + boing() + } + if y < 0 { + dy = 1 + boing() + } + if x+width > float32(cfg.WidthPt) { + dx = -1 + boing() + } + if y+height > float32(cfg.HeightPt) { + dy = -1 + boing() + } + + x += dx + y += dy + + eng.SetTransform(n, f32.Affine{ + {width, 0, x}, + {0, height, y}, + }) + }) +} + +func boing() { + player.Seek(0) + player.Play() +} + +func loadGopher() sprite.SubTex { + a, err := asset.Open("gopher.jpeg") + if err != nil { + log.Fatal(err) + } + defer a.Close() + + img, _, err := image.Decode(a) + if err != nil { + log.Fatal(err) + } + t, err := eng.LoadTexture(img) + if err != nil { + log.Fatal(err) + } + return sprite.SubTex{t, image.Rect(0, 0, 360, 300)} +} + +type arrangerFunc func(e sprite.Engine, n *sprite.Node, t clock.Time) + +func (a arrangerFunc) Arrange(e sprite.Engine, n *sprite.Node, t clock.Time) { a(e, n, t) } diff --git a/src/golang.org/x/mobile/example/basic/main.go b/src/golang.org/x/mobile/example/basic/main.go new file mode 100644 index 0000000000..8e398424d1 --- /dev/null +++ b/src/golang.org/x/mobile/example/basic/main.go @@ -0,0 +1,160 @@ +// Copyright 2014 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 darwin linux + +// An app that draws a green triangle on a red background. +// +// Note: This demo is an early preview of Go 1.5. In order to build this +// program as an Android APK using the gomobile tool. +// +// See http://godoc.org/golang.org/x/mobile/cmd/gomobile to install gomobile. +// +// Get the basic example and use gomobile to build or install it on your device. +// +// $ go get -d golang.org/x/mobile/example/basic +// $ gomobile build golang.org/x/mobile/example/basic # will build an APK +// +// # plug your Android device to your computer or start an Android emulator. +// # if you have adb installed on your machine, use gomobile install to +// # build and deploy the APK to an Android target. +// $ gomobile install golang.org/x/mobile/example/basic +// +// Switch to your device or emulator to start the Basic application from +// the launcher. +// You can also run the application on your desktop by running the command +// below. (Note: It currently doesn't work on Windows.) +// $ go install golang.org/x/mobile/example/basic && basic +package main + +import ( + "encoding/binary" + "log" + + "golang.org/x/mobile/app" + "golang.org/x/mobile/event/config" + "golang.org/x/mobile/event/lifecycle" + "golang.org/x/mobile/event/paint" + "golang.org/x/mobile/event/touch" + "golang.org/x/mobile/exp/app/debug" + "golang.org/x/mobile/exp/f32" + "golang.org/x/mobile/exp/gl/glutil" + "golang.org/x/mobile/gl" +) + +var ( + program gl.Program + position gl.Attrib + offset gl.Uniform + color gl.Uniform + buf gl.Buffer + + green float32 + touchX float32 + touchY float32 +) + +func main() { + app.Main(func(a app.App) { + var c config.Event + for e := range a.Events() { + switch e := app.Filter(e).(type) { + case lifecycle.Event: + switch e.Crosses(lifecycle.StageVisible) { + case lifecycle.CrossOn: + onStart() + case lifecycle.CrossOff: + onStop() + } + case config.Event: + c = e + touchX = float32(c.WidthPx / 2) + touchY = float32(c.HeightPx / 2) + case paint.Event: + onPaint(c) + a.EndPaint(e) + case touch.Event: + touchX = e.X + touchY = e.Y + } + } + }) +} + +func onStart() { + var err error + program, err = glutil.CreateProgram(vertexShader, fragmentShader) + if err != nil { + log.Printf("error creating GL program: %v", err) + return + } + + buf = gl.CreateBuffer() + gl.BindBuffer(gl.ARRAY_BUFFER, buf) + gl.BufferData(gl.ARRAY_BUFFER, triangleData, gl.STATIC_DRAW) + + position = gl.GetAttribLocation(program, "position") + color = gl.GetUniformLocation(program, "color") + offset = gl.GetUniformLocation(program, "offset") + + // TODO(crawshaw): the debug package needs to put GL state init here + // Can this be an app.RegisterFilter call now?? +} + +func onStop() { + gl.DeleteProgram(program) + gl.DeleteBuffer(buf) +} + +func onPaint(c config.Event) { + gl.ClearColor(1, 0, 0, 1) + gl.Clear(gl.COLOR_BUFFER_BIT) + + gl.UseProgram(program) + + green += 0.01 + if green > 1 { + green = 0 + } + gl.Uniform4f(color, 0, green, 0, 1) + + gl.Uniform2f(offset, touchX/float32(c.WidthPx), touchY/float32(c.HeightPx)) + + gl.BindBuffer(gl.ARRAY_BUFFER, buf) + gl.EnableVertexAttribArray(position) + gl.VertexAttribPointer(position, coordsPerVertex, gl.FLOAT, false, 0, 0) + gl.DrawArrays(gl.TRIANGLES, 0, vertexCount) + gl.DisableVertexAttribArray(position) + + debug.DrawFPS(c) +} + +var triangleData = f32.Bytes(binary.LittleEndian, + 0.0, 0.4, 0.0, // top left + 0.0, 0.0, 0.0, // bottom left + 0.4, 0.0, 0.0, // bottom right +) + +const ( + coordsPerVertex = 3 + vertexCount = 3 +) + +const vertexShader = `#version 100 +uniform vec2 offset; + +attribute vec4 position; +void main() { + // offset comes in with x/y values between 0 and 1. + // position bounds are -1 to 1. + vec4 offset4 = vec4(2.0*offset.x-1.0, 1.0-2.0*offset.y, 0, 0); + gl_Position = position + offset4; +}` + +const fragmentShader = `#version 100 +precision mediump float; +uniform vec4 color; +void main() { + gl_FragColor = color; +}` diff --git a/src/golang.org/x/mobile/example/bind/android/README b/src/golang.org/x/mobile/example/bind/android/README new file mode 100644 index 0000000000..f60569f4a7 --- /dev/null +++ b/src/golang.org/x/mobile/example/bind/android/README @@ -0,0 +1,4 @@ +Go bind android app example + +Set the GOPATH, GO, GOMOBILE variables in hello/build.gradle +and import this project in Android Studio. diff --git a/src/golang.org/x/mobile/example/bind/android/app/build.gradle b/src/golang.org/x/mobile/example/bind/android/app/build.gradle new file mode 100644 index 0000000000..3fb729ef9a --- /dev/null +++ b/src/golang.org/x/mobile/example/bind/android/app/build.gradle @@ -0,0 +1,30 @@ +/* + * Copyright 2015 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. + */ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 22 + buildToolsVersion "22.0.1" + + defaultConfig { + applicationId "org.golang.example.android" + minSdkVersion 15 + targetSdkVersion 22 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + } + } +} + +dependencies { + compile fileTree(include: ['*.jar'], dir: 'libs') + compile 'com.android.support:appcompat-v7:22.1.1' + compile project(':hello') +} diff --git a/src/golang.org/x/mobile/example/bind/android/app/src/main/AndroidManifest.xml b/src/golang.org/x/mobile/example/bind/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..fa1a16079b --- /dev/null +++ b/src/golang.org/x/mobile/example/bind/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + diff --git a/src/golang.org/x/mobile/example/bind/android/app/src/main/java/org/golang/example/bind/MainActivity.java b/src/golang.org/x/mobile/example/bind/android/app/src/main/java/org/golang/example/bind/MainActivity.java new file mode 100644 index 0000000000..8308bb47b9 --- /dev/null +++ b/src/golang.org/x/mobile/example/bind/android/app/src/main/java/org/golang/example/bind/MainActivity.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015 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. + */ + +package org.golang.example.bind; + +import android.app.Activity; +import android.os.Bundle; +import android.widget.TextView; + +import go.hello.Hello; + + +public class MainActivity extends Activity { + + private TextView mTextView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + mTextView = (TextView) findViewById(R.id.mytextview); + + // Call Go function. + String greetings = Hello.Greetings("Android and Gopher"); + mTextView.setText(greetings); + } +} diff --git a/src/golang.org/x/mobile/example/bind/android/app/src/main/res/layout/activity_main.xml b/src/golang.org/x/mobile/example/bind/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000000..43be4e31b1 --- /dev/null +++ b/src/golang.org/x/mobile/example/bind/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/src/golang.org/x/mobile/example/bind/android/app/src/main/res/values/dimens.xml b/src/golang.org/x/mobile/example/bind/android/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000000..cc0636b6cf --- /dev/null +++ b/src/golang.org/x/mobile/example/bind/android/app/src/main/res/values/dimens.xml @@ -0,0 +1,8 @@ + + + + 16dp + 16dp + diff --git a/src/golang.org/x/mobile/example/bind/android/build.gradle b/src/golang.org/x/mobile/example/bind/android/build.gradle new file mode 100644 index 0000000000..cc7ccc86e8 --- /dev/null +++ b/src/golang.org/x/mobile/example/bind/android/build.gradle @@ -0,0 +1,22 @@ +/* Copyright 2015 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. + */ + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.2.3' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + } +} diff --git a/src/golang.org/x/mobile/example/bind/android/hello/build.gradle b/src/golang.org/x/mobile/example/bind/android/hello/build.gradle new file mode 100644 index 0000000000..e7388d8946 --- /dev/null +++ b/src/golang.org/x/mobile/example/bind/android/hello/build.gradle @@ -0,0 +1,24 @@ +/* Copyright 2015 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. + */ + +plugins { + id "org.golang.mobile.bind" version "0.2.2" +} + +gobind { + /* The Go package path; must be under one of the GOPATH elements or + a relative to the current directory (e.g. ../../hello) */ + pkg = "golang.org/x/mobile/example/bind/hello" + + /* GOPATH where the Go package is; check `go env` */ + GOPATH = "/YOUR/GOPATH" + + /* Absolute path to the go binary */ + GO = "/PATH/TO/GO" + + /* Optionally, set the absolute path to the gomobile binary if the + /* gomobile binary is not located in the GOPATH's bin directory. */ + // GOMOBILE = "/PATH/TO/GOMOBILE" +} diff --git a/src/golang.org/x/mobile/example/bind/android/settings.gradle b/src/golang.org/x/mobile/example/bind/android/settings.gradle new file mode 100644 index 0000000000..f37694ce81 --- /dev/null +++ b/src/golang.org/x/mobile/example/bind/android/settings.gradle @@ -0,0 +1,5 @@ +/* Copyright 2015 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. + */ +include ':app', ':hello' diff --git a/src/golang.org/x/mobile/example/bind/hello/hello.go b/src/golang.org/x/mobile/example/bind/hello/hello.go new file mode 100644 index 0000000000..2d98ff990c --- /dev/null +++ b/src/golang.org/x/mobile/example/bind/hello/hello.go @@ -0,0 +1,12 @@ +// Copyright 2015 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. + +// Package hello is a trivial package for gomobile bind example. +package hello + +import "fmt" + +func Greetings(name string) string { + return fmt.Sprintf("Hello, %s!", name) +} diff --git a/src/golang.org/x/mobile/example/bind/ios/README b/src/golang.org/x/mobile/example/bind/ios/README new file mode 100644 index 0000000000..d0203fd3b5 --- /dev/null +++ b/src/golang.org/x/mobile/example/bind/ios/README @@ -0,0 +1,15 @@ +1. Use gomobile bind to bind the golang.org/x/mobile/example/bind/hello package. + The following command will create a static framework bundle in the current + directory. + + $ gomobile bind -target=ios golang.org/x/mobile/example/bind/hello + +2. Open the Xcode project by double clicking on bind.xcodeproj. + The project will not build - ViewController.m calls a function from the hello + package so requires the hello.framework the gomobild bind command created + in Step 1. + +3. Drag-and-drop the hello.framework from the desktop to the project navigation window. + This will automatically include the hello framework into the project. + +4. Build. diff --git a/src/golang.org/x/mobile/example/bind/ios/bind.xcodeproj/project.pbxproj b/src/golang.org/x/mobile/example/bind/ios/bind.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..8c7234228d --- /dev/null +++ b/src/golang.org/x/mobile/example/bind/ios/bind.xcodeproj/project.pbxproj @@ -0,0 +1,309 @@ +// !$*UTF8*$! + +// Copyright 2015 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. + +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + EBA3E2861B681AFA00018449 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = EBA3E2851B681AFA00018449 /* main.m */; }; + EBA3E2891B681AFA00018449 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = EBA3E2881B681AFA00018449 /* AppDelegate.m */; }; + EBA3E28C1B681AFA00018449 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = EBA3E28B1B681AFA00018449 /* ViewController.m */; }; + EBA3E28F1B681AFA00018449 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EBA3E28D1B681AFA00018449 /* Main.storyboard */; }; + EBA3E2941B681AFA00018449 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = EBA3E2921B681AFA00018449 /* LaunchScreen.xib */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + EBA3E2801B681AFA00018449 /* bind.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = bind.app; sourceTree = BUILT_PRODUCTS_DIR; }; + EBA3E2841B681AFA00018449 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + EBA3E2851B681AFA00018449 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + EBA3E2871B681AFA00018449 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + EBA3E2881B681AFA00018449 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + EBA3E28A1B681AFA00018449 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + EBA3E28B1B681AFA00018449 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + EBA3E28E1B681AFA00018449 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + EBA3E2931B681AFA00018449 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + EBA3E27D1B681AFA00018449 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + EBA3E2771B681AFA00018449 = { + isa = PBXGroup; + children = ( + EBA3E2821B681AFA00018449 /* bind */, + EBA3E2811B681AFA00018449 /* Products */, + ); + sourceTree = ""; + }; + EBA3E2811B681AFA00018449 /* Products */ = { + isa = PBXGroup; + children = ( + EBA3E2801B681AFA00018449 /* bind.app */, + ); + name = Products; + sourceTree = ""; + }; + EBA3E2821B681AFA00018449 /* bind */ = { + isa = PBXGroup; + children = ( + EBA3E2871B681AFA00018449 /* AppDelegate.h */, + EBA3E2881B681AFA00018449 /* AppDelegate.m */, + EBA3E28A1B681AFA00018449 /* ViewController.h */, + EBA3E28B1B681AFA00018449 /* ViewController.m */, + EBA3E28D1B681AFA00018449 /* Main.storyboard */, + EBA3E2921B681AFA00018449 /* LaunchScreen.xib */, + EBA3E2831B681AFA00018449 /* Supporting Files */, + ); + path = bind; + sourceTree = ""; + }; + EBA3E2831B681AFA00018449 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + EBA3E2841B681AFA00018449 /* Info.plist */, + EBA3E2851B681AFA00018449 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + EBA3E27F1B681AFA00018449 /* bind */ = { + isa = PBXNativeTarget; + buildConfigurationList = EBA3E2A31B681AFB00018449 /* Build configuration list for PBXNativeTarget "bind" */; + buildPhases = ( + EBA3E27C1B681AFA00018449 /* Sources */, + EBA3E27D1B681AFA00018449 /* Frameworks */, + EBA3E27E1B681AFA00018449 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = bind; + productName = bind; + productReference = EBA3E2801B681AFA00018449 /* bind.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + EBA3E2781B681AFA00018449 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0630; + ORGANIZATIONNAME = "Hana (Hyang-Ah) Kim"; + TargetAttributes = { + EBA3E27F1B681AFA00018449 = { + CreatedOnToolsVersion = 6.3.2; + }; + }; + }; + buildConfigurationList = EBA3E27B1B681AFA00018449 /* Build configuration list for PBXProject "bind" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = EBA3E2771B681AFA00018449; + productRefGroup = EBA3E2811B681AFA00018449 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + EBA3E27F1B681AFA00018449 /* bind */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + EBA3E27E1B681AFA00018449 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EBA3E28F1B681AFA00018449 /* Main.storyboard in Resources */, + EBA3E2941B681AFA00018449 /* LaunchScreen.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + EBA3E27C1B681AFA00018449 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EBA3E28C1B681AFA00018449 /* ViewController.m in Sources */, + EBA3E2891B681AFA00018449 /* AppDelegate.m in Sources */, + EBA3E2861B681AFA00018449 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + EBA3E28D1B681AFA00018449 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + EBA3E28E1B681AFA00018449 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + EBA3E2921B681AFA00018449 /* LaunchScreen.xib */ = { + isa = PBXVariantGroup; + children = ( + EBA3E2931B681AFA00018449 /* Base */, + ); + name = LaunchScreen.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + EBA3E2A11B681AFB00018449 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.3; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + EBA3E2A21B681AFB00018449 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.3; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + EBA3E2A41B681AFB00018449 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + INFOPLIST_FILE = bind/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + EBA3E2A51B681AFB00018449 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + INFOPLIST_FILE = bind/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + EBA3E27B1B681AFA00018449 /* Build configuration list for PBXProject "bind" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + EBA3E2A11B681AFB00018449 /* Debug */, + EBA3E2A21B681AFB00018449 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + EBA3E2A31B681AFB00018449 /* Build configuration list for PBXNativeTarget "bind" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + EBA3E2A41B681AFB00018449 /* Debug */, + EBA3E2A51B681AFB00018449 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = EBA3E2781B681AFA00018449 /* Project object */; +} diff --git a/src/golang.org/x/mobile/example/bind/ios/bind/AppDelegate.h b/src/golang.org/x/mobile/example/bind/ios/bind/AppDelegate.h new file mode 100644 index 0000000000..642915d17e --- /dev/null +++ b/src/golang.org/x/mobile/example/bind/ios/bind/AppDelegate.h @@ -0,0 +1,10 @@ +// Copyright 2015 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. + +#import + +@interface AppDelegate : UIResponder +@property (strong, nonatomic) UIWindow *window; +@end + diff --git a/src/golang.org/x/mobile/example/bind/ios/bind/AppDelegate.m b/src/golang.org/x/mobile/example/bind/ios/bind/AppDelegate.m new file mode 100644 index 0000000000..e10ee2d150 --- /dev/null +++ b/src/golang.org/x/mobile/example/bind/ios/bind/AppDelegate.m @@ -0,0 +1,16 @@ +// Copyright 2015 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. + +#import "AppDelegate.h" + +@interface AppDelegate () +@end + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + return YES; +} + +@end diff --git a/src/golang.org/x/mobile/example/bind/ios/bind/Base.lproj/LaunchScreen.xib b/src/golang.org/x/mobile/example/bind/ios/bind/Base.lproj/LaunchScreen.xib new file mode 100644 index 0000000000..6ed6eb67c7 --- /dev/null +++ b/src/golang.org/x/mobile/example/bind/ios/bind/Base.lproj/LaunchScreen.xib @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/golang.org/x/mobile/example/bind/ios/bind/Base.lproj/Main.storyboard b/src/golang.org/x/mobile/example/bind/ios/bind/Base.lproj/Main.storyboard new file mode 100644 index 0000000000..9b035c5cc1 --- /dev/null +++ b/src/golang.org/x/mobile/example/bind/ios/bind/Base.lproj/Main.storyboard @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/golang.org/x/mobile/example/bind/ios/bind/Info.plist b/src/golang.org/x/mobile/example/bind/ios/bind/Info.plist new file mode 100644 index 0000000000..fa5adf404e --- /dev/null +++ b/src/golang.org/x/mobile/example/bind/ios/bind/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + org.golang.example.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/src/golang.org/x/mobile/example/bind/ios/bind/ViewController.h b/src/golang.org/x/mobile/example/bind/ios/bind/ViewController.h new file mode 100644 index 0000000000..4b38773676 --- /dev/null +++ b/src/golang.org/x/mobile/example/bind/ios/bind/ViewController.h @@ -0,0 +1,9 @@ +// Copyright 2015 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. + +#import + +@interface ViewController : UIViewController +@property (strong, nonatomic) IBOutlet UILabel *textLabel; +@end diff --git a/src/golang.org/x/mobile/example/bind/ios/bind/ViewController.m b/src/golang.org/x/mobile/example/bind/ios/bind/ViewController.m new file mode 100644 index 0000000000..0ef836b744 --- /dev/null +++ b/src/golang.org/x/mobile/example/bind/ios/bind/ViewController.m @@ -0,0 +1,20 @@ +// Copyright 2015 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. + +#import "ViewController.h" +#import "hello/Hello.h" // Gomobile bind generated header file in hello.framework + +@interface ViewController () +@end + +@implementation ViewController + +@synthesize textLabel; + +- (void)loadView { + [super loadView]; + textLabel.text = GoHelloGreetings(@"iOS and Gopher"); +} + +@end diff --git a/src/golang.org/x/mobile/example/bind/ios/bind/main.m b/src/golang.org/x/mobile/example/bind/ios/bind/main.m new file mode 100644 index 0000000000..c9dcc2df87 --- /dev/null +++ b/src/golang.org/x/mobile/example/bind/ios/bind/main.m @@ -0,0 +1,12 @@ +// Copyright 2015 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. + +#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/src/golang.org/x/mobile/example/network/AndroidManifest.xml b/src/golang.org/x/mobile/example/network/AndroidManifest.xml new file mode 100644 index 0000000000..312b4f3e7a --- /dev/null +++ b/src/golang.org/x/mobile/example/network/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/golang.org/x/mobile/example/network/main.go b/src/golang.org/x/mobile/example/network/main.go new file mode 100644 index 0000000000..dd409940f1 --- /dev/null +++ b/src/golang.org/x/mobile/example/network/main.go @@ -0,0 +1,99 @@ +// Copyright 2015 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 darwin linux + +// An app that paints green if golang.org is reachable when the app first +// starts, or red otherwise. +// +// In order to access the network from the Android app, its AndroidManifest.xml +// file must include the permission to access the network. +// +// http://developer.android.com/guide/topics/manifest/manifest-intro.html#perms +// +// The gomobile tool auto-generates a default AndroidManifest file by default +// unless the package directory contains the AndroidManifest.xml. Users can +// customize app behavior, such as permissions and app name, by providing +// the AndroidManifest file. This is irrelevent to iOS. +// +// Note: This demo is an early preview of Go 1.5. In order to build this +// program as an Android APK using the gomobile tool. +// +// See http://godoc.org/golang.org/x/mobile/cmd/gomobile to install gomobile. +// +// Get the network example and use gomobile to build or install it on your device. +// +// $ go get -d golang.org/x/mobile/example/network +// $ gomobile build golang.org/x/mobile/example/network # will build an APK +// +// # plug your Android device to your computer or start an Android emulator. +// # if you have adb installed on your machine, use gomobile install to +// # build and deploy the APK to an Android target. +// $ gomobile install golang.org/x/mobile/example/network +// +// Switch to your device or emulator to start the network application from +// the launcher. +// You can also run the application on your desktop by running the command +// below. (Note: It currently doesn't work on Windows.) +// $ go install golang.org/x/mobile/example/network && network +package main + +import ( + "net/http" + + "golang.org/x/mobile/app" + "golang.org/x/mobile/event/config" + "golang.org/x/mobile/event/paint" + "golang.org/x/mobile/exp/app/debug" + "golang.org/x/mobile/gl" +) + +func main() { + // checkNetwork runs only once when the app first loads. + go checkNetwork() + + app.Main(func(a app.App) { + var c config.Event + for e := range a.Events() { + switch e := app.Filter(e).(type) { + case config.Event: + c = e + case paint.Event: + onDraw(c) + a.EndPaint(e) + } + } + }) +} + +var ( + determined = make(chan struct{}) + ok = false +) + +func checkNetwork() { + defer close(determined) + + _, err := http.Get("http://golang.org/") + if err != nil { + return + } + ok = true +} + +func onDraw(c config.Event) { + select { + case <-determined: + if ok { + gl.ClearColor(0, 1, 0, 1) + } else { + gl.ClearColor(1, 0, 0, 1) + } + default: + gl.ClearColor(0, 0, 0, 1) + } + gl.Clear(gl.COLOR_BUFFER_BIT) + + debug.DrawFPS(c) +} diff --git a/src/golang.org/x/mobile/example/sprite/assets/waza-gophers.jpeg b/src/golang.org/x/mobile/example/sprite/assets/waza-gophers.jpeg new file mode 100644 index 0000000000..17db10f3c0 Binary files /dev/null and b/src/golang.org/x/mobile/example/sprite/assets/waza-gophers.jpeg differ diff --git a/src/golang.org/x/mobile/example/sprite/main.go b/src/golang.org/x/mobile/example/sprite/main.go new file mode 100644 index 0000000000..cdab6b8436 --- /dev/null +++ b/src/golang.org/x/mobile/example/sprite/main.go @@ -0,0 +1,172 @@ +// Copyright 2014 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 darwin linux + +// An app that demonstrates the sprite package. +// +// Note: This demo is an early preview of Go 1.5. In order to build this +// program as an Android APK using the gomobile tool. +// +// See http://godoc.org/golang.org/x/mobile/cmd/gomobile to install gomobile. +// +// Get the sprite example and use gomobile to build or install it on your device. +// +// $ go get -d golang.org/x/mobile/example/sprite +// $ gomobile build golang.org/x/mobile/example/sprite # will build an APK +// +// # plug your Android device to your computer or start an Android emulator. +// # if you have adb installed on your machine, use gomobile install to +// # build and deploy the APK to an Android target. +// $ gomobile install golang.org/x/mobile/example/sprite +// +// Switch to your device or emulator to start the Basic application from +// the launcher. +// You can also run the application on your desktop by running the command +// below. (Note: It currently doesn't work on Windows.) +// $ go install golang.org/x/mobile/example/sprite && sprite +package main + +import ( + "image" + "log" + "math" + "time" + + _ "image/jpeg" + + "golang.org/x/mobile/app" + "golang.org/x/mobile/asset" + "golang.org/x/mobile/event/config" + "golang.org/x/mobile/event/paint" + "golang.org/x/mobile/exp/app/debug" + "golang.org/x/mobile/exp/f32" + "golang.org/x/mobile/exp/sprite" + "golang.org/x/mobile/exp/sprite/clock" + "golang.org/x/mobile/exp/sprite/glsprite" + "golang.org/x/mobile/gl" +) + +var ( + startTime = time.Now() + eng = glsprite.Engine() + scene *sprite.Node +) + +func main() { + app.Main(func(a app.App) { + var c config.Event + for e := range a.Events() { + switch e := app.Filter(e).(type) { + case config.Event: + c = e + case paint.Event: + onPaint(c) + a.EndPaint(e) + } + } + }) +} + +func onPaint(c config.Event) { + if scene == nil { + loadScene() + } + gl.ClearColor(1, 1, 1, 1) + gl.Clear(gl.COLOR_BUFFER_BIT) + now := clock.Time(time.Since(startTime) * 60 / time.Second) + eng.Render(scene, now, c) + debug.DrawFPS(c) +} + +func newNode() *sprite.Node { + n := &sprite.Node{} + eng.Register(n) + scene.AppendChild(n) + return n +} + +func loadScene() { + texs := loadTextures() + scene = &sprite.Node{} + eng.Register(scene) + eng.SetTransform(scene, f32.Affine{ + {1, 0, 0}, + {0, 1, 0}, + }) + + var n *sprite.Node + + n = newNode() + eng.SetSubTex(n, texs[texBooks]) + eng.SetTransform(n, f32.Affine{ + {36, 0, 0}, + {0, 36, 0}, + }) + + n = newNode() + eng.SetSubTex(n, texs[texFire]) + eng.SetTransform(n, f32.Affine{ + {72, 0, 144}, + {0, 72, 144}, + }) + + n = newNode() + n.Arranger = arrangerFunc(func(eng sprite.Engine, n *sprite.Node, t clock.Time) { + // TODO: use a tweening library instead of manually arranging. + t0 := uint32(t) % 120 + if t0 < 60 { + eng.SetSubTex(n, texs[texGopherR]) + } else { + eng.SetSubTex(n, texs[texGopherL]) + } + + u := float32(t0) / 120 + u = (1 - f32.Cos(u*2*math.Pi)) / 2 + + tx := 18 + u*48 + ty := 36 + u*108 + sx := 36 + u*36 + sy := 36 + u*36 + eng.SetTransform(n, f32.Affine{ + {sx, 0, tx}, + {0, sy, ty}, + }) + }) +} + +const ( + texBooks = iota + texFire + texGopherR + texGopherL +) + +func loadTextures() []sprite.SubTex { + a, err := asset.Open("waza-gophers.jpeg") + if err != nil { + log.Fatal(err) + } + defer a.Close() + + img, _, err := image.Decode(a) + if err != nil { + log.Fatal(err) + } + t, err := eng.LoadTexture(img) + if err != nil { + log.Fatal(err) + } + + return []sprite.SubTex{ + texBooks: sprite.SubTex{t, image.Rect(4, 71, 132, 182)}, + texFire: sprite.SubTex{t, image.Rect(330, 56, 440, 155)}, + texGopherR: sprite.SubTex{t, image.Rect(152, 10, 152+140, 10+90)}, + texGopherL: sprite.SubTex{t, image.Rect(162, 120, 162+140, 120+90)}, + } +} + +type arrangerFunc func(e sprite.Engine, n *sprite.Node, t clock.Time) + +func (a arrangerFunc) Arrange(e sprite.Engine, n *sprite.Node, t clock.Time) { a(e, n, t) } diff --git a/src/golang.org/x/mobile/exp/README b/src/golang.org/x/mobile/exp/README new file mode 100644 index 0000000000..c70d499980 --- /dev/null +++ b/src/golang.org/x/mobile/exp/README @@ -0,0 +1,2 @@ +golang.org/x/mobile/exp contains experimental packages for mobile app +development. diff --git a/src/golang.org/x/mobile/exp/app/debug/fps.go b/src/golang.org/x/mobile/exp/app/debug/fps.go new file mode 100644 index 0000000000..b45801b3a3 --- /dev/null +++ b/src/golang.org/x/mobile/exp/app/debug/fps.go @@ -0,0 +1,198 @@ +// Copyright 2014 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. + +// Package debug provides GL-based debugging tools for apps. +package debug // import "golang.org/x/mobile/exp/app/debug" + +import ( + "image" + "image/color" + "image/draw" + "sync" + "time" + + "golang.org/x/mobile/event/config" + "golang.org/x/mobile/exp/gl/glutil" + "golang.org/x/mobile/geom" +) + +var lastDraw = time.Now() + +var fps struct { + mu sync.Mutex + c config.Event + m *glutil.Image +} + +// DrawFPS draws the per second framerate in the bottom-left of the screen. +func DrawFPS(c config.Event) { + const imgW, imgH = 7*(fontWidth+1) + 1, fontHeight + 2 + + fps.mu.Lock() + if fps.c != c || fps.m == nil { + fps.c = c + fps.m = glutil.NewImage(imgW, imgH) + } + fps.mu.Unlock() + + display := [7]byte{ + 4: 'F', + 5: 'P', + 6: 'S', + } + now := time.Now() + f := 0 + if dur := now.Sub(lastDraw); dur > 0 { + f = int(time.Second / dur) + } + display[2] = '0' + byte((f/1e0)%10) + display[1] = '0' + byte((f/1e1)%10) + display[0] = '0' + byte((f/1e2)%10) + draw.Draw(fps.m.RGBA, fps.m.RGBA.Bounds(), image.White, image.Point{}, draw.Src) + for i, c := range display { + glyph := glyphs[c] + if len(glyph) != fontWidth*fontHeight { + continue + } + for y := 0; y < fontHeight; y++ { + for x := 0; x < fontWidth; x++ { + if glyph[fontWidth*y+x] == ' ' { + continue + } + fps.m.RGBA.SetRGBA((fontWidth+1)*i+x+1, y+1, color.RGBA{A: 0xff}) + } + } + } + + fps.m.Upload() + fps.m.Draw( + c, + geom.Point{0, c.HeightPt - imgH}, + geom.Point{imgW, c.HeightPt - imgH}, + geom.Point{0, c.HeightPt}, + fps.m.RGBA.Bounds(), + ) + + lastDraw = now +} + +const ( + fontWidth = 5 + fontHeight = 7 +) + +// glyphs comes from the 6x10 fixed font from the plan9port: +// https://github.com/9fans/plan9port/tree/master/font/fixed +// +// 6x10 becomes 5x7 because each glyph has a 1-pixel margin plus space for +// descenders. +// +// Its README file says that those fonts were converted from XFree86, and are +// in the public domain. +var glyphs = [256]string{ + '0': "" + + " X " + + " X X " + + "X X" + + "X X" + + "X X" + + " X X " + + " X ", + '1': "" + + " X " + + " XX " + + "X X " + + " X " + + " X " + + " X " + + "XXXXX", + '2': "" + + " XXX " + + "X X" + + " X" + + " XX " + + " X " + + "X " + + "XXXXX", + '3': "" + + "XXXXX" + + " X" + + " X " + + " XX " + + " X" + + "X X" + + " XXX ", + '4': "" + + " X " + + " XX " + + " X X " + + "X X " + + "XXXXX" + + " X " + + " X ", + '5': "" + + "XXXXX" + + "X " + + "X XX " + + "XX X" + + " X" + + "X X" + + " XXX ", + '6': "" + + " XX " + + " X " + + "X " + + "X XX " + + "XX X" + + "X X" + + " XXX ", + '7': "" + + "XXXXX" + + " X" + + " X " + + " X " + + " X " + + " X " + + " X ", + '8': "" + + " XXX " + + "X X" + + "X X" + + " XXX " + + "X X" + + "X X" + + " XXX ", + '9': "" + + " XXX " + + "X X" + + "X XX" + + " XX X" + + " X" + + " X " + + " XX ", + 'F': "" + + "XXXXX" + + "X " + + "X " + + "XXXX " + + "X " + + "X " + + "X ", + 'P': "" + + "XXXX " + + "X X" + + "X X" + + "XXXX " + + "X " + + "X " + + "X ", + 'S': "" + + " XXX " + + "X X" + + "X " + + " XXX " + + " X" + + "X X" + + " XXX ", +} diff --git a/src/golang.org/x/mobile/exp/audio/al/al.go b/src/golang.org/x/mobile/exp/audio/al/al.go new file mode 100644 index 0000000000..34de1b665c --- /dev/null +++ b/src/golang.org/x/mobile/exp/audio/al/al.go @@ -0,0 +1,419 @@ +// Copyright 2015 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 darwin linux + +// Package al provides OpenAL Soft bindings for Go. +// +// Calls are not safe for concurrent use. +// +// More information about OpenAL Soft is available at +// http://www.openal.org/documentation/openal-1.1-specification.pdf. +// +// In order to use this package on Linux desktop distros, +// you will need OpenAL library as an external dependency. +// On Ubuntu 14.04 'Trusty', you may have to install this library +// by running the command below. +// +// sudo apt-get install libopenal-dev +// +// When compiled for Android, this package uses OpenAL Soft. Please add its +// license file to the open source notices of your application. +// OpenAL Soft's license file could be found at +// http://repo.or.cz/w/openal-soft.git/blob/HEAD:/COPYING. +package al // import "golang.org/x/mobile/exp/audio/al" + +// Enable enables a capability. +func Enable(capability int32) { + alEnable(capability) +} + +// Disable disables a capability. +func Disable(capability int32) { + alDisable(capability) +} + +// Enabled returns true if the specified capability is enabled. +func Enabled(capability int32) bool { + return alIsEnabled(capability) +} + +// Vector represents an vector in a Cartesian coordinate system. +type Vector [3]float32 + +// Orientation represents the angular position of an object in a +// right-handed Cartesian coordinate system. +// A cross product between the forward and up vector returns a vector +// that points to the right. +type Orientation struct { + // Forward vector is the direction that the object is looking at. + Forward Vector + // Up vector represents the rotation of the object. + Up Vector +} + +func orientationFromSlice(v []float32) Orientation { + return Orientation{ + Forward: Vector{v[0], v[1], v[2]}, + Up: Vector{v[3], v[4], v[5]}, + } +} + +func (v Orientation) slice() []float32 { + return []float32{v.Forward[0], v.Forward[1], v.Forward[2], v.Up[0], v.Up[1], v.Up[2]} +} + +func geti(param int) int32 { + return alGetInteger(param) +} + +func getf(param int) float32 { + return alGetFloat(param) +} + +func getString(param int) string { + return alGetString(param) +} + +// DistanceModel returns the distance model. +func DistanceModel() int32 { + return geti(paramDistanceModel) +} + +// SetDistanceModel sets the distance model. +func SetDistanceModel(v int32) { + alDistanceModel(v) +} + +// DopplerFactor returns the doppler factor. +func DopplerFactor() float32 { + return getf(paramDopplerFactor) +} + +// SetDopplerFactor sets the doppler factor. +func SetDopplerFactor(v float32) { + alDopplerFactor(v) +} + +// DopplerVelocity returns the doppler velocity. +func DopplerVelocity() float32 { + return getf(paramDopplerVelocity) +} + +// SetDopplerVelocity sets the doppler velocity. +func SetDopplerVelocity(v float32) { + alDopplerVelocity(v) +} + +// SpeedOfSound is the speed of sound in meters per second (m/s). +func SpeedOfSound() float32 { + return getf(paramSpeedOfSound) +} + +// SetSpeedOfSound sets the speed of sound, its unit should be meters per second (m/s). +func SetSpeedOfSound(v float32) { + alSpeedOfSound(v) +} + +// Vendor returns the vendor. +func Vendor() string { + return getString(paramVendor) +} + +// Version returns the version string. +func Version() string { + return getString(paramVersion) +} + +// Renderer returns the renderer information. +func Renderer() string { + return getString(paramRenderer) +} + +// Extensions returns the enabled extensions. +func Extensions() string { + return getString(paramExtensions) +} + +// Error returns the most recently generated error. +func Error() int32 { + return alGetError() +} + +// Source represents an individual sound source in 3D-space. +// They take PCM data, apply modifications and then submit them to +// be mixed according to their spatial location. +type Source uint32 + +// GenSources generates n new sources. These sources should be deleted +// once they are not in use. +func GenSources(n int) []Source { + return alGenSources(n) +} + +// PlaySources plays the sources. +func PlaySources(source ...Source) { + alSourcePlayv(source) +} + +// PauseSources pauses the sources. +func PauseSources(source ...Source) { + alSourcePausev(source) +} + +// StopSources stops the sources. +func StopSources(source ...Source) { + alSourceStopv(source) +} + +// RewindSources rewinds the sources to their beginning positions. +func RewindSources(source ...Source) { + alSourceRewindv(source) +} + +// DeleteSources deletes the sources. +func DeleteSources(source ...Source) { + alDeleteSources(source) +} + +// Gain returns the source gain. +func (s Source) Gain() float32 { + return getSourcef(s, paramGain) +} + +// SetGain sets the source gain. +func (s Source) SetGain(v float32) { + setSourcef(s, paramGain, v) +} + +// MinGain returns the source's minimum gain setting. +func (s Source) MinGain() float32 { + return getSourcef(s, paramMinGain) +} + +// SetMinGain sets the source's minimum gain setting. +func (s Source) SetMinGain(v float32) { + setSourcef(s, paramMinGain, v) +} + +// MaxGain returns the source's maximum gain setting. +func (s Source) MaxGain() float32 { + return getSourcef(s, paramMaxGain) +} + +// SetMaxGain sets the source's maximum gain setting. +func (s Source) SetMaxGain(v float32) { + setSourcef(s, paramMaxGain, v) +} + +// Position returns the position of the source. +func (s Source) Position() Vector { + v := Vector{} + getSourcefv(s, paramPosition, v[:]) + return v +} + +// SetPosition sets the position of the source. +func (s Source) SetPosition(v Vector) { + setSourcefv(s, paramPosition, v[:]) +} + +// Velocity returns the source's velocity. +func (s Source) Velocity() Vector { + v := Vector{} + getSourcefv(s, paramVelocity, v[:]) + return v +} + +// SetVelocity sets the source's velocity. +func (s Source) SetVelocity(v Vector) { + setSourcefv(s, paramVelocity, v[:]) +} + +// Orientation returns the orientation of the source. +func (s Source) Orientation() Orientation { + v := make([]float32, 6) + getSourcefv(s, paramOrientation, v) + return orientationFromSlice(v) +} + +// SetOrientation sets the orientation of the source. +func (s Source) SetOrientation(o Orientation) { + setSourcefv(s, paramOrientation, o.slice()) +} + +// State returns the playing state of the source. +func (s Source) State() int32 { + return getSourcei(s, paramSourceState) +} + +// BuffersQueued returns the number of the queued buffers. +func (s Source) BuffersQueued() int32 { + return getSourcei(s, paramBuffersQueued) +} + +// BuffersProcessed returns the number of the processed buffers. +func (s Source) BuffersProcessed() int32 { + return getSourcei(s, paramBuffersProcessed) +} + +// OffsetSeconds returns the current playback position of the source in seconds. +func (s Source) OffsetSeconds() int32 { + return getSourcei(s, paramSecOffset) +} + +// OffsetSample returns the sample offset of the current playback position. +func (s Source) OffsetSample() int32 { + return getSourcei(s, paramSampleOffset) +} + +// OffsetByte returns the byte offset of the current playback position. +func (s Source) OffsetByte() int32 { + return getSourcei(s, paramByteOffset) +} + +func getSourcei(s Source, param int) int32 { + return alGetSourcei(s, param) +} + +func getSourcef(s Source, param int) float32 { + return alGetSourcef(s, param) +} + +func getSourcefv(s Source, param int, v []float32) { + alGetSourcefv(s, param, v) +} + +func setSourcei(s Source, param int, v int32) { + alSourcei(s, param, v) +} + +func setSourcef(s Source, param int, v float32) { + alSourcef(s, param, v) +} + +func setSourcefv(s Source, param int, v []float32) { + alSourcefv(s, param, v) +} + +// QueueBuffers adds the buffers to the buffer queue. +func (s Source) QueueBuffers(buffers []Buffer) { + alSourceQueueBuffers(s, buffers) +} + +// UnqueueBuffers removes the specified buffers from the buffer queue. +func (s Source) UnqueueBuffers(buffers []Buffer) { + alSourceUnqueueBuffers(s, buffers) +} + +// ListenerGain returns the total gain applied to the final mix. +func ListenerGain() float32 { + return getListenerf(paramGain) +} + +// ListenerPosition returns the position of the listener. +func ListenerPosition() Vector { + v := Vector{} + getListenerfv(paramPosition, v[:]) + return v +} + +// ListenerVelocity returns the velocity of the listener. +func ListenerVelocity() Vector { + v := Vector{} + getListenerfv(paramVelocity, v[:]) + return v +} + +// ListenerOrientation returns the orientation of the listener. +func ListenerOrientation() Orientation { + v := make([]float32, 6) + getListenerfv(paramOrientation, v) + return orientationFromSlice(v) +} + +// SetListenerGain sets the total gain that will be applied to the final mix. +func SetListenerGain(v float32) { + setListenerf(paramGain, v) +} + +// SetListenerPosition sets the position of the listener. +func SetListenerPosition(v Vector) { + setListenerfv(paramPosition, v[:]) +} + +// SetListenerVelocity sets the velocity of the listener. +func SetListenerVelocity(v Vector) { + setListenerfv(paramVelocity, v[:]) +} + +// SetListenerOrientation sets the orientation of the listener. +func SetListenerOrientation(v Orientation) { + setListenerfv(paramOrientation, v.slice()) +} + +func getListenerf(param int) float32 { + return alGetListenerf(param) +} + +func getListenerfv(param int, v []float32) { + alGetListenerfv(param, v) +} + +func setListenerf(param int, v float32) { + alListenerf(param, v) +} + +func setListenerfv(param int, v []float32) { + alListenerfv(param, v) +} + +// A buffer represents a chunk of PCM audio data that could be buffered to an audio +// source. A single buffer could be shared between multiple sources. +type Buffer uint32 + +// GenBuffers generates n new buffers. The generated buffers should be deleted +// once they are no longer in use. +func GenBuffers(n int) []Buffer { + return alGenBuffers(n) +} + +// DeleteBuffers deletes the buffers. +func DeleteBuffers(buffers []Buffer) { + alDeleteBuffers(buffers) +} + +func getBufferi(b Buffer, param int) int32 { + return alGetBufferi(b, param) +} + +// Frequency returns the frequency of the buffer data in Hertz (Hz). +func (b Buffer) Frequency() int32 { + return getBufferi(b, paramFreq) +} + +// Bits return the number of bits used to represent a sample. +func (b Buffer) Bits() int32 { + return getBufferi(b, paramBits) +} + +// Channels return the number of the audio channels. +func (b Buffer) Channels() int32 { + return getBufferi(b, paramChannels) +} + +// Size returns the size of the data. +func (b Buffer) Size() int32 { + return getBufferi(b, paramSize) +} + +// BufferData buffers PCM data to the current buffer. +func (b Buffer) BufferData(format uint32, data []byte, freq int32) { + alBufferData(b, format, data, freq) +} + +// Valid returns true if the buffer exists and is valid. +func (b Buffer) Valid() bool { + return alIsBuffer(b) +} diff --git a/src/golang.org/x/mobile/exp/audio/al/al_android.go b/src/golang.org/x/mobile/exp/audio/al/al_android.go new file mode 100644 index 0000000000..a076a9cadf --- /dev/null +++ b/src/golang.org/x/mobile/exp/audio/al/al_android.go @@ -0,0 +1,465 @@ +// Copyright 2015 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. + +package al + +/* +#include +#include +#include +#include +#include +#include +#include + +typedef enum { + AL_INIT_RESULT_OK, + AL_INIT_RESULT_CANNOT_ATTACH_JVM, + AL_INIT_RESULT_BAD_JNI_VERSION, + AL_INIT_RESULT_CANNOT_LOAD_SO +} ALInitResult; + +ALInitResult al_init(void* vm, void* context, void** handle) { + JavaVM* current_vm = (JavaVM*)(vm); + JNIEnv* env; + + int attached = 0; + switch ((*current_vm)->GetEnv(current_vm, (void**)&env, JNI_VERSION_1_6)) { + case JNI_OK: + break; + case JNI_EDETACHED: + if ((*current_vm)->AttachCurrentThread(current_vm, &env, 0) != 0) { + return AL_INIT_RESULT_CANNOT_ATTACH_JVM; + } + attached = 1; + break; + case JNI_EVERSION: + return AL_INIT_RESULT_BAD_JNI_VERSION; + } + + jclass android_content_Context = (*env)->FindClass(env, "android/content/Context"); + jmethodID get_package_name = (*env)->GetMethodID(env, android_content_Context, "getPackageName", "()Ljava/lang/String;"); + jstring package_name = (*env)->CallObjectMethod(env, context, get_package_name); + const char *cpackage_name = (*env)->GetStringUTFChars(env, package_name, 0); + + char lib_path[PATH_MAX] = "/data/data/"; + strlcat(lib_path, cpackage_name, sizeof(lib_path)); + strlcat(lib_path, "/lib/libopenal.so", sizeof(lib_path)); + *handle = dlopen(lib_path, RTLD_LAZY); + (*env)->ReleaseStringUTFChars(env, package_name, cpackage_name); + if (attached) { + (*current_vm)->DetachCurrentThread(current_vm); + } + if (!*handle) { + return AL_INIT_RESULT_CANNOT_LOAD_SO; + } + return AL_INIT_RESULT_OK; +} + +void call_alEnable(LPALENABLE fn, ALenum capability) { + fn(capability); +} + +void call_alDisable(LPALDISABLE fn, ALenum capability) { + fn(capability); +} + +ALboolean call_alIsEnabled(LPALISENABLED fn, ALenum capability) { + return fn(capability); +} + +ALint call_alGetInteger(LPALGETINTEGER fn, ALenum p) { + return fn(p); +} + +ALfloat call_alGetFloat(LPALGETFLOAT fn, ALenum p) { + return fn(p); +} + +const char* call_alGetString(LPALGETSTRING fn, ALenum p) { + return fn(p); +} + +void call_alDistanceModel(LPALDISTANCEMODEL fn, ALenum v) { + fn(v); +} + +void call_alDopplerFactor(LPALDOPPLERFACTOR fn, ALfloat v) { + fn(v); +} + +void call_alDopplerVelocity(LPALDOPPLERVELOCITY fn, ALfloat v) { + fn(v); +} + +void call_alSpeedOfSound(LPALSPEEDOFSOUND fn, ALfloat v) { + fn(v); +} + +ALint call_alGetError(LPALGETERROR fn) { + return fn(); +} + +void call_alGenSources(LPALGENSOURCES fn, ALsizei n, ALuint* s) { + fn(n, s); +} + +void call_alSourcePlayv(LPALSOURCEPLAYV fn, ALsizei n, const ALuint* s) { + fn(n, s); +} + +void call_alSourcePausev(LPALSOURCEPAUSEV fn, ALsizei n, const ALuint* s) { + fn(n, s); +} + +void call_alSourceStopv(LPALSOURCESTOPV fn, ALsizei n, const ALuint* s) { + fn(n, s); +} + +void call_alSourceRewindv(LPALSOURCEREWINDV fn, ALsizei n, const ALuint* s) { + fn(n, s); +} + +void call_alDeleteSources(LPALDELETESOURCES fn, ALsizei n, const ALuint* s) { + fn(n, s); +} + +void call_alGetSourcei(LPALGETSOURCEI fn, ALuint s, ALenum k, ALint* v) { + fn(s, k, v); +} + +void call_alGetSourcef(LPALGETSOURCEF fn, ALuint s, ALenum k, ALfloat* v) { + fn(s, k, v); +} + +void call_alGetSourcefv(LPALGETSOURCEFV fn, ALuint s, ALenum k, ALfloat* v) { + fn(s, k, v); +} + +void call_alSourcei(LPALSOURCEI fn, ALuint s, ALenum k, ALint v) { + fn(s, k, v); +} + +void call_alSourcef(LPALSOURCEF fn, ALuint s, ALenum k, ALfloat v) { + fn(s, k, v); +} + +void call_alSourcefv(LPALSOURCEFV fn, ALuint s, ALenum k, const ALfloat* v) { + fn(s, k, v); +} + +void call_alSourceQueueBuffers(LPALSOURCEQUEUEBUFFERS fn, ALuint s, ALsizei n, const ALuint* b) { + fn(s, n, b); +} + +void call_alSourceUnqueueBuffers(LPALSOURCEUNQUEUEBUFFERS fn, ALuint s, ALsizei n, ALuint* b) { + fn(s, n, b); +} + +void call_alGetListenerf(LPALGETLISTENERF fn, ALenum k, ALfloat* v) { + fn(k, v); +} + +void call_alGetListenerfv(LPALLISTENERFV fn, ALenum k, ALfloat* v) { + fn(k, v); +} + +void call_alListenerf(LPALLISTENERF fn, ALenum k, ALfloat v) { + fn(k, v); +} + +void call_alListenerfv(LPALLISTENERFV fn, ALenum k, const ALfloat* v) { + fn(k, v); +} + +void call_alGenBuffers(LPALGENBUFFERS fn, ALsizei n, ALuint* v) { + fn(n, v); +} + +void call_alDeleteBuffers(LPALDELETEBUFFERS fn, ALsizei n, ALuint* v) { + fn(n, v); +} + +void call_alGetBufferi(LPALGETBUFFERI fn, ALuint b, ALenum k, ALint* v) { + fn(b, k, v); +} + +void call_alBufferData(LPALBUFFERDATA fn, ALuint b, ALenum format, const ALvoid* data, ALsizei size, ALsizei freq) { + fn(b, format, data, size, freq); +} + +ALboolean call_alIsBuffer(LPALISBUFFER fn, ALuint b) { + return fn(b); +} +*/ +import "C" +import ( + "log" + "unsafe" + + "golang.org/x/mobile/internal/mobileinit" +) + +var ( + alHandle unsafe.Pointer + alEnableFunc C.LPALENABLE + alDisableFunc C.LPALDISABLE + alIsEnabledFunc C.LPALISENABLED + alGetIntegerFunc C.LPALGETINTEGER + alGetFloatFunc C.LPALGETFLOAT + alGetStringFunc C.LPALGETSTRING + alDistanceModelFunc C.LPALDISTANCEMODEL + alDopplerFactorFunc C.LPALDOPPLERFACTOR + alDopplerVelocityFunc C.LPALDOPPLERVELOCITY + alSpeedOfSoundFunc C.LPALSPEEDOFSOUND + alGetErrorFunc C.LPALGETERROR + alGenSourcesFunc C.LPALGENSOURCES + alSourcePlayvFunc C.LPALSOURCEPLAYV + alSourcePausevFunc C.LPALSOURCEPAUSEV + alSourceStopvFunc C.LPALSOURCESTOPV + alSourceRewindvFunc C.LPALSOURCEREWINDV + alDeleteSourcesFunc C.LPALDELETESOURCES + alGetSourceiFunc C.LPALGETSOURCEI + alGetSourcefFunc C.LPALGETSOURCEF + alGetSourcefvFunc C.LPALGETSOURCEFV + alSourceiFunc C.LPALSOURCEI + alSourcefFunc C.LPALSOURCEF + alSourcefvFunc C.LPALSOURCEFV + alSourceQueueBuffersFunc C.LPALSOURCEQUEUEBUFFERS + alSourceUnqueueBuffersFunc C.LPALSOURCEUNQUEUEBUFFERS + alGetListenerfFunc C.LPALGETLISTENERF + alGetListenerfvFunc C.LPALGETLISTENERFV + alListenerfFunc C.LPALLISTENERF + alListenerfvFunc C.LPALLISTENERFV + alGenBuffersFunc C.LPALGENBUFFERS + alDeleteBuffersFunc C.LPALDELETEBUFFERS + alGetBufferiFunc C.LPALGETBUFFERI + alBufferDataFunc C.LPALBUFFERDATA + alIsBufferFunc C.LPALISBUFFER + + alcGetErrorFunc C.LPALCGETERROR + alcOpenDeviceFunc C.LPALCOPENDEVICE + alcCloseDeviceFunc C.LPALCCLOSEDEVICE + alcCreateContextFunc C.LPALCCREATECONTEXT + alcMakeContextCurrentFunc C.LPALCMAKECONTEXTCURRENT + alcDestroyContextFunc C.LPALCDESTROYCONTEXT +) + +func initAL() { + ctx := mobileinit.Context{} + switch C.al_init(ctx.JavaVM(), ctx.AndroidContext(), &alHandle) { + case C.AL_INIT_RESULT_OK: + // No-op. + case C.AL_INIT_RESULT_CANNOT_ATTACH_JVM: + log.Fatal("al: cannot attach JVM") + case C.AL_INIT_RESULT_BAD_JNI_VERSION: + log.Fatal("al: bad JNI version") + case C.AL_INIT_RESULT_CANNOT_LOAD_SO: + log.Fatal("al: cannot load libopenal.so") + default: + log.Fatal("al: cannot initialize OpenAL library") + } + + alEnableFunc = C.LPALENABLE(fn("alEnable")) + alDisableFunc = C.LPALDISABLE(fn("alDisable")) + alIsEnabledFunc = C.LPALISENABLED(fn("alIsEnabled")) + alGetIntegerFunc = C.LPALGETINTEGER(fn("alGetInteger")) + alGetFloatFunc = C.LPALGETFLOAT(fn("alGetFloat")) + alGetStringFunc = C.LPALGETSTRING(fn("alGetString")) + alDistanceModelFunc = C.LPALDISTANCEMODEL(fn("alDistanceModel")) + alDopplerFactorFunc = C.LPALDOPPLERFACTOR(fn("alDopplerFactor")) + alDopplerVelocityFunc = C.LPALDOPPLERVELOCITY(fn("alDopplerVelocity")) + alSpeedOfSoundFunc = C.LPALSPEEDOFSOUND(fn("alSpeedOfSound")) + alGetErrorFunc = C.LPALGETERROR(fn("alGetError")) + alGenSourcesFunc = C.LPALGENSOURCES(fn("alGenSources")) + alSourcePlayvFunc = C.LPALSOURCEPLAYV(fn("alSourcePlayv")) + alSourcePausevFunc = C.LPALSOURCEPAUSEV(fn("alSourcePausev")) + alSourceStopvFunc = C.LPALSOURCESTOPV(fn("alSourceStopv")) + alSourceRewindvFunc = C.LPALSOURCEREWINDV(fn("alSourceRewindv")) + alDeleteSourcesFunc = C.LPALDELETESOURCES(fn("alDeleteSources")) + alGetSourceiFunc = C.LPALGETSOURCEI(fn("alGetSourcei")) + alGetSourcefFunc = C.LPALGETSOURCEF(fn("alGetSourcef")) + alGetSourcefvFunc = C.LPALGETSOURCEFV(fn("alGetSourcefv")) + alSourceiFunc = C.LPALSOURCEI(fn("alSourcei")) + alSourcefFunc = C.LPALSOURCEF(fn("alSourcef")) + alSourcefvFunc = C.LPALSOURCEFV(fn("alSourcefv")) + alSourceQueueBuffersFunc = C.LPALSOURCEQUEUEBUFFERS(fn("alSourceQueueBuffers")) + alSourceUnqueueBuffersFunc = C.LPALSOURCEUNQUEUEBUFFERS(fn("alSourceUnqueueBuffers")) + alGetListenerfFunc = C.LPALGETLISTENERF(fn("alGetListenerf")) + alGetListenerfvFunc = C.LPALGETLISTENERFV(fn("alGetListenerfv")) + alListenerfFunc = C.LPALLISTENERF(fn("alListenerf")) + alListenerfvFunc = C.LPALLISTENERFV(fn("alListenerfv")) + alGenBuffersFunc = C.LPALGENBUFFERS(fn("alGenBuffers")) + alDeleteBuffersFunc = C.LPALDELETEBUFFERS(fn("alDeleteBuffers")) + alGetBufferiFunc = C.LPALGETBUFFERI(fn("alGetBufferi")) + alBufferDataFunc = C.LPALBUFFERDATA(fn("alBufferData")) + alIsBufferFunc = C.LPALISBUFFER(fn("alIsBuffer")) + + alcGetErrorFunc = C.LPALCGETERROR(fn("alcGetError")) + alcOpenDeviceFunc = C.LPALCOPENDEVICE(fn("alcOpenDevice")) + alcCloseDeviceFunc = C.LPALCCLOSEDEVICE(fn("alcCloseDevice")) + alcCreateContextFunc = C.LPALCCREATECONTEXT(fn("alcCreateContext")) + alcMakeContextCurrentFunc = C.LPALCMAKECONTEXTCURRENT(fn("alcMakeContextCurrent")) + alcDestroyContextFunc = C.LPALCDESTROYCONTEXT(fn("alcDestroyContext")) +} + +func fn(fname string) unsafe.Pointer { + name := C.CString(fname) + defer C.free(unsafe.Pointer(name)) + + p := C.dlsym(alHandle, name) + if uintptr(p) == 0 { + log.Fatalf("al: couldn't dlsym %q", fname) + } + return p +} + +func alEnable(capability int32) { + C.call_alEnable(alEnableFunc, C.ALenum(capability)) +} + +func alDisable(capability int32) { + C.call_alDisable(alDisableFunc, C.ALenum(capability)) +} + +func alIsEnabled(capability int32) bool { + return C.call_alIsEnabled(alIsEnabledFunc, C.ALenum(capability)) == C.AL_TRUE +} + +func alGetInteger(k int) int32 { + return int32(C.call_alGetInteger(alGetIntegerFunc, C.ALenum(k))) +} + +func alGetFloat(k int) float32 { + return float32(C.call_alGetFloat(alGetFloatFunc, C.ALenum(k))) +} + +func alGetString(v int) string { + value := C.call_alGetString(alGetStringFunc, C.ALenum(v)) + return C.GoString(value) +} + +func alDistanceModel(v int32) { + C.call_alDistanceModel(alDistanceModelFunc, C.ALenum(v)) +} + +func alDopplerFactor(v float32) { + C.call_alDopplerFactor(alDopplerFactorFunc, C.ALfloat(v)) +} + +func alDopplerVelocity(v float32) { + C.call_alDopplerVelocity(alDopplerVelocityFunc, C.ALfloat(v)) +} + +func alSpeedOfSound(v float32) { + C.call_alSpeedOfSound(alSpeedOfSoundFunc, C.ALfloat(v)) +} + +func alGetError() int32 { + return int32(C.call_alGetError(alGetErrorFunc)) +} + +func alGenSources(n int) []Source { + s := make([]Source, n) + C.call_alGenSources(alGenSourcesFunc, C.ALsizei(n), (*C.ALuint)(unsafe.Pointer(&s[0]))) + return s +} + +func alSourcePlayv(s []Source) { + C.call_alSourcePlayv(alSourcePlayvFunc, C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0]))) +} + +func alSourcePausev(s []Source) { + C.call_alSourcePausev(alSourcePausevFunc, C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0]))) +} + +func alSourceStopv(s []Source) { + C.call_alSourceStopv(alSourceStopvFunc, C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0]))) +} + +func alSourceRewindv(s []Source) { + C.call_alSourceRewindv(alSourceRewindvFunc, C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0]))) +} + +func alDeleteSources(s []Source) { + C.call_alDeleteSources(alDeleteSourcesFunc, C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0]))) +} + +func alGetSourcei(s Source, k int) int32 { + var v C.ALint + C.call_alGetSourcei(alGetSourceiFunc, C.ALuint(s), C.ALenum(k), &v) + return int32(v) +} + +func alGetSourcef(s Source, k int) float32 { + var v C.ALfloat + C.call_alGetSourcef(alGetSourcefFunc, C.ALuint(s), C.ALenum(k), &v) + return float32(v) +} + +func alGetSourcefv(s Source, k int, v []float32) { + C.call_alGetSourcefv(alGetSourcefvFunc, C.ALuint(s), C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0]))) +} + +func alSourcei(s Source, k int, v int32) { + C.call_alSourcei(alSourcefFunc, C.ALuint(s), C.ALenum(k), C.ALint(v)) +} + +func alSourcef(s Source, k int, v float32) { + C.call_alSourcef(alSourcefFunc, C.ALuint(s), C.ALenum(k), C.ALfloat(v)) +} + +func alSourcefv(s Source, k int, v []float32) { + C.call_alSourcefv(alSourcefvFunc, C.ALuint(s), C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0]))) +} + +func alSourceQueueBuffers(s Source, b []Buffer) { + C.call_alSourceQueueBuffers(alSourceQueueBuffersFunc, C.ALuint(s), C.ALsizei(len(b)), (*C.ALuint)(unsafe.Pointer(&b[0]))) +} + +func alSourceUnqueueBuffers(s Source, b []Buffer) { + C.call_alSourceUnqueueBuffers(alSourceUnqueueBuffersFunc, C.ALuint(s), C.ALsizei(len(b)), (*C.ALuint)(unsafe.Pointer(&b[0]))) +} + +func alGetListenerf(k int) float32 { + var v C.ALfloat + C.call_alGetListenerf(alListenerfFunc, C.ALenum(k), &v) + return float32(v) +} + +func alGetListenerfv(k int, v []float32) { + C.call_alGetListenerfv(alGetListenerfvFunc, C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0]))) +} + +func alListenerf(k int, v float32) { + C.call_alListenerf(alListenerfFunc, C.ALenum(k), C.ALfloat(v)) +} + +func alListenerfv(k int, v []float32) { + C.call_alListenerfv(alListenerfvFunc, C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0]))) +} + +func alGenBuffers(n int) []Buffer { + s := make([]Buffer, n) + C.call_alGenBuffers(alGenBuffersFunc, C.ALsizei(n), (*C.ALuint)(unsafe.Pointer(&s[0]))) + return s +} + +func alDeleteBuffers(b []Buffer) { + C.call_alDeleteBuffers(alDeleteBuffersFunc, C.ALsizei(len(b)), (*C.ALuint)(unsafe.Pointer(&b[0]))) +} + +func alGetBufferi(b Buffer, k int) int32 { + var v C.ALint + C.call_alGetBufferi(alGetBufferiFunc, C.ALuint(b), C.ALenum(k), &v) + return int32(v) +} + +func alBufferData(b Buffer, format uint32, data []byte, freq int32) { + C.call_alBufferData(alBufferDataFunc, C.ALuint(b), C.ALenum(format), unsafe.Pointer(&data[0]), C.ALsizei(len(data)), C.ALsizei(freq)) +} + +func alIsBuffer(b Buffer) bool { + return C.call_alIsBuffer(alIsBufferFunc, C.ALuint(b)) == C.AL_TRUE +} diff --git a/src/golang.org/x/mobile/exp/audio/al/al_notandroid.go b/src/golang.org/x/mobile/exp/audio/al/al_notandroid.go new file mode 100644 index 0000000000..965d448610 --- /dev/null +++ b/src/golang.org/x/mobile/exp/audio/al/al_notandroid.go @@ -0,0 +1,176 @@ +// Copyright 2015 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 darwin linux,!android + +package al + +/* +#cgo darwin CFLAGS: -DGOOS_darwin +#cgo linux CFLAGS: -DGOOS_linux +#cgo darwin LDFLAGS: -framework OpenAL +#cgo linux LDFLAGS: -lopenal + +#ifdef GOOS_darwin +#include +#include +#endif + +#ifdef GOOS_linux +#include +#include +#endif +*/ +import "C" +import "unsafe" + +func alEnable(capability int32) { + C.alEnable(C.ALenum(capability)) +} + +func alDisable(capability int32) { + C.alDisable(C.ALenum(capability)) +} + +func alIsEnabled(capability int32) bool { + return C.alIsEnabled(C.ALenum(capability)) == C.AL_TRUE +} + +func alGetInteger(k int) int32 { + return int32(C.alGetInteger(C.ALenum(k))) +} + +func alGetFloat(k int) float32 { + return float32(C.alGetFloat(C.ALenum(k))) +} + +func alGetString(v int) string { + value := C.alGetString(C.ALenum(v)) + return C.GoString((*C.char)(value)) +} + +func alDistanceModel(v int32) { + C.alDistanceModel(C.ALenum(v)) +} + +func alDopplerFactor(v float32) { + C.alDopplerFactor(C.ALfloat(v)) +} + +func alDopplerVelocity(v float32) { + C.alDopplerVelocity(C.ALfloat(v)) +} + +func alSpeedOfSound(v float32) { + C.alSpeedOfSound(C.ALfloat(v)) +} + +func alGetError() int32 { + return int32(C.alGetError()) +} + +func alGenSources(n int) []Source { + s := make([]Source, n) + C.alGenSources(C.ALsizei(n), (*C.ALuint)(unsafe.Pointer(&s[0]))) + return s +} + +func alSourcePlayv(s []Source) { + C.alSourcePlayv(C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0]))) +} + +func alSourcePausev(s []Source) { + C.alSourcePausev(C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0]))) + +} + +func alSourceStopv(s []Source) { + C.alSourceStopv(C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0]))) +} + +func alSourceRewindv(s []Source) { + C.alSourceRewindv(C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0]))) +} + +func alDeleteSources(s []Source) { + C.alDeleteSources(C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0]))) +} + +func alGetSourcei(s Source, k int) int32 { + var v C.ALint + C.alGetSourcei(C.ALuint(s), C.ALenum(k), &v) + return int32(v) +} + +func alGetSourcef(s Source, k int) float32 { + var v C.ALfloat + C.alGetSourcef(C.ALuint(s), C.ALenum(k), &v) + return float32(v) +} + +func alGetSourcefv(s Source, k int, v []float32) { + C.alGetSourcefv(C.ALuint(s), C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0]))) +} + +func alSourcei(s Source, k int, v int32) { + C.alSourcei(C.ALuint(s), C.ALenum(k), C.ALint(v)) +} + +func alSourcef(s Source, k int, v float32) { + C.alSourcef(C.ALuint(s), C.ALenum(k), C.ALfloat(v)) +} + +func alSourcefv(s Source, k int, v []float32) { + C.alSourcefv(C.ALuint(s), C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0]))) +} + +func alSourceQueueBuffers(s Source, b []Buffer) { + C.alSourceQueueBuffers(C.ALuint(s), C.ALsizei(len(b)), (*C.ALuint)(unsafe.Pointer(&b[0]))) +} + +func alSourceUnqueueBuffers(s Source, b []Buffer) { + C.alSourceUnqueueBuffers(C.ALuint(s), C.ALsizei(len(b)), (*C.ALuint)(unsafe.Pointer(&b[0]))) +} + +func alGetListenerf(k int) float32 { + var v C.ALfloat + C.alGetListenerf(C.ALenum(k), &v) + return float32(v) +} + +func alGetListenerfv(k int, v []float32) { + C.alGetListenerfv(C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0]))) +} + +func alListenerf(k int, v float32) { + C.alListenerf(C.ALenum(k), C.ALfloat(v)) +} + +func alListenerfv(k int, v []float32) { + C.alListenerfv(C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0]))) +} + +func alGenBuffers(n int) []Buffer { + s := make([]Buffer, n) + C.alGenBuffers(C.ALsizei(n), (*C.ALuint)(unsafe.Pointer(&s[0]))) + return s +} + +func alDeleteBuffers(b []Buffer) { + C.alDeleteBuffers(C.ALsizei(len(b)), (*C.ALuint)(unsafe.Pointer(&b[0]))) +} + +func alGetBufferi(b Buffer, k int) int32 { + var v C.ALint + C.alGetBufferi(C.ALuint(b), C.ALenum(k), &v) + return int32(v) +} + +func alBufferData(b Buffer, format uint32, data []byte, freq int32) { + C.alBufferData(C.ALuint(b), C.ALenum(format), unsafe.Pointer(&data[0]), C.ALsizei(len(data)), C.ALsizei(freq)) +} + +func alIsBuffer(b Buffer) bool { + return C.alIsBuffer(C.ALuint(b)) == C.AL_TRUE +} diff --git a/src/golang.org/x/mobile/exp/audio/al/alc.go b/src/golang.org/x/mobile/exp/audio/al/alc.go new file mode 100644 index 0000000000..2c8137df7e --- /dev/null +++ b/src/golang.org/x/mobile/exp/audio/al/alc.go @@ -0,0 +1,74 @@ +// Copyright 2015 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 darwin linux + +package al + +import ( + "errors" + "sync" + "unsafe" +) + +var ( + mu sync.Mutex + device unsafe.Pointer + context unsafe.Pointer +) + +// DeviceError returns the last known error from the current device. +func DeviceError() int32 { + return alcGetError(device) +} + +// TODO(jbd): Investigate the cases where multiple audio output +// devices might be needed. + +// OpenDevice opens the default audio device. +// Calls to OpenDevice are safe for concurrent use. +func OpenDevice() error { + mu.Lock() + defer mu.Unlock() + + // already opened + if device != nil { + return nil + } + + dev := alcOpenDevice("") + if dev == nil { + return errors.New("al: cannot open the default audio device") + } + ctx := alcCreateContext(dev, nil) + if ctx == nil { + alcCloseDevice(dev) + return errors.New("al: cannot create a new context") + } + if !alcMakeContextCurrent(ctx) { + alcCloseDevice(dev) + return errors.New("al: cannot make context current") + } + device = dev + context = ctx + return nil +} + +// CloseDevice closes the device and frees related resources. +// Calls to CloseDevice are safe for concurrent use. +func CloseDevice() { + mu.Lock() + defer mu.Unlock() + + if device == nil { + return + } + + alcCloseDevice(device) + if context != nil { + alcDestroyContext(context) + } + device = nil + context = nil +} diff --git a/src/golang.org/x/mobile/exp/audio/al/alc_android.go b/src/golang.org/x/mobile/exp/audio/al/alc_android.go new file mode 100644 index 0000000000..8efae2c6f6 --- /dev/null +++ b/src/golang.org/x/mobile/exp/audio/al/alc_android.go @@ -0,0 +1,76 @@ +// Copyright 2015 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. + +package al + +/* +#include +#include +#include +#include + +ALCint call_alcGetError(LPALCGETERROR fn, ALCdevice* d) { + return fn(d); +} + +ALCdevice* call_alcOpenDevice(LPALCOPENDEVICE fn, const ALCchar* name) { + return fn(name); +} + +ALCboolean call_alcCloseDevice(LPALCCLOSEDEVICE fn, ALCdevice* d) { + return fn(d); +} + +ALCcontext* call_alcCreateContext(LPALCCREATECONTEXT fn, ALCdevice* d, const ALCint* attrs) { + return fn(d, attrs); +} + +ALCboolean call_alcMakeContextCurrent(LPALCMAKECONTEXTCURRENT fn, ALCcontext* c) { + return fn(c); +} + +void call_alcDestroyContext(LPALCDESTROYCONTEXT fn, ALCcontext* c) { + return fn(c); +} +*/ +import "C" +import ( + "sync" + "unsafe" +) + +var once sync.Once + +func alcGetError(d unsafe.Pointer) int32 { + dev := (*C.ALCdevice)(d) + return int32(C.call_alcGetError(alcGetErrorFunc, dev)) +} + +func alcOpenDevice(name string) unsafe.Pointer { + once.Do(initAL) + n := C.CString(name) + defer C.free(unsafe.Pointer(n)) + + return (unsafe.Pointer)(C.call_alcOpenDevice(alcOpenDeviceFunc, (*C.ALCchar)(unsafe.Pointer(n)))) +} + +func alcCloseDevice(d unsafe.Pointer) bool { + dev := (*C.ALCdevice)(d) + return C.call_alcCloseDevice(alcCloseDeviceFunc, dev) == C.AL_TRUE +} + +func alcCreateContext(d unsafe.Pointer, attrs []int32) unsafe.Pointer { + dev := (*C.ALCdevice)(d) + // TODO(jbd): Handle attrs. + return (unsafe.Pointer)(C.call_alcCreateContext(alcCreateContextFunc, dev, nil)) +} + +func alcMakeContextCurrent(c unsafe.Pointer) bool { + ctx := (*C.ALCcontext)(c) + return C.call_alcMakeContextCurrent(alcMakeContextCurrentFunc, ctx) == C.AL_TRUE +} + +func alcDestroyContext(c unsafe.Pointer) { + C.call_alcDestroyContext(alcDestroyContextFunc, (*C.ALCcontext)(c)) +} diff --git a/src/golang.org/x/mobile/exp/audio/al/alc_notandroid.go b/src/golang.org/x/mobile/exp/audio/al/alc_notandroid.go new file mode 100644 index 0000000000..ff7c59ba9f --- /dev/null +++ b/src/golang.org/x/mobile/exp/audio/al/alc_notandroid.go @@ -0,0 +1,62 @@ +// Copyright 2015 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 darwin linux,!android + +package al + +/* +#cgo darwin CFLAGS: -DGOOS_darwin +#cgo linux CFLAGS: -DGOOS_linux +#cgo darwin LDFLAGS: -framework OpenAL +#cgo linux LDFLAGS: -lopenal + +#ifdef GOOS_darwin +#include +#include +#endif + +#ifdef GOOS_linux +#include +#include +#endif +*/ +import "C" +import "unsafe" + +/* +On Ubuntu 14.04 'Trusty', you may have to install these libraries: +sudo apt-get install libopenal-dev +*/ + +func alcGetError(d unsafe.Pointer) int32 { + dev := (*C.ALCdevice)(d) + return int32(C.alcGetError(dev)) +} + +func alcOpenDevice(name string) unsafe.Pointer { + n := C.CString(name) + defer C.free(unsafe.Pointer(n)) + + return (unsafe.Pointer)(C.alcOpenDevice((*C.ALCchar)(unsafe.Pointer(n)))) +} + +func alcCloseDevice(d unsafe.Pointer) bool { + dev := (*C.ALCdevice)(d) + return C.alcCloseDevice(dev) == C.ALC_TRUE +} + +func alcCreateContext(d unsafe.Pointer, attrs []int32) unsafe.Pointer { + dev := (*C.ALCdevice)(d) + return (unsafe.Pointer)(C.alcCreateContext(dev, nil)) +} + +func alcMakeContextCurrent(c unsafe.Pointer) bool { + ctx := (*C.ALCcontext)(c) + return C.alcMakeContextCurrent(ctx) == C.ALC_TRUE +} + +func alcDestroyContext(c unsafe.Pointer) { + C.alcDestroyContext((*C.ALCcontext)(c)) +} diff --git a/src/golang.org/x/mobile/exp/audio/al/const.go b/src/golang.org/x/mobile/exp/audio/al/const.go new file mode 100644 index 0000000000..4b35ee4fc7 --- /dev/null +++ b/src/golang.org/x/mobile/exp/audio/al/const.go @@ -0,0 +1,78 @@ +// Copyright 2015 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 darwin linux + +package al + +// Error returns one of these error codes. +const ( + InvalidName = 0xA001 + InvalidEnum = 0xA002 + InvalidValue = 0xA003 + InvalidOperation = 0xA004 + OutOfMemory = 0xA005 +) + +// Distance models. +const ( + InverseDistance = 0xD001 + InverseDistanceClamped = 0xD002 + LinearDistance = 0xD003 + LinearDistanceClamped = 0xD004 + ExponentDistance = 0xD005 + ExponentDistanceClamped = 0xD006 +) + +// Global parameters. +const ( + paramDistanceModel = 0xD000 + paramDopplerFactor = 0xC000 + paramDopplerVelocity = 0xC001 + paramSpeedOfSound = 0xC003 + paramVendor = 0xB001 + paramVersion = 0xB002 + paramRenderer = 0xB003 + paramExtensions = 0xB004 +) + +// Source and listener parameters. +const ( + paramGain = 0x100A + paramPosition = 0x1004 + paramVelocity = 0x1006 + paramOrientation = 0x100F + paramMinGain = 0x100D + paramMaxGain = 0x100E + paramSourceState = 0x1010 + paramBuffersQueued = 0x1015 + paramBuffersProcessed = 0x1016 + paramSecOffset = 0x1024 + paramSampleOffset = 0x1025 + paramByteOffset = 0x1026 +) + +// A source could be in the state of initial, playing, paused or stopped. +const ( + Initial = 0x1011 + Playing = 0x1012 + Paused = 0x1013 + Stopped = 0x1014 +) + +// Buffer parameters. +const ( + paramFreq = 0x2001 + paramBits = 0x2002 + paramChannels = 0x2003 + paramSize = 0x2004 +) + +// Audio formats. Buffer.BufferData accepts one of these formats as the data format. +const ( + FormatMono8 = 0x1100 + FormatMono16 = 0x1101 + FormatStereo8 = 0x1102 + FormatStereo16 = 0x1103 +) diff --git a/src/golang.org/x/mobile/exp/audio/audio.go b/src/golang.org/x/mobile/exp/audio/audio.go new file mode 100644 index 0000000000..6b24ed07fd --- /dev/null +++ b/src/golang.org/x/mobile/exp/audio/audio.go @@ -0,0 +1,386 @@ +// Copyright 2015 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 darwin linux + +// Package audio provides a basic audio player. +// +// In order to use this package on Linux desktop distros, +// you will need OpenAL library as an external dependency. +// On Ubuntu 14.04 'Trusty', you may have to install this library +// by running the command below. +// +// sudo apt-get install libopenal-dev +// +// When compiled for Android, this package uses OpenAL Soft as a backend. +// Please add its license file to the open source notices of your +// application. +// OpenAL Soft's license file could be found at +// http://repo.or.cz/w/openal-soft.git/blob/HEAD:/COPYING. +package audio // import "golang.org/x/mobile/exp/audio" + +import ( + "bytes" + "errors" + "fmt" + "io" + "sync" + "time" + + "golang.org/x/mobile/exp/audio/al" +) + +// ReadSeekCloser is an io.ReadSeeker and io.Closer. +type ReadSeekCloser interface { + io.ReadSeeker + io.Closer +} + +// Format represents a PCM data format. +type Format int + +const ( + Mono8 Format = iota + 1 + Mono16 + Stereo8 + Stereo16 +) + +func (f Format) String() string { return formatStrings[f] } + +// formatBytes is the product of bytes per sample and number of channels. +var formatBytes = [...]int64{ + Mono8: 1, + Mono16: 2, + Stereo8: 2, + Stereo16: 4, +} + +var formatCodes = [...]uint32{ + Mono8: al.FormatMono8, + Mono16: al.FormatMono16, + Stereo8: al.FormatStereo8, + Stereo16: al.FormatStereo16, +} + +var formatStrings = [...]string{ + 0: "unknown", + Mono8: "mono8", + Mono16: "mono16", + Stereo8: "stereo8", + Stereo16: "stereo16", +} + +// State indicates the current playing state of the player. +type State int + +const ( + Unknown State = iota + Initial + Playing + Paused + Stopped +) + +func (s State) String() string { return stateStrings[s] } + +var stateStrings = [...]string{ + Unknown: "unknown", + Initial: "initial", + Playing: "playing", + Paused: "paused", + Stopped: "stopped", +} + +var codeToState = map[int32]State{ + 0: Unknown, + al.Initial: Initial, + al.Playing: Playing, + al.Paused: Paused, + al.Stopped: Stopped, +} + +type track struct { + format Format + samplesPerSecond int64 + src ReadSeekCloser + + // hasHeader represents whether the audio source contains + // a PCM header. If true, the audio data starts 44 bytes + // later in the source. + hasHeader bool +} + +// Player is a basic audio player that plays PCM data. +// Operations on a nil *Player are no-op, a nil *Player can +// be used for testing purposes. +type Player struct { + t *track + source al.Source + + mu sync.Mutex + prep bool + bufs []al.Buffer // buffers are created and queued to source during prepare. + sizeBytes int64 // size of the audio source +} + +// NewPlayer returns a new Player. +// It initializes the underlying audio devices and the related resources. +// If zero values are provided for format and sample rate values, the player +// determines them from the source's WAV header. +// An error is returned if the format and sample rate can't be determined. +func NewPlayer(src ReadSeekCloser, format Format, samplesPerSecond int64) (*Player, error) { + if err := al.OpenDevice(); err != nil { + return nil, err + } + s := al.GenSources(1) + if code := al.Error(); code != 0 { + return nil, fmt.Errorf("audio: cannot generate an audio source [err=%x]", code) + } + p := &Player{ + t: &track{format: format, src: src, samplesPerSecond: samplesPerSecond}, + source: s[0], + } + if err := p.discoverHeader(); err != nil { + return nil, err + } + if p.t.format == 0 { + return nil, errors.New("audio: cannot determine the format") + } + if p.t.samplesPerSecond == 0 { + return nil, errors.New("audio: cannot determine the sample rate") + } + return p, nil +} + +// headerSize is the size of WAV headers. +// See http://www.topherlee.com/software/pcm-tut-wavformat.html. +const headerSize = 44 + +var ( + riffHeader = []byte("RIFF") + waveHeader = []byte("WAVE") +) + +func (p *Player) discoverHeader() error { + buf := make([]byte, headerSize) + if n, _ := io.ReadFull(p.t.src, buf); n != headerSize { + // No header present or read error. + return nil + } + if !(bytes.Equal(buf[0:4], riffHeader) && bytes.Equal(buf[8:12], waveHeader)) { + return nil + } + p.t.hasHeader = true + var format Format + switch channels, depth := buf[22], buf[34]; { + case channels == 1 && depth == 8: + format = Mono8 + case channels == 1 && depth == 16: + format = Mono16 + case channels == 2 && depth == 8: + format = Stereo8 + case channels == 2 && depth == 16: + format = Stereo16 + default: + return fmt.Errorf("audio: unsupported format; num of channels=%d, bit rate=%d", channels, depth) + } + if p.t.format == 0 { + p.t.format = format + } + if p.t.format != format { + return fmt.Errorf("audio: given format %v does not match header %v", p.t.format, format) + } + sampleRate := int64(buf[24]) | int64(buf[25])<<8 | int64(buf[26])<<16 | int64(buf[27]<<24) + if p.t.samplesPerSecond == 0 { + p.t.samplesPerSecond = sampleRate + } + if p.t.samplesPerSecond != sampleRate { + return fmt.Errorf("audio: given sample rate %v does not match header", p.t.samplesPerSecond, sampleRate) + } + return nil +} + +func (p *Player) prepare(offset int64, force bool) error { + p.mu.Lock() + if !force && p.prep { + p.mu.Unlock() + return nil + } + p.mu.Unlock() + + if p.t.hasHeader { + offset += headerSize + } + if _, err := p.t.src.Seek(offset, 0); err != nil { + return err + } + var bufs []al.Buffer + // TODO(jbd): Limit the number of buffers in use, unqueue and reuse + // the existing buffers as buffers are processed. + buf := make([]byte, 128*1024) + size := offset + for { + n, err := p.t.src.Read(buf) + if n > 0 { + size += int64(n) + b := al.GenBuffers(1) + b[0].BufferData(formatCodes[p.t.format], buf[:n], int32(p.t.samplesPerSecond)) + bufs = append(bufs, b[0]) + } + if err == io.EOF { + break + } + if err != nil { + return err + } + } + + p.mu.Lock() + if len(p.bufs) > 0 { + p.source.UnqueueBuffers(p.bufs) + al.DeleteBuffers(p.bufs) + } + p.sizeBytes = size + p.bufs = bufs + p.prep = true + if len(bufs) > 0 { + p.source.QueueBuffers(bufs) + } + p.mu.Unlock() + return nil +} + +// Play buffers the source audio to the audio device and starts +// to play the source. +// If the player paused or stopped, it reuses the previously buffered +// resources to keep playing from the time it has paused or stopped. +func (p *Player) Play() error { + if p == nil { + return nil + } + // Prepares if the track hasn't been buffered before. + if err := p.prepare(0, false); err != nil { + return err + } + al.PlaySources(p.source) + return lastErr() +} + +// Pause pauses the player. +func (p *Player) Pause() error { + if p == nil { + return nil + } + al.PauseSources(p.source) + return lastErr() +} + +// Stop stops the player. +func (p *Player) Stop() error { + if p == nil { + return nil + } + al.StopSources(p.source) + return lastErr() +} + +// Seek moves the play head to the given offset relative to the start of the source. +func (p *Player) Seek(offset time.Duration) error { + if p == nil { + return nil + } + if err := p.Stop(); err != nil { + return err + } + size := durToByteOffset(p.t, offset) + if err := p.prepare(size, true); err != nil { + return err + } + al.PlaySources(p.source) + return lastErr() +} + +// Current returns the current playback position of the audio that is being played. +func (p *Player) Current() time.Duration { + if p == nil { + return 0 + } + // TODO(jbd): Current never returns the Total when the playing is finished. + // OpenAL may be returning the last buffer's start point as an OffsetByte. + return byteOffsetToDur(p.t, int64(p.source.OffsetByte())) +} + +// Total returns the total duration of the audio source. +func (p *Player) Total() time.Duration { + if p == nil { + return 0 + } + // Prepare is required to determine the length of the source. + // We need to read the entire source to calculate the length. + p.prepare(0, false) + return byteOffsetToDur(p.t, p.sizeBytes) +} + +// Volume returns the current player volume. The range of the volume is [0, 1]. +func (p *Player) Volume() float64 { + if p == nil { + return 0 + } + return float64(p.source.Gain()) +} + +// SetVolume sets the volume of the player. The range of the volume is [0, 1]. +func (p *Player) SetVolume(vol float64) { + if p == nil { + return + } + p.source.SetGain(float32(vol)) +} + +// State returns the player's current state. +func (p *Player) State() State { + if p == nil { + return Unknown + } + return codeToState[p.source.State()] +} + +// Close closes the device and frees the underlying resources +// used by the player. +// It should be called as soon as the player is not in-use anymore. +func (p *Player) Close() error { + if p == nil { + return nil + } + if p.source != 0 { + al.DeleteSources(p.source) + } + p.mu.Lock() + if len(p.bufs) > 0 { + al.DeleteBuffers(p.bufs) + } + p.mu.Unlock() + p.t.src.Close() + return nil +} + +func byteOffsetToDur(t *track, offset int64) time.Duration { + return time.Duration(offset * formatBytes[t.format] * int64(time.Second) / t.samplesPerSecond) +} + +func durToByteOffset(t *track, dur time.Duration) int64 { + return int64(dur) * t.samplesPerSecond / (formatBytes[t.format] * int64(time.Second)) +} + +// lastErr returns the last error or nil if the last operation +// has been succesful. +func lastErr() error { + if code := al.Error(); code != 0 { + return fmt.Errorf("audio: openal failed with %x", code) + } + return nil +} + +// TODO(jbd): Close the device. diff --git a/src/golang.org/x/mobile/exp/audio/audio_test.go b/src/golang.org/x/mobile/exp/audio/audio_test.go new file mode 100644 index 0000000000..51f047b01d --- /dev/null +++ b/src/golang.org/x/mobile/exp/audio/audio_test.go @@ -0,0 +1,40 @@ +// Copyright 2015 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 darwin linux,!android + +package audio + +import ( + "testing" + "time" +) + +func TestNoOp(t *testing.T) { + var p *Player + if err := p.Play(); err != nil { + t.Errorf("no-op player failed to play: %v", err) + } + if err := p.Pause(); err != nil { + t.Errorf("no-op player failed to pause: %v", err) + } + if err := p.Stop(); err != nil { + t.Errorf("no-op player failed to stop: %v", err) + } + if c := p.Current(); c != 0 { + t.Errorf("no-op player returns a non-zero playback position: %v", c) + } + if tot := p.Total(); tot != 0 { + t.Errorf("no-op player returns a non-zero total: %v", tot) + } + if vol := p.Volume(); vol != 0 { + t.Errorf("no-op player returns a non-zero volume: %v", vol) + } + if s := p.State(); s != Unknown { + t.Errorf("playing state: %v", s) + } + p.SetVolume(0.1) + p.Seek(1 * time.Second) + p.Close() +} diff --git a/src/golang.org/x/mobile/exp/f32/affine.go b/src/golang.org/x/mobile/exp/f32/affine.go new file mode 100644 index 0000000000..708a309fba --- /dev/null +++ b/src/golang.org/x/mobile/exp/f32/affine.go @@ -0,0 +1,109 @@ +// Copyright 2014 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. + +package f32 + +import "fmt" + +// An Affine is a 3x3 matrix of float32 values for which the bottom row is +// implicitly always equal to [0 0 1]. +// Elements are indexed first by row then column, i.e. m[row][column]. +type Affine [2]Vec3 + +func (m Affine) String() string { + return fmt.Sprintf(`Affine[% 0.3f, % 0.3f, % 0.3f, + % 0.3f, % 0.3f, % 0.3f]`, + m[0][0], m[0][1], m[0][2], + m[1][0], m[1][1], m[1][2]) +} + +// Identity sets m to be the identity transform. +func (m *Affine) Identity() { + *m = Affine{ + {1, 0, 0}, + {0, 1, 0}, + } +} + +// Eq reports whether each component of m is within epsilon of the same +// component in n. +func (m *Affine) Eq(n *Affine, epsilon float32) bool { + for i := range m { + for j := range m[i] { + diff := m[i][j] - n[i][j] + if diff < -epsilon || +epsilon < diff { + return false + } + } + } + return true +} + +// Mul sets m to be p × q. +func (m *Affine) Mul(p, q *Affine) { + // Store the result in local variables, in case m == a || m == b. + m00 := p[0][0]*q[0][0] + p[0][1]*q[1][0] + m01 := p[0][0]*q[0][1] + p[0][1]*q[1][1] + m02 := p[0][0]*q[0][2] + p[0][1]*q[1][2] + p[0][2] + m10 := p[1][0]*q[0][0] + p[1][1]*q[1][0] + m11 := p[1][0]*q[0][1] + p[1][1]*q[1][1] + m12 := p[1][0]*q[0][2] + p[1][1]*q[1][2] + p[1][2] + m[0][0] = m00 + m[0][1] = m01 + m[0][2] = m02 + m[1][0] = m10 + m[1][1] = m11 + m[1][2] = m12 +} + +// Inverse sets m to be the inverse of p. +func (m *Affine) Inverse(p *Affine) { + m00 := p[1][1] + m01 := -p[0][1] + m02 := p[1][2]*p[0][1] - p[1][1]*p[0][2] + m10 := -p[1][0] + m11 := p[0][0] + m12 := p[1][0]*p[0][2] - p[1][2]*p[0][0] + + det := m00*m11 - m10*m01 + + m[0][0] = m00 / det + m[0][1] = m01 / det + m[0][2] = m02 / det + m[1][0] = m10 / det + m[1][1] = m11 / det + m[1][2] = m12 / det +} + +// Scale sets m to be a scale followed by p. +// It is equivalent to m.Mul(p, &Affine{{x,0,0}, {0,y,0}}). +func (m *Affine) Scale(p *Affine, x, y float32) { + m[0][0] = p[0][0] * x + m[0][1] = p[0][1] * y + m[0][2] = p[0][2] + m[1][0] = p[1][0] * x + m[1][1] = p[1][1] * y + m[1][2] = p[1][2] +} + +// Translate sets m to be a translation followed by p. +// It is equivalent to m.Mul(p, &Affine{{1,0,x}, {0,1,y}}). +func (m *Affine) Translate(p *Affine, x, y float32) { + m[0][0] = p[0][0] + m[0][1] = p[0][1] + m[0][2] = p[0][0]*x + p[0][1]*y + p[0][2] + m[1][0] = p[1][0] + m[1][1] = p[1][1] + m[1][2] = p[1][0]*x + p[1][1]*y + p[1][2] +} + +// Rotate sets m to a rotation in radians followed by p. +// It is equivalent to m.Mul(p, affineRotation). +func (m *Affine) Rotate(p *Affine, radians float32) { + s, c := Sin(radians), Cos(radians) + m.Mul(p, &Affine{ + {+c, +s, 0}, + {-s, +c, 0}, + }) +} diff --git a/src/golang.org/x/mobile/exp/f32/affine_test.go b/src/golang.org/x/mobile/exp/f32/affine_test.go new file mode 100644 index 0000000000..f1d84cdd92 --- /dev/null +++ b/src/golang.org/x/mobile/exp/f32/affine_test.go @@ -0,0 +1,136 @@ +package f32 + +import ( + "math" + "testing" +) + +var xyTests = []struct { + x, y float32 +}{ + {0, 0}, + {1, 1}, + {2, 3}, + {6.5, 4.3}, +} + +var a = Affine{ + {3, 4, 5}, + {6, 7, 8}, +} + +func TestInverse(t *testing.T) { + wantInv := Affine{ + {-2.33333, 1.33333, 1}, + {2, -1, -2}, + } + var gotInv Affine + gotInv.Inverse(&a) + if !gotInv.Eq(&wantInv, 0.01) { + t.Errorf("Inverse: got %s want %s", gotInv, wantInv) + } + + var wantId, gotId Affine + wantId.Identity() + gotId.Mul(&a, &wantInv) + if !gotId.Eq(&wantId, 0.01) { + t.Errorf("Identity #0: got %s want %s", gotId, wantId) + } + gotId.Mul(&wantInv, &a) + if !gotId.Eq(&wantId, 0.01) { + t.Errorf("Identity #1: got %s want %s", gotId, wantId) + } +} + +func TestAffineScale(t *testing.T) { + for _, test := range xyTests { + want := a + want.Mul(&want, &Affine{{test.x, 0, 0}, {0, test.y, 0}}) + got := a + got.Scale(&got, test.x, test.y) + + if !got.Eq(&want, 0.01) { + t.Errorf("(%.2f, %.2f): got %s want %s", test.x, test.y, got, want) + } + } +} + +func TestAffineTranslate(t *testing.T) { + for _, test := range xyTests { + want := a + want.Mul(&want, &Affine{{1, 0, test.x}, {0, 1, test.y}}) + got := a + got.Translate(&got, test.x, test.y) + + if !got.Eq(&want, 0.01) { + t.Errorf("(%.2f, %.2f): got %s want %s", test.x, test.y, got, want) + } + } + +} + +func TestAffineRotate(t *testing.T) { + want := Affine{ + {-4.000, 3.000, 5.000}, + {-7.000, 6.000, 8.000}, + } + got := a + got.Rotate(&got, math.Pi/2) + if !got.Eq(&want, 0.01) { + t.Errorf("rotate π: got %s want %s", got, want) + } + + want = a + got = a + got.Rotate(&got, 2*math.Pi) + if !got.Eq(&want, 0.01) { + t.Errorf("rotate 2π: got %s want %s", got, want) + } + + got = a + got.Rotate(&got, math.Pi) + got.Rotate(&got, math.Pi) + if !got.Eq(&want, 0.01) { + t.Errorf("rotate π then π: got %s want %s", got, want) + } + + got = a + got.Rotate(&got, math.Pi/3) + got.Rotate(&got, -math.Pi/3) + if !got.Eq(&want, 0.01) { + t.Errorf("rotate π/3 then -π/3: got %s want %s", got, want) + } +} + +func TestAffineScaleTranslate(t *testing.T) { + mulVec := func(m *Affine, v [2]float32) (mv [2]float32) { + mv[0] = m[0][0]*v[0] + m[0][1]*v[1] + m[0][2] + mv[1] = m[1][0]*v[0] + m[1][1]*v[1] + m[1][2] + return mv + } + v := [2]float32{1, 10} + + var sThenT Affine + sThenT.Identity() + sThenT.Scale(&sThenT, 13, 17) + sThenT.Translate(&sThenT, 101, 151) + wantSTT := [2]float32{ + 13 * (101 + 1), + 17 * (151 + 10), + } + if got := mulVec(&sThenT, v); got != wantSTT { + t.Errorf("S then T: got %v, want %v", got, wantSTT) + } + + var tThenS Affine + tThenS.Identity() + tThenS.Translate(&tThenS, 101, 151) + tThenS.Scale(&tThenS, 13, 17) + wantTTS := [2]float32{ + 101 + (13 * 1), + 151 + (17 * 10), + } + if got := mulVec(&tThenS, v); got != wantTTS { + t.Errorf("T then S: got %v, want %v", got, wantTTS) + } +} diff --git a/src/golang.org/x/mobile/exp/f32/f32.go b/src/golang.org/x/mobile/exp/f32/f32.go new file mode 100644 index 0000000000..d79439808f --- /dev/null +++ b/src/golang.org/x/mobile/exp/f32/f32.go @@ -0,0 +1,93 @@ +// Copyright 2014 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. + +//go:generate go run gen.go -output table.go + +// Package f32 implements some linear algebra and GL helpers for float32s. +// +// Types defined in this package have methods implementing common +// mathematical operations. The common form for these functions is +// +// func (dst *T) Op(lhs, rhs *T) +// +// which reads in traditional mathematical notation as +// +// dst = lhs op rhs. +// +// It is safe to use the destination address as the left-hand side, +// that is, dst *= rhs is dst.Mul(dst, rhs). +// +// WARNING +// +// The interface to this package is not stable. It will change considerably. +// Only use functions that provide package documentation. Semantics are +// non-obvious. Be prepared for the package name to change. +package f32 // import "golang.org/x/mobile/exp/f32" + +import ( + "encoding/binary" + "fmt" + "math" +) + +type Radian float32 + +func Cos(x float32) float32 { + const n = sinTableLen + i := uint32(int32(x * (n / math.Pi))) + i += n / 2 + i &= 2*n - 1 + if i >= n { + return -sinTable[i&(n-1)] + } + return sinTable[i&(n-1)] +} + +func Sin(x float32) float32 { + const n = sinTableLen + i := uint32(int32(x * (n / math.Pi))) + i &= 2*n - 1 + if i >= n { + return -sinTable[i&(n-1)] + } + return sinTable[i&(n-1)] +} + +func Sqrt(x float32) float32 { + return float32(math.Sqrt(float64(x))) // TODO(crawshaw): implement +} + +func Tan(x float32) float32 { + return float32(math.Tan(float64(x))) // TODO(crawshaw): fast version +} + +// Bytes returns the byte representation of float32 values in the given byte +// order. byteOrder must be either binary.BigEndian or binary.LittleEndian. +func Bytes(byteOrder binary.ByteOrder, values ...float32) []byte { + le := false + switch byteOrder { + case binary.BigEndian: + case binary.LittleEndian: + le = true + default: + panic(fmt.Sprintf("invalid byte order %v", byteOrder)) + } + + b := make([]byte, 4*len(values)) + for i, v := range values { + u := math.Float32bits(v) + if le { + b[4*i+0] = byte(u >> 0) + b[4*i+1] = byte(u >> 8) + b[4*i+2] = byte(u >> 16) + b[4*i+3] = byte(u >> 24) + } else { + b[4*i+0] = byte(u >> 24) + b[4*i+1] = byte(u >> 16) + b[4*i+2] = byte(u >> 8) + b[4*i+3] = byte(u >> 0) + } + } + return b +} diff --git a/src/golang.org/x/mobile/exp/f32/f32_test.go b/src/golang.org/x/mobile/exp/f32/f32_test.go new file mode 100644 index 0000000000..d662fe6c07 --- /dev/null +++ b/src/golang.org/x/mobile/exp/f32/f32_test.go @@ -0,0 +1,361 @@ +// Copyright 2014 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. + +package f32 + +import ( + "bytes" + "encoding/binary" + "math" + "testing" +) + +func TestAffineTranslationsCommute(t *testing.T) { + a := &Affine{ + {1, 0, 3}, + {0, 1, 4}, + } + b := &Affine{ + {1, 0, 20}, + {0, 1, 30}, + } + + var m0, m1 Affine + m0.Mul(a, b) + m1.Mul(b, a) + if !m0.Eq(&m1, 0) { + t.Errorf("m0, m1 differ.\nm0: %v\nm1: %v", m0, m1) + } +} + +func TestAffineMat3Equivalence(t *testing.T) { + a0 := Affine{ + {13, 19, 37}, + {101, 149, 311}, + } + m0 := Mat3{ + a0[0], + a0[1], + {0, 0, 1}, + } + + a1 := Affine{ + {1009, 1051, 1087}, + {563, 569, 571}, + } + m1 := Mat3{ + a1[0], + a1[1], + {0, 0, 1}, + } + + a2 := Affine{} + a2.Mul(&a0, &a1) + m2 := Mat3{ + a2[0], + a2[1], + {0, 0, 1}, + } + + mm := Mat3{} + mm.Mul(&m0, &m1) + + if !m2.Eq(&mm, 0) { + t.Errorf("m2, mm differ.\nm2: %v\nmm: %v", m2, mm) + } +} + +var x3 = Mat3{ + {0, 1, 2}, + {3, 4, 5}, + {6, 7, 8}, +} + +var x3sq = Mat3{ + {15, 18, 21}, + {42, 54, 66}, + {69, 90, 111}, +} + +var id3 = Mat3{ + {1, 0, 0}, + {0, 1, 0}, + {0, 0, 1}, +} + +func TestMat3Mul(t *testing.T) { + tests := []struct{ m0, m1, want Mat3 }{ + {x3, id3, x3}, + {id3, x3, x3}, + {x3, x3, x3sq}, + { + Mat3{ + {+1.811, +0.000, +0.000}, + {+0.000, +2.414, +0.000}, + {+0.000, +0.000, -1.010}, + }, + Mat3{ + {+0.992, -0.015, +0.123}, + {+0.000, +0.992, +0.123}, + {-0.124, -0.122, +0.985}, + }, + Mat3{ + {+1.797, -0.027, +0.223}, + {+0.000, +2.395, +0.297}, + {+0.125, +0.123, -0.995}, + }, + }, + } + + for i, test := range tests { + got := Mat3{} + got.Mul(&test.m0, &test.m1) + if !got.Eq(&test.want, 0.01) { + t.Errorf("test #%d:\n%s *\n%s =\n%s, want\n%s", i, test.m0, test.m1, got, test.want) + } + } +} + +func TestMat3SelfMul(t *testing.T) { + m := x3 + m.Mul(&m, &m) + if !m.Eq(&x3sq, 0) { + t.Errorf("m, x3sq differ.\nm: %v\nx3sq: %v", m, x3sq) + } +} + +var x4 = Mat4{ + {0, 1, 2, 3}, + {4, 5, 6, 7}, + {8, 9, 10, 11}, + {12, 13, 14, 15}, +} + +var x4sq = Mat4{ + {56, 62, 68, 74}, + {152, 174, 196, 218}, + {248, 286, 324, 362}, + {344, 398, 452, 506}, +} + +var id4 = Mat4{ + {1, 0, 0, 0}, + {0, 1, 0, 0}, + {0, 0, 1, 0}, + {0, 0, 0, 1}, +} + +func TestMat4Eq(t *testing.T) { + tests := []struct { + m0, m1 Mat4 + eq bool + }{ + {x4, x4, true}, + {id4, id4, true}, + {x4, id4, false}, + } + + for _, test := range tests { + got := test.m0.Eq(&test.m1, 0.01) + if got != test.eq { + t.Errorf("Eq=%v, want %v for\n%s\n%s", got, test.eq, test.m0, test.m1) + } + } +} + +func TestMat4Mul(t *testing.T) { + tests := []struct{ m0, m1, want Mat4 }{ + {x4, id4, x4}, + {id4, x4, x4}, + {x4, x4, x4sq}, + { + Mat4{ + {+1.811, +0.000, +0.000, +0.000}, + {+0.000, +2.414, +0.000, +0.000}, + {+0.000, +0.000, -1.010, -1.000}, + {+0.000, +0.000, -2.010, +0.000}, + }, + Mat4{ + {+0.992, -0.015, +0.123, +0.000}, + {+0.000, +0.992, +0.123, +0.000}, + {-0.124, -0.122, +0.985, +0.000}, + {-0.000, -0.000, -8.124, +1.000}, + }, + Mat4{ + {+1.797, -0.027, +0.223, +0.000}, + {+0.000, +2.395, +0.297, +0.000}, + {+0.125, +0.123, +7.129, -1.000}, + {+0.249, +0.245, -1.980, +0.000}, + }, + }, + } + + for i, test := range tests { + got := Mat4{} + got.Mul(&test.m0, &test.m1) + if !got.Eq(&test.want, 0.01) { + t.Errorf("test #%d:\n%s *\n%s =\n%s, want\n%s", i, test.m0, test.m1, got, test.want) + } + } +} + +func TestMat4LookAt(t *testing.T) { + tests := []struct { + eye, center, up Vec3 + want Mat4 + }{ + { + Vec3{1, 1, 8}, Vec3{0, 0, 0}, Vec3{0, 1, 0}, + Mat4{ + {0.992, -0.015, 0.123, 0.000}, + {0.000, 0.992, 0.123, 0.000}, + {-0.124, -0.122, 0.985, 0.000}, + {-0.000, -0.000, -8.124, 1.000}, + }, + }, + { + Vec3{4, 5, 7}, Vec3{0.1, 0.2, 0.3}, Vec3{0, -1, 0}, + Mat4{ + {-0.864, 0.265, 0.428, 0.000}, + {0.000, -0.850, 0.526, 0.000}, + {0.503, 0.455, 0.735, 0.000}, + {-0.064, 0.007, -9.487, 1.000}, + }, + }, + } + + for _, test := range tests { + got := Mat4{} + got.LookAt(&test.eye, &test.center, &test.up) + if !got.Eq(&test.want, 0.01) { + t.Errorf("LookAt(%s,%s%s) =\n%s\nwant\n%s", test.eye, test.center, test.up, got, test.want) + } + } + +} + +func TestMat4Perspective(t *testing.T) { + want := Mat4{ + {1.811, 0.000, 0.000, 0.000}, + {0.000, 2.414, 0.000, 0.000}, + {0.000, 0.000, -1.010, -1.000}, + {0.000, 0.000, -2.010, 0.000}, + } + got := Mat4{} + + got.Perspective(Radian(math.Pi/4), 4.0/3, 1, 200) + + if !got.Eq(&want, 0.01) { + t.Errorf("got\n%s\nwant\n%s", got, want) + } + +} + +func TestMat4Rotate(t *testing.T) { + want := &Mat4{ + {2.000, 1.000, -0.000, 3.000}, + {6.000, 5.000, -4.000, 7.000}, + {10.000, 9.000, -8.000, 11.000}, + {14.000, 13.000, -12.000, 15.000}, + } + + got := new(Mat4) + got.Rotate(&x4, Radian(math.Pi/2), &Vec3{0, 1, 0}) + + if !got.Eq(want, 0.01) { + t.Errorf("got\n%s\nwant\n%s", got, want) + } +} + +func TestMat4Scale(t *testing.T) { + want := &Mat4{ + {0 * 2, 1 * 3, 2 * 4, 3 * 1}, + {4 * 2, 5 * 3, 6 * 4, 7 * 1}, + {8 * 2, 9 * 3, 10 * 4, 11 * 1}, + {12 * 2, 13 * 3, 14 * 4, 15 * 1}, + } + + got := new(Mat4) + got.Scale(&x4, 2, 3, 4) + + if !got.Eq(want, 0.01) { + t.Errorf("got\n%s\nwant\n%s", got, want) + } +} + +func TestMat4Translate(t *testing.T) { + want := &Mat4{ + {0, 1, 2, 0*0.1 + 1*0.2 + 2*0.3 + 3*1}, + {4, 5, 6, 4*0.1 + 5*0.2 + 6*0.3 + 7*1}, + {8, 9, 10, 8*0.1 + 9*0.2 + 10*0.3 + 11*1}, + {12, 13, 14, 12*0.1 + 13*0.2 + 14*0.3 + 15*1}, + } + + got := new(Mat4) + got.Translate(&x4, 0.1, 0.2, 0.3) + + if !got.Eq(want, 0.01) { + t.Errorf("got\n%s\nwant\n%s", got, want) + } +} + +func testTrig(t *testing.T, gotFunc func(float32) float32, wantFunc func(float64) float64) { + nBad := 0 + for a := float32(-9); a < +9; a += .01 { + got := gotFunc(a) + want := float32(wantFunc(float64(a))) + diff := got - want + if diff < 0 { + diff = -diff + } + if diff > 0.001 { + if nBad++; nBad == 10 { + t.Errorf("too many failures") + break + } + t.Errorf("a=%+.2f: got %+.4f, want %+.4f, diff=%.4f", a, got, want, diff) + } + } +} + +func TestCos(t *testing.T) { testTrig(t, Cos, math.Cos) } +func TestSin(t *testing.T) { testTrig(t, Sin, math.Sin) } +func TestTan(t *testing.T) { testTrig(t, Tan, math.Tan) } + +func BenchmarkSin(b *testing.B) { + for i := 0; i < b.N; i++ { + for a := 0; a < 3141; a++ { + Sin(float32(a) / 1000) + } + } +} + +func TestBytes(t *testing.T) { + testCases := []struct { + byteOrder binary.ByteOrder + want []byte + }{{ + binary.BigEndian, + []byte{ + // The IEEE 754 binary32 format is 1 sign bit, 8 exponent bits and 23 fraction bits. + 0x00, 0x00, 0x00, 0x00, // float32(+0.00) is 0 0000000_0 0000000_00000000_00000000 + 0x3f, 0xa0, 0x00, 0x00, // float32(+1.25) is 0 0111111_1 0100000_00000000_00000000 + 0xc0, 0x00, 0x00, 0x00, // float32(-2.00) is 1 1000000_0 0000000_00000000_00000000 + }, + }, { + binary.LittleEndian, + []byte{ + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xa0, 0x3f, + 0x00, 0x00, 0x00, 0xc0, + }, + }} + + for _, tc := range testCases { + got := Bytes(tc.byteOrder, +0.00, +1.25, -2.00) + if !bytes.Equal(got, tc.want) { + t.Errorf("%v:\ngot % x\nwant % x", tc.byteOrder, got, tc.want) + } + } +} diff --git a/src/golang.org/x/mobile/exp/f32/gen.go b/src/golang.org/x/mobile/exp/f32/gen.go new file mode 100644 index 0000000000..8f8b1871a9 --- /dev/null +++ b/src/golang.org/x/mobile/exp/f32/gen.go @@ -0,0 +1,48 @@ +// Copyright 2014 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 ignore + +package main + +/* +This program generates table.go. Invoke it as: +go run gen.go -output table.go +*/ + +import ( + "bytes" + "flag" + "fmt" + "go/format" + "io/ioutil" + "log" + "math" +) + +// N is the number of entries in the sin look-up table. It must be a power of 2. +const N = 4096 + +var filename = flag.String("output", "table.go", "output file name") + +func main() { + b := new(bytes.Buffer) + fmt.Fprintf(b, "// generated by go run gen.go; DO NOT EDIT\n\npackage f32\n\n") + fmt.Fprintf(b, "const sinTableLen = %d\n\n", N) + fmt.Fprintf(b, "// sinTable[i] equals sin(i * π / sinTableLen).\n") + fmt.Fprintf(b, "var sinTable = [sinTableLen]float32{\n") + for i := 0; i < N; i++ { + radians := float64(i) * (math.Pi / N) + fmt.Fprintf(b, "%v,\n", float32(math.Sin(radians))) + } + fmt.Fprintf(b, "}\n") + + data, err := format.Source(b.Bytes()) + if err != nil { + log.Fatal(err) + } + if err := ioutil.WriteFile(*filename, data, 0644); err != nil { + log.Fatal(err) + } +} diff --git a/src/golang.org/x/mobile/exp/f32/mat3.go b/src/golang.org/x/mobile/exp/f32/mat3.go new file mode 100644 index 0000000000..2577dbfa92 --- /dev/null +++ b/src/golang.org/x/mobile/exp/f32/mat3.go @@ -0,0 +1,63 @@ +// Copyright 2014 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. + +package f32 + +import "fmt" + +// A Mat3 is a 3x3 matrix of float32 values. +// Elements are indexed first by row then column, i.e. m[row][column]. +type Mat3 [3]Vec3 + +func (m Mat3) String() string { + return fmt.Sprintf(`Mat3[% 0.3f, % 0.3f, % 0.3f, + % 0.3f, % 0.3f, % 0.3f, + % 0.3f, % 0.3f, % 0.3f]`, + m[0][0], m[0][1], m[0][2], + m[1][0], m[1][1], m[1][2], + m[2][0], m[2][1], m[2][2]) +} + +func (m *Mat3) Identity() { + *m = Mat3{ + {1, 0, 0}, + {0, 1, 0}, + {0, 0, 1}, + } +} + +func (m *Mat3) Eq(n *Mat3, epsilon float32) bool { + for i := range m { + for j := range m[i] { + diff := m[i][j] - n[i][j] + if diff < -epsilon || +epsilon < diff { + return false + } + } + } + return true +} + +// Mul stores a × b in m. +func (m *Mat3) Mul(a, b *Mat3) { + // Store the result in local variables, in case m == a || m == b. + m00 := a[0][0]*b[0][0] + a[0][1]*b[1][0] + a[0][2]*b[2][0] + m01 := a[0][0]*b[0][1] + a[0][1]*b[1][1] + a[0][2]*b[2][1] + m02 := a[0][0]*b[0][2] + a[0][1]*b[1][2] + a[0][2]*b[2][2] + m10 := a[1][0]*b[0][0] + a[1][1]*b[1][0] + a[1][2]*b[2][0] + m11 := a[1][0]*b[0][1] + a[1][1]*b[1][1] + a[1][2]*b[2][1] + m12 := a[1][0]*b[0][2] + a[1][1]*b[1][2] + a[1][2]*b[2][2] + m20 := a[2][0]*b[0][0] + a[2][1]*b[1][0] + a[2][2]*b[2][0] + m21 := a[2][0]*b[0][1] + a[2][1]*b[1][1] + a[2][2]*b[2][1] + m22 := a[2][0]*b[0][2] + a[2][1]*b[1][2] + a[2][2]*b[2][2] + m[0][0] = m00 + m[0][1] = m01 + m[0][2] = m02 + m[1][0] = m10 + m[1][1] = m11 + m[1][2] = m12 + m[2][0] = m20 + m[2][1] = m21 + m[2][2] = m22 +} diff --git a/src/golang.org/x/mobile/exp/f32/mat4.go b/src/golang.org/x/mobile/exp/f32/mat4.go new file mode 100644 index 0000000000..75d3a59849 --- /dev/null +++ b/src/golang.org/x/mobile/exp/f32/mat4.go @@ -0,0 +1,193 @@ +// Copyright 2014 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. + +package f32 + +import "fmt" + +// A Mat4 is a 4x4 matrix of float32 values. +// Elements are indexed first by row then column, i.e. m[row][column]. +type Mat4 [4]Vec4 + +func (m Mat4) String() string { + return fmt.Sprintf(`Mat4[% 0.3f, % 0.3f, % 0.3f, % 0.3f, + % 0.3f, % 0.3f, % 0.3f, % 0.3f, + % 0.3f, % 0.3f, % 0.3f, % 0.3f, + % 0.3f, % 0.3f, % 0.3f, % 0.3f]`, + m[0][0], m[0][1], m[0][2], m[0][3], + m[1][0], m[1][1], m[1][2], m[1][3], + m[2][0], m[2][1], m[2][2], m[2][3], + m[3][0], m[3][1], m[3][2], m[3][3]) +} + +func (m *Mat4) Identity() { + *m = Mat4{ + {1, 0, 0, 0}, + {0, 1, 0, 0}, + {0, 0, 1, 0}, + {0, 0, 0, 1}, + } +} + +func (m *Mat4) Eq(n *Mat4, epsilon float32) bool { + for i := range m { + for j := range m[i] { + diff := m[i][j] - n[i][j] + if diff < -epsilon || +epsilon < diff { + return false + } + } + } + return true +} + +// Mul stores a × b in m. +func (m *Mat4) Mul(a, b *Mat4) { + // Store the result in local variables, in case m == a || m == b. + m00 := a[0][0]*b[0][0] + a[0][1]*b[1][0] + a[0][2]*b[2][0] + a[0][3]*b[3][0] + m01 := a[0][0]*b[0][1] + a[0][1]*b[1][1] + a[0][2]*b[2][1] + a[0][3]*b[3][1] + m02 := a[0][0]*b[0][2] + a[0][1]*b[1][2] + a[0][2]*b[2][2] + a[0][3]*b[3][2] + m03 := a[0][0]*b[0][3] + a[0][1]*b[1][3] + a[0][2]*b[2][3] + a[0][3]*b[3][3] + m10 := a[1][0]*b[0][0] + a[1][1]*b[1][0] + a[1][2]*b[2][0] + a[1][3]*b[3][0] + m11 := a[1][0]*b[0][1] + a[1][1]*b[1][1] + a[1][2]*b[2][1] + a[1][3]*b[3][1] + m12 := a[1][0]*b[0][2] + a[1][1]*b[1][2] + a[1][2]*b[2][2] + a[1][3]*b[3][2] + m13 := a[1][0]*b[0][3] + a[1][1]*b[1][3] + a[1][2]*b[2][3] + a[1][3]*b[3][3] + m20 := a[2][0]*b[0][0] + a[2][1]*b[1][0] + a[2][2]*b[2][0] + a[2][3]*b[3][0] + m21 := a[2][0]*b[0][1] + a[2][1]*b[1][1] + a[2][2]*b[2][1] + a[2][3]*b[3][1] + m22 := a[2][0]*b[0][2] + a[2][1]*b[1][2] + a[2][2]*b[2][2] + a[2][3]*b[3][2] + m23 := a[2][0]*b[0][3] + a[2][1]*b[1][3] + a[2][2]*b[2][3] + a[2][3]*b[3][3] + m30 := a[3][0]*b[0][0] + a[3][1]*b[1][0] + a[3][2]*b[2][0] + a[3][3]*b[3][0] + m31 := a[3][0]*b[0][1] + a[3][1]*b[1][1] + a[3][2]*b[2][1] + a[3][3]*b[3][1] + m32 := a[3][0]*b[0][2] + a[3][1]*b[1][2] + a[3][2]*b[2][2] + a[3][3]*b[3][2] + m33 := a[3][0]*b[0][3] + a[3][1]*b[1][3] + a[3][2]*b[2][3] + a[3][3]*b[3][3] + m[0][0] = m00 + m[0][1] = m01 + m[0][2] = m02 + m[0][3] = m03 + m[1][0] = m10 + m[1][1] = m11 + m[1][2] = m12 + m[1][3] = m13 + m[2][0] = m20 + m[2][1] = m21 + m[2][2] = m22 + m[2][3] = m23 + m[3][0] = m30 + m[3][1] = m31 + m[3][2] = m32 + m[3][3] = m33 +} + +// Perspective sets m to be the GL perspective matrix. +func (m *Mat4) Perspective(fov Radian, aspect, near, far float32) { + t := Tan(float32(fov) / 2) + + m[0][0] = 1 / (aspect * t) + m[1][1] = 1 / t + m[2][2] = -(far + near) / (far - near) + m[2][3] = -1 + m[3][2] = -2 * far * near / (far - near) +} + +// Scale sets m to be a scale followed by p. +// It is equivalent to +// m.Mul(p, &Mat4{ +// {x, 0, 0, 0}, +// {0, y, 0, 0}, +// {0, 0, z, 0}, +// {0, 0, 0, 1}, +// }). +func (m *Mat4) Scale(p *Mat4, x, y, z float32) { + m[0][0] = p[0][0] * x + m[0][1] = p[0][1] * y + m[0][2] = p[0][2] * z + m[0][3] = p[0][3] + m[1][0] = p[1][0] * x + m[1][1] = p[1][1] * y + m[1][2] = p[1][2] * z + m[1][3] = p[1][3] + m[2][0] = p[2][0] * x + m[2][1] = p[2][1] * y + m[2][2] = p[2][2] * z + m[2][3] = p[2][3] + m[3][0] = p[3][0] * x + m[3][1] = p[3][1] * y + m[3][2] = p[3][2] * z + m[3][3] = p[3][3] +} + +// Translate sets m to be a translation followed by p. +// It is equivalent to +// m.Mul(p, &Mat4{ +// {1, 0, 0, x}, +// {0, 1, 0, y}, +// {0, 0, 1, z}, +// {0, 0, 0, 1}, +// }). +func (m *Mat4) Translate(p *Mat4, x, y, z float32) { + m[0][0] = p[0][0] + m[0][1] = p[0][1] + m[0][2] = p[0][2] + m[0][3] = p[0][0]*x + p[0][1]*y + p[0][2]*z + p[0][3] + m[1][0] = p[1][0] + m[1][1] = p[1][1] + m[1][2] = p[1][2] + m[1][3] = p[1][0]*x + p[1][1]*y + p[1][2]*z + p[1][3] + m[2][0] = p[2][0] + m[2][1] = p[2][1] + m[2][2] = p[2][2] + m[2][3] = p[2][0]*x + p[2][1]*y + p[2][2]*z + p[2][3] + m[3][0] = p[3][0] + m[3][1] = p[3][1] + m[3][2] = p[3][2] + m[3][3] = p[3][0]*x + p[3][1]*y + p[3][2]*z + p[3][3] +} + +// Rotate sets m to a rotation in radians around a specified axis, followed by p. +// It is equivalent to m.Mul(p, affineRotation). +func (m *Mat4) Rotate(p *Mat4, angle Radian, axis *Vec3) { + a := *axis + a.Normalize() + + c, s := Cos(float32(angle)), Sin(float32(angle)) + d := 1 - c + + m.Mul(p, &Mat4{{ + c + d*a[0]*a[1], + 0 + d*a[0]*a[1] + s*a[2], + 0 + d*a[0]*a[1] - s*a[1], + 0, + }, { + 0 + d*a[1]*a[0] - s*a[2], + c + d*a[1]*a[1], + 0 + d*a[1]*a[2] + s*a[0], + 0, + }, { + 0 + d*a[2]*a[0] + s*a[1], + 0 + d*a[2]*a[1] - s*a[0], + c + d*a[2]*a[2], + 0, + }, { + 0, 0, 0, 1, + }}) +} + +func (m *Mat4) LookAt(eye, center, up *Vec3) { + f, s, u := new(Vec3), new(Vec3), new(Vec3) + + *f = *center + f.Sub(f, eye) + f.Normalize() + + s.Cross(f, up) + s.Normalize() + u.Cross(s, f) + + *m = Mat4{ + {s[0], u[0], -f[0], 0}, + {s[1], u[1], -f[1], 0}, + {s[2], u[2], -f[2], 0}, + {-s.Dot(eye), -u.Dot(eye), +f.Dot(eye), 1}, + } +} diff --git a/src/golang.org/x/mobile/exp/f32/table.go b/src/golang.org/x/mobile/exp/f32/table.go new file mode 100644 index 0000000000..e0b5e33687 --- /dev/null +++ b/src/golang.org/x/mobile/exp/f32/table.go @@ -0,0 +1,4105 @@ +// generated by go run gen.go; DO NOT EDIT + +package f32 + +const sinTableLen = 4096 + +// sinTable[i] equals sin(i * π / sinTableLen). +var sinTable = [sinTableLen]float32{ + 0, + 0.0007669903, + 0.0015339801, + 0.002300969, + 0.0030679568, + 0.0038349426, + 0.004601926, + 0.005368907, + 0.0061358847, + 0.0069028586, + 0.007669829, + 0.008436794, + 0.009203754, + 0.00997071, + 0.010737659, + 0.011504602, + 0.012271538, + 0.013038468, + 0.0138053885, + 0.014572302, + 0.015339206, + 0.016106103, + 0.016872987, + 0.017639864, + 0.01840673, + 0.019173585, + 0.019940428, + 0.02070726, + 0.02147408, + 0.022240888, + 0.023007682, + 0.023774462, + 0.024541229, + 0.025307981, + 0.026074719, + 0.02684144, + 0.027608145, + 0.028374836, + 0.029141508, + 0.029908165, + 0.030674804, + 0.031441424, + 0.032208025, + 0.03297461, + 0.033741172, + 0.034507714, + 0.035274237, + 0.036040742, + 0.036807224, + 0.037573684, + 0.03834012, + 0.039106537, + 0.039872926, + 0.040639296, + 0.04140564, + 0.042171963, + 0.04293826, + 0.04370453, + 0.044470772, + 0.04523699, + 0.04600318, + 0.046769347, + 0.047535483, + 0.048301592, + 0.049067676, + 0.049833726, + 0.05059975, + 0.05136574, + 0.052131705, + 0.052897636, + 0.053663537, + 0.05442941, + 0.055195246, + 0.05596105, + 0.05672682, + 0.057492558, + 0.058258265, + 0.059023935, + 0.059789572, + 0.06055517, + 0.061320737, + 0.062086266, + 0.06285176, + 0.063617215, + 0.06438263, + 0.06514801, + 0.06591335, + 0.06667866, + 0.06744392, + 0.06820914, + 0.06897433, + 0.06973947, + 0.070504576, + 0.07126963, + 0.07203465, + 0.07279963, + 0.07356457, + 0.07432945, + 0.0750943, + 0.07585911, + 0.076623864, + 0.07738858, + 0.078153245, + 0.07891786, + 0.07968244, + 0.080446966, + 0.08121145, + 0.08197588, + 0.08274026, + 0.0835046, + 0.08426889, + 0.085033126, + 0.08579731, + 0.08656145, + 0.087325536, + 0.08808957, + 0.08885355, + 0.08961748, + 0.09038136, + 0.09114519, + 0.091908954, + 0.092672676, + 0.09343634, + 0.09419994, + 0.0949635, + 0.09572699, + 0.09649043, + 0.097253814, + 0.09801714, + 0.09878041, + 0.099543616, + 0.10030677, + 0.10106986, + 0.1018329, + 0.102595866, + 0.10335878, + 0.10412163, + 0.10488442, + 0.105647154, + 0.10640982, + 0.10717242, + 0.10793497, + 0.108697444, + 0.109459855, + 0.110222206, + 0.11098449, + 0.11174671, + 0.11250886, + 0.11327095, + 0.11403298, + 0.114794925, + 0.115556814, + 0.11631863, + 0.11708038, + 0.11784206, + 0.11860368, + 0.119365215, + 0.12012669, + 0.120888084, + 0.121649414, + 0.12241068, + 0.12317186, + 0.12393297, + 0.12469402, + 0.12545498, + 0.12621588, + 0.1269767, + 0.12773745, + 0.1284981, + 0.1292587, + 0.13001922, + 0.13077967, + 0.13154003, + 0.13230032, + 0.13306053, + 0.13382065, + 0.1345807, + 0.13534068, + 0.13610058, + 0.13686039, + 0.13762012, + 0.13837977, + 0.13913934, + 0.13989884, + 0.14065824, + 0.14141756, + 0.1421768, + 0.14293596, + 0.14369503, + 0.14445402, + 0.14521292, + 0.14597175, + 0.14673047, + 0.14748912, + 0.14824767, + 0.14900614, + 0.14976454, + 0.15052283, + 0.15128104, + 0.15203916, + 0.15279719, + 0.15355512, + 0.15431297, + 0.15507074, + 0.1558284, + 0.15658598, + 0.15734346, + 0.15810084, + 0.15885815, + 0.15961535, + 0.16037245, + 0.16112947, + 0.1618864, + 0.16264322, + 0.16339995, + 0.16415659, + 0.16491312, + 0.16566956, + 0.1664259, + 0.16718215, + 0.16793829, + 0.16869435, + 0.1694503, + 0.17020614, + 0.17096189, + 0.17171754, + 0.17247309, + 0.17322853, + 0.17398387, + 0.17473911, + 0.17549425, + 0.1762493, + 0.17700422, + 0.17775905, + 0.17851377, + 0.17926839, + 0.1800229, + 0.18077731, + 0.18153161, + 0.1822858, + 0.18303989, + 0.18379387, + 0.18454774, + 0.1853015, + 0.18605515, + 0.18680869, + 0.18756212, + 0.18831545, + 0.18906866, + 0.18982176, + 0.19057475, + 0.19132763, + 0.1920804, + 0.19283305, + 0.19358559, + 0.19433801, + 0.19509032, + 0.19584252, + 0.1965946, + 0.19734657, + 0.1980984, + 0.19885014, + 0.19960175, + 0.20035325, + 0.20110464, + 0.2018559, + 0.20260704, + 0.20335807, + 0.20410897, + 0.20485975, + 0.20561041, + 0.20636095, + 0.20711137, + 0.20786168, + 0.20861185, + 0.20936191, + 0.21011184, + 0.21086164, + 0.21161133, + 0.21236089, + 0.21311031, + 0.21385963, + 0.21460882, + 0.21535787, + 0.2161068, + 0.2168556, + 0.21760428, + 0.21835282, + 0.21910124, + 0.21984953, + 0.22059768, + 0.22134572, + 0.22209363, + 0.2228414, + 0.22358903, + 0.22433653, + 0.22508392, + 0.22583115, + 0.22657827, + 0.22732525, + 0.22807208, + 0.22881879, + 0.22956537, + 0.23031181, + 0.2310581, + 0.23180428, + 0.23255031, + 0.2332962, + 0.23404196, + 0.23478758, + 0.23553306, + 0.2362784, + 0.2370236, + 0.23776866, + 0.23851359, + 0.23925838, + 0.24000302, + 0.24074753, + 0.24149188, + 0.24223611, + 0.24298018, + 0.24372411, + 0.2444679, + 0.24521154, + 0.24595505, + 0.24669841, + 0.24744162, + 0.24818468, + 0.24892761, + 0.24967039, + 0.250413, + 0.2511555, + 0.2518978, + 0.25264, + 0.25338203, + 0.25412393, + 0.25486565, + 0.25560725, + 0.25634867, + 0.25708997, + 0.2578311, + 0.25857207, + 0.25931293, + 0.2600536, + 0.2607941, + 0.26153448, + 0.2622747, + 0.26301476, + 0.26375467, + 0.26449442, + 0.26523402, + 0.26597348, + 0.26671275, + 0.26745188, + 0.26819086, + 0.26892966, + 0.2696683, + 0.2704068, + 0.27114516, + 0.27188334, + 0.27262136, + 0.2733592, + 0.2740969, + 0.27483445, + 0.27557182, + 0.27630904, + 0.27704608, + 0.27778298, + 0.2785197, + 0.27925625, + 0.27999264, + 0.28072888, + 0.28146493, + 0.28220084, + 0.28293657, + 0.28367212, + 0.28440753, + 0.28514278, + 0.28587782, + 0.28661272, + 0.28734747, + 0.28808203, + 0.28881642, + 0.28955063, + 0.29028466, + 0.29101855, + 0.29175225, + 0.2924858, + 0.29321915, + 0.29395235, + 0.29468536, + 0.2954182, + 0.2961509, + 0.29688337, + 0.2976157, + 0.29834786, + 0.29907984, + 0.29981163, + 0.30054325, + 0.3012747, + 0.30200595, + 0.30273703, + 0.30346796, + 0.30419868, + 0.30492923, + 0.3056596, + 0.3063898, + 0.30711982, + 0.30784965, + 0.3085793, + 0.30930877, + 0.31003806, + 0.31076714, + 0.31149608, + 0.3122248, + 0.31295338, + 0.31368175, + 0.31440994, + 0.31513792, + 0.31586576, + 0.31659338, + 0.31732082, + 0.3180481, + 0.31877515, + 0.31950203, + 0.32022873, + 0.32095525, + 0.32168156, + 0.3224077, + 0.32313362, + 0.32385936, + 0.32458493, + 0.3253103, + 0.32603547, + 0.32676044, + 0.32748523, + 0.32820985, + 0.32893425, + 0.32965845, + 0.3303825, + 0.3311063, + 0.33182994, + 0.33255336, + 0.3332766, + 0.33399966, + 0.3347225, + 0.33544514, + 0.3361676, + 0.33688986, + 0.3376119, + 0.33833376, + 0.33905542, + 0.33977687, + 0.34049815, + 0.34121922, + 0.34194008, + 0.34266073, + 0.34338117, + 0.34410143, + 0.34482148, + 0.34554133, + 0.34626096, + 0.34698042, + 0.34769964, + 0.34841868, + 0.3491375, + 0.34985614, + 0.35057455, + 0.35129276, + 0.35201076, + 0.35272855, + 0.35344616, + 0.35416353, + 0.3548807, + 0.35559767, + 0.35631442, + 0.35703096, + 0.3577473, + 0.3584634, + 0.35917935, + 0.35989505, + 0.3606105, + 0.3613258, + 0.36204088, + 0.36275572, + 0.36347038, + 0.3641848, + 0.364899, + 0.36561298, + 0.36632678, + 0.36704034, + 0.36775368, + 0.36846682, + 0.36917976, + 0.36989245, + 0.37060493, + 0.3713172, + 0.37202924, + 0.37274107, + 0.37345266, + 0.37416407, + 0.37487522, + 0.37558618, + 0.3762969, + 0.37700742, + 0.3777177, + 0.37842774, + 0.3791376, + 0.3798472, + 0.3805566, + 0.38126576, + 0.38197473, + 0.38268343, + 0.38339192, + 0.3841002, + 0.38480824, + 0.38551605, + 0.38622364, + 0.386931, + 0.38763815, + 0.38834503, + 0.38905174, + 0.38975817, + 0.3904644, + 0.39117038, + 0.39187613, + 0.39258167, + 0.39328697, + 0.39399204, + 0.39469686, + 0.39540148, + 0.39610586, + 0.39681, + 0.3975139, + 0.39821756, + 0.398921, + 0.3996242, + 0.40032718, + 0.40102988, + 0.4017324, + 0.40243465, + 0.40313667, + 0.40383846, + 0.40454, + 0.4052413, + 0.40594238, + 0.4066432, + 0.4073438, + 0.40804416, + 0.40874428, + 0.40944415, + 0.4101438, + 0.41084316, + 0.41154233, + 0.41224122, + 0.41293988, + 0.41363832, + 0.4143365, + 0.4150344, + 0.41573212, + 0.41642955, + 0.41712677, + 0.4178237, + 0.41852042, + 0.4192169, + 0.4199131, + 0.4206091, + 0.4213048, + 0.42200026, + 0.4226955, + 0.42339048, + 0.4240852, + 0.42477968, + 0.4254739, + 0.42616788, + 0.4268616, + 0.42755508, + 0.42824832, + 0.42894128, + 0.429634, + 0.4303265, + 0.4310187, + 0.43171066, + 0.43240237, + 0.43309382, + 0.43378502, + 0.43447596, + 0.43516666, + 0.4358571, + 0.43654725, + 0.43723717, + 0.43792683, + 0.43861625, + 0.4393054, + 0.43999428, + 0.4406829, + 0.44137126, + 0.44205937, + 0.44274724, + 0.4434348, + 0.44412214, + 0.4448092, + 0.44549602, + 0.44618255, + 0.44686884, + 0.44755486, + 0.4482406, + 0.4489261, + 0.44961134, + 0.45029628, + 0.450981, + 0.45166543, + 0.45234957, + 0.45303348, + 0.4537171, + 0.45440048, + 0.45508358, + 0.4557664, + 0.45644897, + 0.45713127, + 0.4578133, + 0.45849505, + 0.45917654, + 0.45985776, + 0.46053872, + 0.4612194, + 0.4618998, + 0.46257994, + 0.4632598, + 0.46393937, + 0.46461868, + 0.46529773, + 0.4659765, + 0.466655, + 0.4673332, + 0.46801114, + 0.46868882, + 0.46936622, + 0.47004333, + 0.47072017, + 0.47139674, + 0.47207302, + 0.47274902, + 0.47342476, + 0.4741002, + 0.47477537, + 0.47545028, + 0.47612488, + 0.47679922, + 0.4774733, + 0.47814706, + 0.47882056, + 0.47949377, + 0.48016667, + 0.48083934, + 0.48151168, + 0.48218378, + 0.48285556, + 0.48352706, + 0.4841983, + 0.48486924, + 0.4855399, + 0.4862103, + 0.48688036, + 0.48755017, + 0.48821968, + 0.4888889, + 0.48955783, + 0.49022648, + 0.49089485, + 0.4915629, + 0.4922307, + 0.4928982, + 0.4935654, + 0.4942323, + 0.49489895, + 0.49556527, + 0.4962313, + 0.49689704, + 0.4975625, + 0.49822766, + 0.49889255, + 0.4995571, + 0.5002214, + 0.50088537, + 0.50154907, + 0.50221246, + 0.50287557, + 0.50353837, + 0.5042009, + 0.5048631, + 0.50552505, + 0.50618666, + 0.506848, + 0.507509, + 0.5081697, + 0.50883013, + 0.50949025, + 0.5101501, + 0.5108096, + 0.5114688, + 0.51212776, + 0.5127864, + 0.5134447, + 0.51410276, + 0.51476043, + 0.5154179, + 0.516075, + 0.5167318, + 0.5173883, + 0.51804453, + 0.5187004, + 0.519356, + 0.52001125, + 0.52066624, + 0.52132094, + 0.5219753, + 0.5226294, + 0.5232831, + 0.52393657, + 0.52458966, + 0.5252425, + 0.525895, + 0.52654725, + 0.52719915, + 0.52785075, + 0.528502, + 0.529153, + 0.52980363, + 0.530454, + 0.531104, + 0.5317537, + 0.5324031, + 0.5330522, + 0.533701, + 0.53434944, + 0.53499764, + 0.5356455, + 0.53629297, + 0.53694016, + 0.53758705, + 0.53823364, + 0.53887993, + 0.53952587, + 0.54017144, + 0.5408168, + 0.54146177, + 0.54210645, + 0.5427508, + 0.5433948, + 0.54403853, + 0.5446819, + 0.545325, + 0.54596776, + 0.5466102, + 0.5472523, + 0.54789406, + 0.5485355, + 0.54917663, + 0.5498175, + 0.55045795, + 0.55109817, + 0.55173796, + 0.5523775, + 0.5530167, + 0.55365556, + 0.5542941, + 0.55493236, + 0.55557024, + 0.5562078, + 0.556845, + 0.55748194, + 0.5581185, + 0.5587548, + 0.5593907, + 0.5600263, + 0.56066155, + 0.5612965, + 0.56193113, + 0.5625654, + 0.56319934, + 0.56383294, + 0.56446624, + 0.5650992, + 0.5657318, + 0.5663641, + 0.56699604, + 0.56762767, + 0.56825894, + 0.5688899, + 0.56952053, + 0.5701508, + 0.57078075, + 0.57141036, + 0.5720396, + 0.57266855, + 0.57329714, + 0.57392544, + 0.5745534, + 0.57518095, + 0.57580817, + 0.5764351, + 0.57706165, + 0.5776879, + 0.57831377, + 0.5789393, + 0.5795646, + 0.5801894, + 0.58081394, + 0.5814381, + 0.582062, + 0.5826855, + 0.58330864, + 0.58393145, + 0.58455396, + 0.58517605, + 0.58579785, + 0.5864193, + 0.58704036, + 0.58766115, + 0.5882816, + 0.5889016, + 0.5895213, + 0.5901407, + 0.5907597, + 0.5913784, + 0.59199667, + 0.59261465, + 0.5932323, + 0.5938496, + 0.5944665, + 0.59508306, + 0.5956993, + 0.5963152, + 0.5969307, + 0.59754586, + 0.5981607, + 0.5987752, + 0.5993893, + 0.60000306, + 0.60061646, + 0.60122955, + 0.6018422, + 0.6024546, + 0.6030666, + 0.6036782, + 0.60428953, + 0.6049005, + 0.60551107, + 0.60612124, + 0.6067311, + 0.60734063, + 0.6079498, + 0.6085586, + 0.60916704, + 0.60977507, + 0.6103828, + 0.61099017, + 0.6115972, + 0.6122038, + 0.6128101, + 0.613416, + 0.61402154, + 0.61462677, + 0.6152316, + 0.6158361, + 0.6164402, + 0.6170439, + 0.6176473, + 0.6182503, + 0.618853, + 0.6194553, + 0.6200572, + 0.62065876, + 0.62126, + 0.6218608, + 0.62246126, + 0.62306136, + 0.6236611, + 0.6242605, + 0.6248595, + 0.6254581, + 0.6260564, + 0.62665427, + 0.6272518, + 0.627849, + 0.62844574, + 0.6290422, + 0.62963825, + 0.63023394, + 0.6308292, + 0.6314242, + 0.63201874, + 0.63261294, + 0.6332068, + 0.6338002, + 0.6343933, + 0.634986, + 0.63557833, + 0.63617027, + 0.63676184, + 0.63735306, + 0.6379439, + 0.63853437, + 0.63912445, + 0.6397142, + 0.6403035, + 0.64089245, + 0.64148104, + 0.6420692, + 0.64265704, + 0.6432445, + 0.64383155, + 0.64441824, + 0.6450045, + 0.6455905, + 0.64617604, + 0.6467612, + 0.64734596, + 0.6479304, + 0.6485144, + 0.64909804, + 0.6496813, + 0.6502642, + 0.65084666, + 0.6514288, + 0.65201056, + 0.6525919, + 0.65317285, + 0.6537534, + 0.6543336, + 0.6549134, + 0.65549284, + 0.6560719, + 0.65665054, + 0.6572288, + 0.6578067, + 0.6583842, + 0.6589613, + 0.65953803, + 0.66011435, + 0.6606903, + 0.66126585, + 0.661841, + 0.6624158, + 0.66299015, + 0.66356415, + 0.6641378, + 0.664711, + 0.6652838, + 0.66585624, + 0.66642827, + 0.66699994, + 0.6675712, + 0.668142, + 0.6687125, + 0.6692826, + 0.66985226, + 0.67042154, + 0.67099047, + 0.671559, + 0.67212707, + 0.67269474, + 0.67326206, + 0.673829, + 0.6743955, + 0.6749616, + 0.6755274, + 0.6760927, + 0.6766576, + 0.6772222, + 0.6777863, + 0.67835003, + 0.67891335, + 0.6794763, + 0.68003887, + 0.680601, + 0.6811627, + 0.6817241, + 0.682285, + 0.68284553, + 0.6834057, + 0.6839654, + 0.6845247, + 0.6850837, + 0.6856422, + 0.6862003, + 0.68675804, + 0.68731534, + 0.68787223, + 0.68842876, + 0.6889849, + 0.68954057, + 0.69009584, + 0.6906507, + 0.6912052, + 0.6917592, + 0.6923129, + 0.69286615, + 0.69341904, + 0.69397146, + 0.6945235, + 0.6950751, + 0.6956263, + 0.6961771, + 0.6967275, + 0.6972775, + 0.6978271, + 0.69837624, + 0.698925, + 0.6994733, + 0.70002127, + 0.7005688, + 0.7011159, + 0.7016626, + 0.7022089, + 0.70275474, + 0.7033002, + 0.70384526, + 0.70438987, + 0.70493406, + 0.7054779, + 0.70602125, + 0.70656425, + 0.70710677, + 0.70764893, + 0.7081906, + 0.70873195, + 0.7092728, + 0.7098133, + 0.7103534, + 0.710893, + 0.7114322, + 0.711971, + 0.7125094, + 0.7130473, + 0.71358484, + 0.714122, + 0.7146587, + 0.71519494, + 0.71573085, + 0.7162663, + 0.7168013, + 0.7173359, + 0.71787006, + 0.7184038, + 0.7189371, + 0.71947, + 0.72000253, + 0.72053456, + 0.7210662, + 0.72159743, + 0.7221282, + 0.7226586, + 0.72318846, + 0.723718, + 0.7242471, + 0.72477573, + 0.72530395, + 0.7258318, + 0.7263591, + 0.7268861, + 0.72741264, + 0.7279387, + 0.72846437, + 0.7289896, + 0.7295144, + 0.7300388, + 0.73056275, + 0.7310863, + 0.7316094, + 0.732132, + 0.7326543, + 0.73317605, + 0.7336974, + 0.73421836, + 0.7347389, + 0.73525894, + 0.73577857, + 0.7362978, + 0.7368166, + 0.7373349, + 0.7378528, + 0.7383703, + 0.7388873, + 0.7394039, + 0.7399201, + 0.74043584, + 0.7409511, + 0.741466, + 0.74198043, + 0.7424944, + 0.74300796, + 0.7435211, + 0.74403375, + 0.744546, + 0.74505776, + 0.74556917, + 0.7460801, + 0.74659055, + 0.7471006, + 0.7476102, + 0.74811935, + 0.7486281, + 0.7491364, + 0.7496442, + 0.75015163, + 0.75065863, + 0.75116515, + 0.7516712, + 0.7521768, + 0.75268203, + 0.7531868, + 0.75369114, + 0.754195, + 0.7546984, + 0.7552014, + 0.7557039, + 0.756206, + 0.75670767, + 0.7572088, + 0.7577096, + 0.7582099, + 0.7587098, + 0.7592092, + 0.75970817, + 0.7602067, + 0.76070476, + 0.7612024, + 0.76169956, + 0.7621963, + 0.7626926, + 0.7631884, + 0.7636838, + 0.76417875, + 0.76467323, + 0.76516724, + 0.7656609, + 0.766154, + 0.7666467, + 0.7671389, + 0.7676307, + 0.768122, + 0.7686129, + 0.76910335, + 0.7695933, + 0.77008283, + 0.7705719, + 0.7710605, + 0.7715487, + 0.7720364, + 0.77252364, + 0.77301043, + 0.7734968, + 0.7739827, + 0.7744681, + 0.7749531, + 0.77543765, + 0.7759217, + 0.77640533, + 0.7768885, + 0.77737117, + 0.7778534, + 0.7783352, + 0.7788165, + 0.77929735, + 0.77977777, + 0.78025776, + 0.7807372, + 0.78121626, + 0.7816948, + 0.7821729, + 0.7826506, + 0.7831278, + 0.7836045, + 0.7840808, + 0.78455657, + 0.7850319, + 0.78550684, + 0.78598124, + 0.7864552, + 0.7869287, + 0.78740174, + 0.78787434, + 0.7883464, + 0.78881806, + 0.78928924, + 0.78976, + 0.7902302, + 0.7907, + 0.79116935, + 0.7916382, + 0.79210657, + 0.7925745, + 0.79304194, + 0.79350895, + 0.7939755, + 0.7944415, + 0.79490715, + 0.79537225, + 0.7958369, + 0.79630107, + 0.7967648, + 0.79722804, + 0.79769087, + 0.79815316, + 0.798615, + 0.7990764, + 0.79953724, + 0.7999977, + 0.80045766, + 0.80091715, + 0.80137616, + 0.8018347, + 0.8022928, + 0.8027504, + 0.8032075, + 0.8036642, + 0.80412036, + 0.8045761, + 0.80503136, + 0.8054861, + 0.8059404, + 0.8063942, + 0.8068476, + 0.80730045, + 0.80775285, + 0.8082047, + 0.80865616, + 0.8091071, + 0.8095576, + 0.81000763, + 0.81045717, + 0.81090623, + 0.8113549, + 0.811803, + 0.8122506, + 0.81269777, + 0.8131444, + 0.8135906, + 0.8140363, + 0.81448156, + 0.8149263, + 0.8153706, + 0.81581444, + 0.8162577, + 0.8167006, + 0.8171429, + 0.8175848, + 0.8180262, + 0.81846714, + 0.81890756, + 0.8193475, + 0.81978697, + 0.82022595, + 0.82066447, + 0.8211025, + 0.82154006, + 0.82197714, + 0.8224137, + 0.8228498, + 0.8232854, + 0.8237205, + 0.82415515, + 0.8245893, + 0.825023, + 0.82545614, + 0.8258889, + 0.82632107, + 0.8267528, + 0.827184, + 0.8276148, + 0.82804507, + 0.8284748, + 0.8289041, + 0.82933295, + 0.8297612, + 0.83018905, + 0.8306164, + 0.83104324, + 0.8314696, + 0.8318955, + 0.83232087, + 0.8327458, + 0.8331702, + 0.8335941, + 0.8340175, + 0.8344404, + 0.8348629, + 0.8352848, + 0.8357063, + 0.8361273, + 0.83654773, + 0.8369677, + 0.8373872, + 0.8378062, + 0.8382247, + 0.8386427, + 0.83906025, + 0.83947724, + 0.8398938, + 0.84030986, + 0.84072536, + 0.84114045, + 0.841555, + 0.841969, + 0.8423826, + 0.84279567, + 0.84320825, + 0.8436203, + 0.8440319, + 0.84444296, + 0.8448536, + 0.84526366, + 0.84567326, + 0.8460823, + 0.8464909, + 0.84689903, + 0.8473066, + 0.84771377, + 0.84812033, + 0.8485265, + 0.848932, + 0.84933716, + 0.84974176, + 0.8501459, + 0.85054946, + 0.85095257, + 0.8513552, + 0.8517573, + 0.8521589, + 0.85256, + 0.8529606, + 0.8533607, + 0.8537603, + 0.8541594, + 0.854558, + 0.8549561, + 0.85535365, + 0.85575074, + 0.85614735, + 0.8565434, + 0.85693896, + 0.857334, + 0.8577286, + 0.85812265, + 0.8585162, + 0.85890925, + 0.8593018, + 0.8596939, + 0.86008537, + 0.86047643, + 0.86086696, + 0.86125696, + 0.8616465, + 0.86203545, + 0.86242396, + 0.8628119, + 0.8631994, + 0.86358637, + 0.86397284, + 0.8643588, + 0.86474425, + 0.8651292, + 0.8655136, + 0.86589754, + 0.866281, + 0.8666639, + 0.86704624, + 0.8674281, + 0.8678095, + 0.86819035, + 0.8685707, + 0.86895055, + 0.86932987, + 0.86970866, + 0.87008697, + 0.8704648, + 0.87084204, + 0.87121886, + 0.8715951, + 0.87197083, + 0.87234604, + 0.8727208, + 0.873095, + 0.8734687, + 0.8738418, + 0.87421453, + 0.87458664, + 0.8749583, + 0.8753294, + 0.8757, + 0.8760701, + 0.8764397, + 0.8768087, + 0.87717724, + 0.8775453, + 0.8779128, + 0.8782798, + 0.87864625, + 0.8790122, + 0.87937766, + 0.8797426, + 0.880107, + 0.8804709, + 0.8808343, + 0.8811971, + 0.88155943, + 0.8819213, + 0.88228256, + 0.88264334, + 0.8830036, + 0.88336337, + 0.88372254, + 0.88408124, + 0.88443947, + 0.8847971, + 0.88515425, + 0.88551086, + 0.88586694, + 0.88622254, + 0.8865776, + 0.88693213, + 0.8872861, + 0.88763964, + 0.88799256, + 0.88834506, + 0.88869697, + 0.88904834, + 0.88939923, + 0.8897496, + 0.8900994, + 0.89044875, + 0.8907975, + 0.89114577, + 0.8914935, + 0.8918407, + 0.8921874, + 0.89253354, + 0.8928792, + 0.8932243, + 0.8935689, + 0.893913, + 0.8942565, + 0.8945995, + 0.894942, + 0.89528394, + 0.89562535, + 0.89596623, + 0.89630663, + 0.8966465, + 0.89698577, + 0.89732456, + 0.8976628, + 0.8980006, + 0.8983378, + 0.8986745, + 0.8990106, + 0.89934623, + 0.8996813, + 0.9000159, + 0.9003499, + 0.9006834, + 0.9010164, + 0.9013488, + 0.90168077, + 0.90201217, + 0.902343, + 0.9026733, + 0.9030031, + 0.90333235, + 0.9036611, + 0.9039893, + 0.90431696, + 0.9046441, + 0.9049707, + 0.90529674, + 0.9056223, + 0.90594727, + 0.90627176, + 0.9065957, + 0.9069191, + 0.907242, + 0.90756434, + 0.9078861, + 0.90820736, + 0.9085281, + 0.90884835, + 0.909168, + 0.9094871, + 0.9098057, + 0.91012377, + 0.9104413, + 0.91075826, + 0.91107476, + 0.91139066, + 0.91170603, + 0.91202086, + 0.91233516, + 0.912649, + 0.9129622, + 0.9132749, + 0.91358703, + 0.91389865, + 0.9142098, + 0.9145203, + 0.9148303, + 0.9151398, + 0.9154487, + 0.9157571, + 0.916065, + 0.9163723, + 0.9166791, + 0.9169853, + 0.917291, + 0.91759616, + 0.9179008, + 0.91820484, + 0.9185084, + 0.9188114, + 0.9191139, + 0.9194158, + 0.91971713, + 0.92001796, + 0.9203183, + 0.92061806, + 0.9209172, + 0.9212159, + 0.92151403, + 0.92181164, + 0.92210865, + 0.9224052, + 0.9227011, + 0.9229965, + 0.92329144, + 0.9235858, + 0.9238795, + 0.92417276, + 0.9244655, + 0.9247576, + 0.92504925, + 0.9253403, + 0.9256308, + 0.9259208, + 0.9262102, + 0.9264991, + 0.9267875, + 0.92707527, + 0.9273625, + 0.92764926, + 0.9279354, + 0.928221, + 0.9285061, + 0.9287906, + 0.9290746, + 0.929358, + 0.9296409, + 0.92992324, + 0.93020505, + 0.93048626, + 0.93076694, + 0.9310471, + 0.9313267, + 0.93160576, + 0.9318843, + 0.9321622, + 0.9324396, + 0.9327165, + 0.9329928, + 0.93326855, + 0.9335438, + 0.93381846, + 0.9340925, + 0.9343661, + 0.93463916, + 0.9349116, + 0.9351835, + 0.93545485, + 0.9357257, + 0.93599594, + 0.93626565, + 0.9365348, + 0.93680346, + 0.9370715, + 0.937339, + 0.937606, + 0.93787235, + 0.93813825, + 0.93840355, + 0.9386683, + 0.9389325, + 0.9391961, + 0.9394592, + 0.93972176, + 0.9399837, + 0.9402452, + 0.94050604, + 0.9407664, + 0.94102615, + 0.9412854, + 0.94154406, + 0.9418022, + 0.94205976, + 0.9423168, + 0.9425732, + 0.9428291, + 0.9430844, + 0.9433392, + 0.94359344, + 0.9438471, + 0.94410026, + 0.9443528, + 0.9446048, + 0.9448563, + 0.9451072, + 0.94535756, + 0.9456073, + 0.9458566, + 0.94610524, + 0.9463534, + 0.9466009, + 0.9468479, + 0.9470944, + 0.94734025, + 0.9475856, + 0.9478304, + 0.9480746, + 0.94831824, + 0.9485614, + 0.9488039, + 0.9490459, + 0.9492873, + 0.94952816, + 0.9497685, + 0.9500083, + 0.95024747, + 0.95048606, + 0.9507241, + 0.95096165, + 0.95119864, + 0.951435, + 0.9516709, + 0.95190614, + 0.95214087, + 0.952375, + 0.9526086, + 0.95284164, + 0.9530741, + 0.953306, + 0.9535374, + 0.9537682, + 0.95399845, + 0.9542281, + 0.9544572, + 0.95468575, + 0.95491374, + 0.9551412, + 0.95536804, + 0.95559436, + 0.9558201, + 0.95604527, + 0.95626986, + 0.9564939, + 0.95671743, + 0.95694035, + 0.9571627, + 0.9573845, + 0.9576057, + 0.95782644, + 0.9580465, + 0.9582661, + 0.95848507, + 0.95870346, + 0.9589213, + 0.95913863, + 0.95935535, + 0.95957154, + 0.95978713, + 0.9600021, + 0.96021664, + 0.9604305, + 0.9606439, + 0.9608566, + 0.96106887, + 0.96128047, + 0.9614916, + 0.96170205, + 0.96191204, + 0.9621214, + 0.9623302, + 0.9625385, + 0.96274614, + 0.96295327, + 0.9631598, + 0.9633658, + 0.9635712, + 0.96377605, + 0.9639804, + 0.96418405, + 0.96438724, + 0.9645898, + 0.96479183, + 0.96499324, + 0.9651941, + 0.96539444, + 0.9655942, + 0.9657934, + 0.965992, + 0.96619, + 0.96638745, + 0.9665844, + 0.9667807, + 0.96697646, + 0.96717167, + 0.9673663, + 0.96756035, + 0.9677538, + 0.96794677, + 0.9681391, + 0.96833086, + 0.9685221, + 0.96871275, + 0.9689028, + 0.9690923, + 0.96928126, + 0.9694696, + 0.96965736, + 0.9698446, + 0.97003126, + 0.97021735, + 0.97040284, + 0.9705878, + 0.97077215, + 0.9709559, + 0.97113913, + 0.9713218, + 0.9715039, + 0.9716854, + 0.9718663, + 0.97204673, + 0.9722265, + 0.97240573, + 0.97258437, + 0.97276247, + 0.97293997, + 0.9731169, + 0.97329324, + 0.973469, + 0.97364426, + 0.9738189, + 0.97399294, + 0.97416645, + 0.97433937, + 0.97451174, + 0.9746835, + 0.9748547, + 0.97502536, + 0.9751954, + 0.97536486, + 0.9755338, + 0.9757021, + 0.9758699, + 0.9760371, + 0.9762037, + 0.97636974, + 0.9765352, + 0.97670007, + 0.9768644, + 0.97702813, + 0.9771913, + 0.9773539, + 0.97751594, + 0.97767735, + 0.9778382, + 0.9779985, + 0.97815824, + 0.9783174, + 0.9784759, + 0.97863394, + 0.97879136, + 0.9789482, + 0.97910446, + 0.97926015, + 0.97941524, + 0.9795698, + 0.97972375, + 0.9798771, + 0.9800299, + 0.9801821, + 0.9803338, + 0.98048484, + 0.98063534, + 0.98078525, + 0.9809346, + 0.9810834, + 0.9812316, + 0.9813792, + 0.98152626, + 0.9816727, + 0.98181856, + 0.9819639, + 0.9821086, + 0.9822527, + 0.9823963, + 0.9825393, + 0.9826817, + 0.98282355, + 0.9829648, + 0.9831055, + 0.9832456, + 0.9833851, + 0.983524, + 0.9836624, + 0.98380023, + 0.98393744, + 0.98407406, + 0.9842101, + 0.98434556, + 0.98448044, + 0.9846148, + 0.9847485, + 0.98488164, + 0.98501426, + 0.9851462, + 0.98527765, + 0.9854085, + 0.9855387, + 0.9856684, + 0.9857975, + 0.98592603, + 0.98605394, + 0.9861813, + 0.9863081, + 0.9864343, + 0.9865599, + 0.9866849, + 0.9868094, + 0.9869333, + 0.98705655, + 0.9871793, + 0.9873014, + 0.98742294, + 0.98754394, + 0.98766434, + 0.98778415, + 0.98790336, + 0.988022, + 0.9881401, + 0.9882576, + 0.9883745, + 0.9884908, + 0.9886065, + 0.98872167, + 0.9888363, + 0.98895025, + 0.9890637, + 0.9891765, + 0.98928875, + 0.98940045, + 0.9895115, + 0.989622, + 0.98973197, + 0.9898413, + 0.98995006, + 0.9900582, + 0.9901658, + 0.9902728, + 0.9903792, + 0.9904851, + 0.99059033, + 0.990695, + 0.9907991, + 0.99090266, + 0.99100554, + 0.99110794, + 0.9912097, + 0.99131083, + 0.99141145, + 0.99151146, + 0.9916109, + 0.99170977, + 0.991808, + 0.9919057, + 0.9920028, + 0.9920993, + 0.99219525, + 0.9922906, + 0.9923853, + 0.99247956, + 0.99257314, + 0.9926661, + 0.9927586, + 0.9928504, + 0.9929417, + 0.99303234, + 0.99312246, + 0.9932119, + 0.99330086, + 0.9933892, + 0.993477, + 0.9935641, + 0.99365073, + 0.99373674, + 0.99382216, + 0.993907, + 0.9939912, + 0.9940749, + 0.99415797, + 0.99424046, + 0.99432236, + 0.99440366, + 0.9944844, + 0.9945646, + 0.99464417, + 0.99472314, + 0.9948015, + 0.9948793, + 0.99495655, + 0.9950332, + 0.99510926, + 0.9951847, + 0.9952596, + 0.9953339, + 0.99540764, + 0.9954808, + 0.9955533, + 0.99562526, + 0.9956966, + 0.9957674, + 0.9958376, + 0.99590725, + 0.99597627, + 0.9960447, + 0.9961126, + 0.9961798, + 0.9962465, + 0.9963126, + 0.9963781, + 0.99644303, + 0.9965074, + 0.9965711, + 0.9966343, + 0.9966969, + 0.9967589, + 0.9968203, + 0.9968811, + 0.9969413, + 0.997001, + 0.99706006, + 0.99711853, + 0.9971764, + 0.99723375, + 0.99729043, + 0.9973466, + 0.99740213, + 0.9974571, + 0.99751145, + 0.9975652, + 0.99761844, + 0.99767107, + 0.99772304, + 0.9977745, + 0.9978253, + 0.99787563, + 0.9979253, + 0.9979744, + 0.99802285, + 0.9980708, + 0.9981181, + 0.99816483, + 0.998211, + 0.99825656, + 0.99830157, + 0.9983459, + 0.9983897, + 0.99843293, + 0.99847555, + 0.99851763, + 0.99855906, + 0.99859995, + 0.99864024, + 0.99867994, + 0.99871904, + 0.99875754, + 0.99879545, + 0.9988328, + 0.99886954, + 0.9989057, + 0.9989413, + 0.9989763, + 0.9990107, + 0.9990445, + 0.99907774, + 0.99911034, + 0.9991424, + 0.9991739, + 0.99920475, + 0.99923503, + 0.9992648, + 0.99929386, + 0.99932235, + 0.9993503, + 0.99937767, + 0.99940443, + 0.9994306, + 0.99945617, + 0.9994812, + 0.9995056, + 0.9995294, + 0.99955267, + 0.9995753, + 0.9995974, + 0.9996188, + 0.9996397, + 0.99966, + 0.9996797, + 0.9996988, + 0.99971735, + 0.9997353, + 0.99975264, + 0.9997694, + 0.9997856, + 0.99980116, + 0.9998162, + 0.9998306, + 0.99984443, + 0.99985766, + 0.9998703, + 0.99988234, + 0.99989384, + 0.9999047, + 0.999915, + 0.9999247, + 0.99993384, + 0.99994236, + 0.9999503, + 0.9999576, + 0.9999644, + 0.9999706, + 0.99997616, + 0.99998116, + 0.9999856, + 0.9999894, + 0.99999267, + 0.9999953, + 0.9999974, + 0.9999988, + 0.9999997, + 1, + 0.9999997, + 0.9999988, + 0.9999974, + 0.9999953, + 0.99999267, + 0.9999894, + 0.9999856, + 0.99998116, + 0.99997616, + 0.9999706, + 0.9999644, + 0.9999576, + 0.9999503, + 0.99994236, + 0.99993384, + 0.9999247, + 0.999915, + 0.9999047, + 0.99989384, + 0.99988234, + 0.9998703, + 0.99985766, + 0.99984443, + 0.9998306, + 0.9998162, + 0.99980116, + 0.9997856, + 0.9997694, + 0.99975264, + 0.9997353, + 0.99971735, + 0.9996988, + 0.9996797, + 0.99966, + 0.9996397, + 0.9996188, + 0.9995974, + 0.9995753, + 0.99955267, + 0.9995294, + 0.9995056, + 0.9994812, + 0.99945617, + 0.9994306, + 0.99940443, + 0.99937767, + 0.9993503, + 0.99932235, + 0.99929386, + 0.9992648, + 0.99923503, + 0.99920475, + 0.9991739, + 0.9991424, + 0.99911034, + 0.99907774, + 0.9990445, + 0.9990107, + 0.9989763, + 0.9989413, + 0.9989057, + 0.99886954, + 0.9988328, + 0.99879545, + 0.99875754, + 0.99871904, + 0.99867994, + 0.99864024, + 0.99859995, + 0.99855906, + 0.99851763, + 0.99847555, + 0.99843293, + 0.9983897, + 0.9983459, + 0.99830157, + 0.99825656, + 0.998211, + 0.99816483, + 0.9981181, + 0.9980708, + 0.99802285, + 0.9979744, + 0.9979253, + 0.99787563, + 0.9978253, + 0.9977745, + 0.99772304, + 0.99767107, + 0.99761844, + 0.9975652, + 0.99751145, + 0.9974571, + 0.99740213, + 0.9973466, + 0.99729043, + 0.99723375, + 0.9971764, + 0.99711853, + 0.99706006, + 0.997001, + 0.9969413, + 0.9968811, + 0.9968203, + 0.9967589, + 0.9966969, + 0.9966343, + 0.9965711, + 0.9965074, + 0.99644303, + 0.9963781, + 0.9963126, + 0.9962465, + 0.9961798, + 0.9961126, + 0.9960447, + 0.99597627, + 0.99590725, + 0.9958376, + 0.9957674, + 0.9956966, + 0.99562526, + 0.9955533, + 0.9954808, + 0.99540764, + 0.9953339, + 0.9952596, + 0.9951847, + 0.99510926, + 0.9950332, + 0.99495655, + 0.9948793, + 0.9948015, + 0.99472314, + 0.99464417, + 0.9945646, + 0.9944844, + 0.99440366, + 0.99432236, + 0.99424046, + 0.99415797, + 0.9940749, + 0.9939912, + 0.993907, + 0.99382216, + 0.99373674, + 0.99365073, + 0.9935641, + 0.993477, + 0.9933892, + 0.99330086, + 0.9932119, + 0.99312246, + 0.99303234, + 0.9929417, + 0.9928504, + 0.9927586, + 0.9926661, + 0.99257314, + 0.99247956, + 0.9923853, + 0.9922906, + 0.99219525, + 0.9920993, + 0.9920028, + 0.9919057, + 0.991808, + 0.99170977, + 0.9916109, + 0.99151146, + 0.99141145, + 0.99131083, + 0.9912097, + 0.99110794, + 0.99100554, + 0.99090266, + 0.9907991, + 0.990695, + 0.99059033, + 0.9904851, + 0.9903792, + 0.9902728, + 0.9901658, + 0.9900582, + 0.98995006, + 0.9898413, + 0.98973197, + 0.989622, + 0.9895115, + 0.98940045, + 0.98928875, + 0.9891765, + 0.9890637, + 0.98895025, + 0.9888363, + 0.98872167, + 0.9886065, + 0.9884908, + 0.9883745, + 0.9882576, + 0.9881401, + 0.988022, + 0.98790336, + 0.98778415, + 0.98766434, + 0.98754394, + 0.98742294, + 0.9873014, + 0.9871793, + 0.98705655, + 0.9869333, + 0.9868094, + 0.9866849, + 0.9865599, + 0.9864343, + 0.9863081, + 0.9861813, + 0.98605394, + 0.98592603, + 0.9857975, + 0.9856684, + 0.9855387, + 0.9854085, + 0.98527765, + 0.9851462, + 0.98501426, + 0.98488164, + 0.9847485, + 0.9846148, + 0.98448044, + 0.98434556, + 0.9842101, + 0.98407406, + 0.98393744, + 0.98380023, + 0.9836624, + 0.983524, + 0.9833851, + 0.9832456, + 0.9831055, + 0.9829648, + 0.98282355, + 0.9826817, + 0.9825393, + 0.9823963, + 0.9822527, + 0.9821086, + 0.9819639, + 0.98181856, + 0.9816727, + 0.98152626, + 0.9813792, + 0.9812316, + 0.9810834, + 0.9809346, + 0.98078525, + 0.98063534, + 0.98048484, + 0.9803338, + 0.9801821, + 0.9800299, + 0.9798771, + 0.97972375, + 0.9795698, + 0.97941524, + 0.97926015, + 0.97910446, + 0.9789482, + 0.97879136, + 0.97863394, + 0.9784759, + 0.9783174, + 0.97815824, + 0.9779985, + 0.9778382, + 0.97767735, + 0.97751594, + 0.9773539, + 0.9771913, + 0.97702813, + 0.9768644, + 0.97670007, + 0.9765352, + 0.97636974, + 0.9762037, + 0.9760371, + 0.9758699, + 0.9757021, + 0.9755338, + 0.97536486, + 0.9751954, + 0.97502536, + 0.9748547, + 0.9746835, + 0.97451174, + 0.97433937, + 0.97416645, + 0.97399294, + 0.9738189, + 0.97364426, + 0.973469, + 0.97329324, + 0.9731169, + 0.97293997, + 0.97276247, + 0.97258437, + 0.97240573, + 0.9722265, + 0.97204673, + 0.9718663, + 0.9716854, + 0.9715039, + 0.9713218, + 0.97113913, + 0.9709559, + 0.97077215, + 0.9705878, + 0.97040284, + 0.97021735, + 0.97003126, + 0.9698446, + 0.96965736, + 0.9694696, + 0.96928126, + 0.9690923, + 0.9689028, + 0.96871275, + 0.9685221, + 0.96833086, + 0.9681391, + 0.96794677, + 0.9677538, + 0.96756035, + 0.9673663, + 0.96717167, + 0.96697646, + 0.9667807, + 0.9665844, + 0.96638745, + 0.96619, + 0.965992, + 0.9657934, + 0.9655942, + 0.96539444, + 0.9651941, + 0.96499324, + 0.96479183, + 0.9645898, + 0.96438724, + 0.96418405, + 0.9639804, + 0.96377605, + 0.9635712, + 0.9633658, + 0.9631598, + 0.96295327, + 0.96274614, + 0.9625385, + 0.9623302, + 0.9621214, + 0.96191204, + 0.96170205, + 0.9614916, + 0.96128047, + 0.96106887, + 0.9608566, + 0.9606439, + 0.9604305, + 0.96021664, + 0.9600021, + 0.95978713, + 0.95957154, + 0.95935535, + 0.95913863, + 0.9589213, + 0.95870346, + 0.95848507, + 0.9582661, + 0.9580465, + 0.95782644, + 0.9576057, + 0.9573845, + 0.9571627, + 0.95694035, + 0.95671743, + 0.9564939, + 0.95626986, + 0.95604527, + 0.9558201, + 0.95559436, + 0.95536804, + 0.9551412, + 0.95491374, + 0.95468575, + 0.9544572, + 0.9542281, + 0.95399845, + 0.9537682, + 0.9535374, + 0.953306, + 0.9530741, + 0.95284164, + 0.9526086, + 0.952375, + 0.95214087, + 0.95190614, + 0.9516709, + 0.951435, + 0.95119864, + 0.95096165, + 0.9507241, + 0.95048606, + 0.95024747, + 0.9500083, + 0.9497685, + 0.94952816, + 0.9492873, + 0.9490459, + 0.9488039, + 0.9485614, + 0.94831824, + 0.9480746, + 0.9478304, + 0.9475856, + 0.94734025, + 0.9470944, + 0.9468479, + 0.9466009, + 0.9463534, + 0.94610524, + 0.9458566, + 0.9456073, + 0.94535756, + 0.9451072, + 0.9448563, + 0.9446048, + 0.9443528, + 0.94410026, + 0.9438471, + 0.94359344, + 0.9433392, + 0.9430844, + 0.9428291, + 0.9425732, + 0.9423168, + 0.94205976, + 0.9418022, + 0.94154406, + 0.9412854, + 0.94102615, + 0.9407664, + 0.94050604, + 0.9402452, + 0.9399837, + 0.93972176, + 0.9394592, + 0.9391961, + 0.9389325, + 0.9386683, + 0.93840355, + 0.93813825, + 0.93787235, + 0.937606, + 0.937339, + 0.9370715, + 0.93680346, + 0.9365348, + 0.93626565, + 0.93599594, + 0.9357257, + 0.93545485, + 0.9351835, + 0.9349116, + 0.93463916, + 0.9343661, + 0.9340925, + 0.93381846, + 0.9335438, + 0.93326855, + 0.9329928, + 0.9327165, + 0.9324396, + 0.9321622, + 0.9318843, + 0.93160576, + 0.9313267, + 0.9310471, + 0.93076694, + 0.93048626, + 0.93020505, + 0.92992324, + 0.9296409, + 0.929358, + 0.9290746, + 0.9287906, + 0.9285061, + 0.928221, + 0.9279354, + 0.92764926, + 0.9273625, + 0.92707527, + 0.9267875, + 0.9264991, + 0.9262102, + 0.9259208, + 0.9256308, + 0.9253403, + 0.92504925, + 0.9247576, + 0.9244655, + 0.92417276, + 0.9238795, + 0.9235858, + 0.92329144, + 0.9229965, + 0.9227011, + 0.9224052, + 0.92210865, + 0.92181164, + 0.92151403, + 0.9212159, + 0.9209172, + 0.92061806, + 0.9203183, + 0.92001796, + 0.91971713, + 0.9194158, + 0.9191139, + 0.9188114, + 0.9185084, + 0.91820484, + 0.9179008, + 0.91759616, + 0.917291, + 0.9169853, + 0.9166791, + 0.9163723, + 0.916065, + 0.9157571, + 0.9154487, + 0.9151398, + 0.9148303, + 0.9145203, + 0.9142098, + 0.91389865, + 0.91358703, + 0.9132749, + 0.9129622, + 0.912649, + 0.91233516, + 0.91202086, + 0.91170603, + 0.91139066, + 0.91107476, + 0.91075826, + 0.9104413, + 0.91012377, + 0.9098057, + 0.9094871, + 0.909168, + 0.90884835, + 0.9085281, + 0.90820736, + 0.9078861, + 0.90756434, + 0.907242, + 0.9069191, + 0.9065957, + 0.90627176, + 0.90594727, + 0.9056223, + 0.90529674, + 0.9049707, + 0.9046441, + 0.90431696, + 0.9039893, + 0.9036611, + 0.90333235, + 0.9030031, + 0.9026733, + 0.902343, + 0.90201217, + 0.90168077, + 0.9013488, + 0.9010164, + 0.9006834, + 0.9003499, + 0.9000159, + 0.8996813, + 0.89934623, + 0.8990106, + 0.8986745, + 0.8983378, + 0.8980006, + 0.8976628, + 0.89732456, + 0.89698577, + 0.8966465, + 0.89630663, + 0.89596623, + 0.89562535, + 0.89528394, + 0.894942, + 0.8945995, + 0.8942565, + 0.893913, + 0.8935689, + 0.8932243, + 0.8928792, + 0.89253354, + 0.8921874, + 0.8918407, + 0.8914935, + 0.89114577, + 0.8907975, + 0.89044875, + 0.8900994, + 0.8897496, + 0.88939923, + 0.88904834, + 0.88869697, + 0.88834506, + 0.88799256, + 0.88763964, + 0.8872861, + 0.88693213, + 0.8865776, + 0.88622254, + 0.88586694, + 0.88551086, + 0.88515425, + 0.8847971, + 0.88443947, + 0.88408124, + 0.88372254, + 0.88336337, + 0.8830036, + 0.88264334, + 0.88228256, + 0.8819213, + 0.88155943, + 0.8811971, + 0.8808343, + 0.8804709, + 0.880107, + 0.8797426, + 0.87937766, + 0.8790122, + 0.87864625, + 0.8782798, + 0.8779128, + 0.8775453, + 0.87717724, + 0.8768087, + 0.8764397, + 0.8760701, + 0.8757, + 0.8753294, + 0.8749583, + 0.87458664, + 0.87421453, + 0.8738418, + 0.8734687, + 0.873095, + 0.8727208, + 0.87234604, + 0.87197083, + 0.8715951, + 0.87121886, + 0.87084204, + 0.8704648, + 0.87008697, + 0.86970866, + 0.86932987, + 0.86895055, + 0.8685707, + 0.86819035, + 0.8678095, + 0.8674281, + 0.86704624, + 0.8666639, + 0.866281, + 0.86589754, + 0.8655136, + 0.8651292, + 0.86474425, + 0.8643588, + 0.86397284, + 0.86358637, + 0.8631994, + 0.8628119, + 0.86242396, + 0.86203545, + 0.8616465, + 0.86125696, + 0.86086696, + 0.86047643, + 0.86008537, + 0.8596939, + 0.8593018, + 0.85890925, + 0.8585162, + 0.85812265, + 0.8577286, + 0.857334, + 0.85693896, + 0.8565434, + 0.85614735, + 0.85575074, + 0.85535365, + 0.8549561, + 0.854558, + 0.8541594, + 0.8537603, + 0.8533607, + 0.8529606, + 0.85256, + 0.8521589, + 0.8517573, + 0.8513552, + 0.85095257, + 0.85054946, + 0.8501459, + 0.84974176, + 0.84933716, + 0.848932, + 0.8485265, + 0.84812033, + 0.84771377, + 0.8473066, + 0.84689903, + 0.8464909, + 0.8460823, + 0.84567326, + 0.84526366, + 0.8448536, + 0.84444296, + 0.8440319, + 0.8436203, + 0.84320825, + 0.84279567, + 0.8423826, + 0.841969, + 0.841555, + 0.84114045, + 0.84072536, + 0.84030986, + 0.8398938, + 0.83947724, + 0.83906025, + 0.8386427, + 0.8382247, + 0.8378062, + 0.8373872, + 0.8369677, + 0.83654773, + 0.8361273, + 0.8357063, + 0.8352848, + 0.8348629, + 0.8344404, + 0.8340175, + 0.8335941, + 0.8331702, + 0.8327458, + 0.83232087, + 0.8318955, + 0.8314696, + 0.83104324, + 0.8306164, + 0.83018905, + 0.8297612, + 0.82933295, + 0.8289041, + 0.8284748, + 0.82804507, + 0.8276148, + 0.827184, + 0.8267528, + 0.82632107, + 0.8258889, + 0.82545614, + 0.825023, + 0.8245893, + 0.82415515, + 0.8237205, + 0.8232854, + 0.8228498, + 0.8224137, + 0.82197714, + 0.82154006, + 0.8211025, + 0.82066447, + 0.82022595, + 0.81978697, + 0.8193475, + 0.81890756, + 0.81846714, + 0.8180262, + 0.8175848, + 0.8171429, + 0.8167006, + 0.8162577, + 0.81581444, + 0.8153706, + 0.8149263, + 0.81448156, + 0.8140363, + 0.8135906, + 0.8131444, + 0.81269777, + 0.8122506, + 0.811803, + 0.8113549, + 0.81090623, + 0.81045717, + 0.81000763, + 0.8095576, + 0.8091071, + 0.80865616, + 0.8082047, + 0.80775285, + 0.80730045, + 0.8068476, + 0.8063942, + 0.8059404, + 0.8054861, + 0.80503136, + 0.8045761, + 0.80412036, + 0.8036642, + 0.8032075, + 0.8027504, + 0.8022928, + 0.8018347, + 0.80137616, + 0.80091715, + 0.80045766, + 0.7999977, + 0.79953724, + 0.7990764, + 0.798615, + 0.79815316, + 0.79769087, + 0.79722804, + 0.7967648, + 0.79630107, + 0.7958369, + 0.79537225, + 0.79490715, + 0.7944415, + 0.7939755, + 0.79350895, + 0.79304194, + 0.7925745, + 0.79210657, + 0.7916382, + 0.79116935, + 0.7907, + 0.7902302, + 0.78976, + 0.78928924, + 0.78881806, + 0.7883464, + 0.78787434, + 0.78740174, + 0.7869287, + 0.7864552, + 0.78598124, + 0.78550684, + 0.7850319, + 0.78455657, + 0.7840808, + 0.7836045, + 0.7831278, + 0.7826506, + 0.7821729, + 0.7816948, + 0.78121626, + 0.7807372, + 0.78025776, + 0.77977777, + 0.77929735, + 0.7788165, + 0.7783352, + 0.7778534, + 0.77737117, + 0.7768885, + 0.77640533, + 0.7759217, + 0.77543765, + 0.7749531, + 0.7744681, + 0.7739827, + 0.7734968, + 0.77301043, + 0.77252364, + 0.7720364, + 0.7715487, + 0.7710605, + 0.7705719, + 0.77008283, + 0.7695933, + 0.76910335, + 0.7686129, + 0.768122, + 0.7676307, + 0.7671389, + 0.7666467, + 0.766154, + 0.7656609, + 0.76516724, + 0.76467323, + 0.76417875, + 0.7636838, + 0.7631884, + 0.7626926, + 0.7621963, + 0.76169956, + 0.7612024, + 0.76070476, + 0.7602067, + 0.75970817, + 0.7592092, + 0.7587098, + 0.7582099, + 0.7577096, + 0.7572088, + 0.75670767, + 0.756206, + 0.7557039, + 0.7552014, + 0.7546984, + 0.754195, + 0.75369114, + 0.7531868, + 0.75268203, + 0.7521768, + 0.7516712, + 0.75116515, + 0.75065863, + 0.75015163, + 0.7496442, + 0.7491364, + 0.7486281, + 0.74811935, + 0.7476102, + 0.7471006, + 0.74659055, + 0.7460801, + 0.74556917, + 0.74505776, + 0.744546, + 0.74403375, + 0.7435211, + 0.74300796, + 0.7424944, + 0.74198043, + 0.741466, + 0.7409511, + 0.74043584, + 0.7399201, + 0.7394039, + 0.7388873, + 0.7383703, + 0.7378528, + 0.7373349, + 0.7368166, + 0.7362978, + 0.73577857, + 0.73525894, + 0.7347389, + 0.73421836, + 0.7336974, + 0.73317605, + 0.7326543, + 0.732132, + 0.7316094, + 0.7310863, + 0.73056275, + 0.7300388, + 0.7295144, + 0.7289896, + 0.72846437, + 0.7279387, + 0.72741264, + 0.7268861, + 0.7263591, + 0.7258318, + 0.72530395, + 0.72477573, + 0.7242471, + 0.723718, + 0.72318846, + 0.7226586, + 0.7221282, + 0.72159743, + 0.7210662, + 0.72053456, + 0.72000253, + 0.71947, + 0.7189371, + 0.7184038, + 0.71787006, + 0.7173359, + 0.7168013, + 0.7162663, + 0.71573085, + 0.71519494, + 0.7146587, + 0.714122, + 0.71358484, + 0.7130473, + 0.7125094, + 0.711971, + 0.7114322, + 0.710893, + 0.7103534, + 0.7098133, + 0.7092728, + 0.70873195, + 0.7081906, + 0.70764893, + 0.70710677, + 0.70656425, + 0.70602125, + 0.7054779, + 0.70493406, + 0.70438987, + 0.70384526, + 0.7033002, + 0.70275474, + 0.7022089, + 0.7016626, + 0.7011159, + 0.7005688, + 0.70002127, + 0.6994733, + 0.698925, + 0.69837624, + 0.6978271, + 0.6972775, + 0.6967275, + 0.6961771, + 0.6956263, + 0.6950751, + 0.6945235, + 0.69397146, + 0.69341904, + 0.69286615, + 0.6923129, + 0.6917592, + 0.6912052, + 0.6906507, + 0.69009584, + 0.68954057, + 0.6889849, + 0.68842876, + 0.68787223, + 0.68731534, + 0.68675804, + 0.6862003, + 0.6856422, + 0.6850837, + 0.6845247, + 0.6839654, + 0.6834057, + 0.68284553, + 0.682285, + 0.6817241, + 0.6811627, + 0.680601, + 0.68003887, + 0.6794763, + 0.67891335, + 0.67835003, + 0.6777863, + 0.6772222, + 0.6766576, + 0.6760927, + 0.6755274, + 0.6749616, + 0.6743955, + 0.673829, + 0.67326206, + 0.67269474, + 0.67212707, + 0.671559, + 0.67099047, + 0.67042154, + 0.66985226, + 0.6692826, + 0.6687125, + 0.668142, + 0.6675712, + 0.66699994, + 0.66642827, + 0.66585624, + 0.6652838, + 0.664711, + 0.6641378, + 0.66356415, + 0.66299015, + 0.6624158, + 0.661841, + 0.66126585, + 0.6606903, + 0.66011435, + 0.65953803, + 0.6589613, + 0.6583842, + 0.6578067, + 0.6572288, + 0.65665054, + 0.6560719, + 0.65549284, + 0.6549134, + 0.6543336, + 0.6537534, + 0.65317285, + 0.6525919, + 0.65201056, + 0.6514288, + 0.65084666, + 0.6502642, + 0.6496813, + 0.64909804, + 0.6485144, + 0.6479304, + 0.64734596, + 0.6467612, + 0.64617604, + 0.6455905, + 0.6450045, + 0.64441824, + 0.64383155, + 0.6432445, + 0.64265704, + 0.6420692, + 0.64148104, + 0.64089245, + 0.6403035, + 0.6397142, + 0.63912445, + 0.63853437, + 0.6379439, + 0.63735306, + 0.63676184, + 0.63617027, + 0.63557833, + 0.634986, + 0.6343933, + 0.6338002, + 0.6332068, + 0.63261294, + 0.63201874, + 0.6314242, + 0.6308292, + 0.63023394, + 0.62963825, + 0.6290422, + 0.62844574, + 0.627849, + 0.6272518, + 0.62665427, + 0.6260564, + 0.6254581, + 0.6248595, + 0.6242605, + 0.6236611, + 0.62306136, + 0.62246126, + 0.6218608, + 0.62126, + 0.62065876, + 0.6200572, + 0.6194553, + 0.618853, + 0.6182503, + 0.6176473, + 0.6170439, + 0.6164402, + 0.6158361, + 0.6152316, + 0.61462677, + 0.61402154, + 0.613416, + 0.6128101, + 0.6122038, + 0.6115972, + 0.61099017, + 0.6103828, + 0.60977507, + 0.60916704, + 0.6085586, + 0.6079498, + 0.60734063, + 0.6067311, + 0.60612124, + 0.60551107, + 0.6049005, + 0.60428953, + 0.6036782, + 0.6030666, + 0.6024546, + 0.6018422, + 0.60122955, + 0.60061646, + 0.60000306, + 0.5993893, + 0.5987752, + 0.5981607, + 0.59754586, + 0.5969307, + 0.5963152, + 0.5956993, + 0.59508306, + 0.5944665, + 0.5938496, + 0.5932323, + 0.59261465, + 0.59199667, + 0.5913784, + 0.5907597, + 0.5901407, + 0.5895213, + 0.5889016, + 0.5882816, + 0.58766115, + 0.58704036, + 0.5864193, + 0.58579785, + 0.58517605, + 0.58455396, + 0.58393145, + 0.58330864, + 0.5826855, + 0.582062, + 0.5814381, + 0.58081394, + 0.5801894, + 0.5795646, + 0.5789393, + 0.57831377, + 0.5776879, + 0.57706165, + 0.5764351, + 0.57580817, + 0.57518095, + 0.5745534, + 0.57392544, + 0.57329714, + 0.57266855, + 0.5720396, + 0.57141036, + 0.57078075, + 0.5701508, + 0.56952053, + 0.5688899, + 0.56825894, + 0.56762767, + 0.56699604, + 0.5663641, + 0.5657318, + 0.5650992, + 0.56446624, + 0.56383294, + 0.56319934, + 0.5625654, + 0.56193113, + 0.5612965, + 0.56066155, + 0.5600263, + 0.5593907, + 0.5587548, + 0.5581185, + 0.55748194, + 0.556845, + 0.5562078, + 0.55557024, + 0.55493236, + 0.5542941, + 0.55365556, + 0.5530167, + 0.5523775, + 0.55173796, + 0.55109817, + 0.55045795, + 0.5498175, + 0.54917663, + 0.5485355, + 0.54789406, + 0.5472523, + 0.5466102, + 0.54596776, + 0.545325, + 0.5446819, + 0.54403853, + 0.5433948, + 0.5427508, + 0.54210645, + 0.54146177, + 0.5408168, + 0.54017144, + 0.53952587, + 0.53887993, + 0.53823364, + 0.53758705, + 0.53694016, + 0.53629297, + 0.5356455, + 0.53499764, + 0.53434944, + 0.533701, + 0.5330522, + 0.5324031, + 0.5317537, + 0.531104, + 0.530454, + 0.52980363, + 0.529153, + 0.528502, + 0.52785075, + 0.52719915, + 0.52654725, + 0.525895, + 0.5252425, + 0.52458966, + 0.52393657, + 0.5232831, + 0.5226294, + 0.5219753, + 0.52132094, + 0.52066624, + 0.52001125, + 0.519356, + 0.5187004, + 0.51804453, + 0.5173883, + 0.5167318, + 0.516075, + 0.5154179, + 0.51476043, + 0.51410276, + 0.5134447, + 0.5127864, + 0.51212776, + 0.5114688, + 0.5108096, + 0.5101501, + 0.50949025, + 0.50883013, + 0.5081697, + 0.507509, + 0.506848, + 0.50618666, + 0.50552505, + 0.5048631, + 0.5042009, + 0.50353837, + 0.50287557, + 0.50221246, + 0.50154907, + 0.50088537, + 0.5002214, + 0.4995571, + 0.49889255, + 0.49822766, + 0.4975625, + 0.49689704, + 0.4962313, + 0.49556527, + 0.49489895, + 0.4942323, + 0.4935654, + 0.4928982, + 0.4922307, + 0.4915629, + 0.49089485, + 0.49022648, + 0.48955783, + 0.4888889, + 0.48821968, + 0.48755017, + 0.48688036, + 0.4862103, + 0.4855399, + 0.48486924, + 0.4841983, + 0.48352706, + 0.48285556, + 0.48218378, + 0.48151168, + 0.48083934, + 0.48016667, + 0.47949377, + 0.47882056, + 0.47814706, + 0.4774733, + 0.47679922, + 0.47612488, + 0.47545028, + 0.47477537, + 0.4741002, + 0.47342476, + 0.47274902, + 0.47207302, + 0.47139674, + 0.47072017, + 0.47004333, + 0.46936622, + 0.46868882, + 0.46801114, + 0.4673332, + 0.466655, + 0.4659765, + 0.46529773, + 0.46461868, + 0.46393937, + 0.4632598, + 0.46257994, + 0.4618998, + 0.4612194, + 0.46053872, + 0.45985776, + 0.45917654, + 0.45849505, + 0.4578133, + 0.45713127, + 0.45644897, + 0.4557664, + 0.45508358, + 0.45440048, + 0.4537171, + 0.45303348, + 0.45234957, + 0.45166543, + 0.450981, + 0.45029628, + 0.44961134, + 0.4489261, + 0.4482406, + 0.44755486, + 0.44686884, + 0.44618255, + 0.44549602, + 0.4448092, + 0.44412214, + 0.4434348, + 0.44274724, + 0.44205937, + 0.44137126, + 0.4406829, + 0.43999428, + 0.4393054, + 0.43861625, + 0.43792683, + 0.43723717, + 0.43654725, + 0.4358571, + 0.43516666, + 0.43447596, + 0.43378502, + 0.43309382, + 0.43240237, + 0.43171066, + 0.4310187, + 0.4303265, + 0.429634, + 0.42894128, + 0.42824832, + 0.42755508, + 0.4268616, + 0.42616788, + 0.4254739, + 0.42477968, + 0.4240852, + 0.42339048, + 0.4226955, + 0.42200026, + 0.4213048, + 0.4206091, + 0.4199131, + 0.4192169, + 0.41852042, + 0.4178237, + 0.41712677, + 0.41642955, + 0.41573212, + 0.4150344, + 0.4143365, + 0.41363832, + 0.41293988, + 0.41224122, + 0.41154233, + 0.41084316, + 0.4101438, + 0.40944415, + 0.40874428, + 0.40804416, + 0.4073438, + 0.4066432, + 0.40594238, + 0.4052413, + 0.40454, + 0.40383846, + 0.40313667, + 0.40243465, + 0.4017324, + 0.40102988, + 0.40032718, + 0.3996242, + 0.398921, + 0.39821756, + 0.3975139, + 0.39681, + 0.39610586, + 0.39540148, + 0.39469686, + 0.39399204, + 0.39328697, + 0.39258167, + 0.39187613, + 0.39117038, + 0.3904644, + 0.38975817, + 0.38905174, + 0.38834503, + 0.38763815, + 0.386931, + 0.38622364, + 0.38551605, + 0.38480824, + 0.3841002, + 0.38339192, + 0.38268343, + 0.38197473, + 0.38126576, + 0.3805566, + 0.3798472, + 0.3791376, + 0.37842774, + 0.3777177, + 0.37700742, + 0.3762969, + 0.37558618, + 0.37487522, + 0.37416407, + 0.37345266, + 0.37274107, + 0.37202924, + 0.3713172, + 0.37060493, + 0.36989245, + 0.36917976, + 0.36846682, + 0.36775368, + 0.36704034, + 0.36632678, + 0.36561298, + 0.364899, + 0.3641848, + 0.36347038, + 0.36275572, + 0.36204088, + 0.3613258, + 0.3606105, + 0.35989505, + 0.35917935, + 0.3584634, + 0.3577473, + 0.35703096, + 0.35631442, + 0.35559767, + 0.3548807, + 0.35416353, + 0.35344616, + 0.35272855, + 0.35201076, + 0.35129276, + 0.35057455, + 0.34985614, + 0.3491375, + 0.34841868, + 0.34769964, + 0.34698042, + 0.34626096, + 0.34554133, + 0.34482148, + 0.34410143, + 0.34338117, + 0.34266073, + 0.34194008, + 0.34121922, + 0.34049815, + 0.33977687, + 0.33905542, + 0.33833376, + 0.3376119, + 0.33688986, + 0.3361676, + 0.33544514, + 0.3347225, + 0.33399966, + 0.3332766, + 0.33255336, + 0.33182994, + 0.3311063, + 0.3303825, + 0.32965845, + 0.32893425, + 0.32820985, + 0.32748523, + 0.32676044, + 0.32603547, + 0.3253103, + 0.32458493, + 0.32385936, + 0.32313362, + 0.3224077, + 0.32168156, + 0.32095525, + 0.32022873, + 0.31950203, + 0.31877515, + 0.3180481, + 0.31732082, + 0.31659338, + 0.31586576, + 0.31513792, + 0.31440994, + 0.31368175, + 0.31295338, + 0.3122248, + 0.31149608, + 0.31076714, + 0.31003806, + 0.30930877, + 0.3085793, + 0.30784965, + 0.30711982, + 0.3063898, + 0.3056596, + 0.30492923, + 0.30419868, + 0.30346796, + 0.30273703, + 0.30200595, + 0.3012747, + 0.30054325, + 0.29981163, + 0.29907984, + 0.29834786, + 0.2976157, + 0.29688337, + 0.2961509, + 0.2954182, + 0.29468536, + 0.29395235, + 0.29321915, + 0.2924858, + 0.29175225, + 0.29101855, + 0.29028466, + 0.28955063, + 0.28881642, + 0.28808203, + 0.28734747, + 0.28661272, + 0.28587782, + 0.28514278, + 0.28440753, + 0.28367212, + 0.28293657, + 0.28220084, + 0.28146493, + 0.28072888, + 0.27999264, + 0.27925625, + 0.2785197, + 0.27778298, + 0.27704608, + 0.27630904, + 0.27557182, + 0.27483445, + 0.2740969, + 0.2733592, + 0.27262136, + 0.27188334, + 0.27114516, + 0.2704068, + 0.2696683, + 0.26892966, + 0.26819086, + 0.26745188, + 0.26671275, + 0.26597348, + 0.26523402, + 0.26449442, + 0.26375467, + 0.26301476, + 0.2622747, + 0.26153448, + 0.2607941, + 0.2600536, + 0.25931293, + 0.25857207, + 0.2578311, + 0.25708997, + 0.25634867, + 0.25560725, + 0.25486565, + 0.25412393, + 0.25338203, + 0.25264, + 0.2518978, + 0.2511555, + 0.250413, + 0.24967039, + 0.24892761, + 0.24818468, + 0.24744162, + 0.24669841, + 0.24595505, + 0.24521154, + 0.2444679, + 0.24372411, + 0.24298018, + 0.24223611, + 0.24149188, + 0.24074753, + 0.24000302, + 0.23925838, + 0.23851359, + 0.23776866, + 0.2370236, + 0.2362784, + 0.23553306, + 0.23478758, + 0.23404196, + 0.2332962, + 0.23255031, + 0.23180428, + 0.2310581, + 0.23031181, + 0.22956537, + 0.22881879, + 0.22807208, + 0.22732525, + 0.22657827, + 0.22583115, + 0.22508392, + 0.22433653, + 0.22358903, + 0.2228414, + 0.22209363, + 0.22134572, + 0.22059768, + 0.21984953, + 0.21910124, + 0.21835282, + 0.21760428, + 0.2168556, + 0.2161068, + 0.21535787, + 0.21460882, + 0.21385963, + 0.21311031, + 0.21236089, + 0.21161133, + 0.21086164, + 0.21011184, + 0.20936191, + 0.20861185, + 0.20786168, + 0.20711137, + 0.20636095, + 0.20561041, + 0.20485975, + 0.20410897, + 0.20335807, + 0.20260704, + 0.2018559, + 0.20110464, + 0.20035325, + 0.19960175, + 0.19885014, + 0.1980984, + 0.19734657, + 0.1965946, + 0.19584252, + 0.19509032, + 0.19433801, + 0.19358559, + 0.19283305, + 0.1920804, + 0.19132763, + 0.19057475, + 0.18982176, + 0.18906866, + 0.18831545, + 0.18756212, + 0.18680869, + 0.18605515, + 0.1853015, + 0.18454774, + 0.18379387, + 0.18303989, + 0.1822858, + 0.18153161, + 0.18077731, + 0.1800229, + 0.17926839, + 0.17851377, + 0.17775905, + 0.17700422, + 0.1762493, + 0.17549425, + 0.17473911, + 0.17398387, + 0.17322853, + 0.17247309, + 0.17171754, + 0.17096189, + 0.17020614, + 0.1694503, + 0.16869435, + 0.16793829, + 0.16718215, + 0.1664259, + 0.16566956, + 0.16491312, + 0.16415659, + 0.16339995, + 0.16264322, + 0.1618864, + 0.16112947, + 0.16037245, + 0.15961535, + 0.15885815, + 0.15810084, + 0.15734346, + 0.15658598, + 0.1558284, + 0.15507074, + 0.15431297, + 0.15355512, + 0.15279719, + 0.15203916, + 0.15128104, + 0.15052283, + 0.14976454, + 0.14900614, + 0.14824767, + 0.14748912, + 0.14673047, + 0.14597175, + 0.14521292, + 0.14445402, + 0.14369503, + 0.14293596, + 0.1421768, + 0.14141756, + 0.14065824, + 0.13989884, + 0.13913934, + 0.13837977, + 0.13762012, + 0.13686039, + 0.13610058, + 0.13534068, + 0.1345807, + 0.13382065, + 0.13306053, + 0.13230032, + 0.13154003, + 0.13077967, + 0.13001922, + 0.1292587, + 0.1284981, + 0.12773745, + 0.1269767, + 0.12621588, + 0.12545498, + 0.12469402, + 0.12393297, + 0.12317186, + 0.12241068, + 0.121649414, + 0.120888084, + 0.12012669, + 0.119365215, + 0.11860368, + 0.11784206, + 0.11708038, + 0.11631863, + 0.115556814, + 0.114794925, + 0.11403298, + 0.11327095, + 0.11250886, + 0.11174671, + 0.11098449, + 0.110222206, + 0.109459855, + 0.108697444, + 0.10793497, + 0.10717242, + 0.10640982, + 0.105647154, + 0.10488442, + 0.10412163, + 0.10335878, + 0.102595866, + 0.1018329, + 0.10106986, + 0.10030677, + 0.099543616, + 0.09878041, + 0.09801714, + 0.097253814, + 0.09649043, + 0.09572699, + 0.0949635, + 0.09419994, + 0.09343634, + 0.092672676, + 0.091908954, + 0.09114519, + 0.09038136, + 0.08961748, + 0.08885355, + 0.08808957, + 0.087325536, + 0.08656145, + 0.08579731, + 0.085033126, + 0.08426889, + 0.0835046, + 0.08274026, + 0.08197588, + 0.08121145, + 0.080446966, + 0.07968244, + 0.07891786, + 0.078153245, + 0.07738858, + 0.076623864, + 0.07585911, + 0.0750943, + 0.07432945, + 0.07356457, + 0.07279963, + 0.07203465, + 0.07126963, + 0.070504576, + 0.06973947, + 0.06897433, + 0.06820914, + 0.06744392, + 0.06667866, + 0.06591335, + 0.06514801, + 0.06438263, + 0.063617215, + 0.06285176, + 0.062086266, + 0.061320737, + 0.06055517, + 0.059789572, + 0.059023935, + 0.058258265, + 0.057492558, + 0.05672682, + 0.05596105, + 0.055195246, + 0.05442941, + 0.053663537, + 0.052897636, + 0.052131705, + 0.05136574, + 0.05059975, + 0.049833726, + 0.049067676, + 0.048301592, + 0.047535483, + 0.046769347, + 0.04600318, + 0.04523699, + 0.044470772, + 0.04370453, + 0.04293826, + 0.042171963, + 0.04140564, + 0.040639296, + 0.039872926, + 0.039106537, + 0.03834012, + 0.037573684, + 0.036807224, + 0.036040742, + 0.035274237, + 0.034507714, + 0.033741172, + 0.03297461, + 0.032208025, + 0.031441424, + 0.030674804, + 0.029908165, + 0.029141508, + 0.028374836, + 0.027608145, + 0.02684144, + 0.026074719, + 0.025307981, + 0.024541229, + 0.023774462, + 0.023007682, + 0.022240888, + 0.02147408, + 0.02070726, + 0.019940428, + 0.019173585, + 0.01840673, + 0.017639864, + 0.016872987, + 0.016106103, + 0.015339206, + 0.014572302, + 0.0138053885, + 0.013038468, + 0.012271538, + 0.011504602, + 0.010737659, + 0.00997071, + 0.009203754, + 0.008436794, + 0.007669829, + 0.0069028586, + 0.0061358847, + 0.005368907, + 0.004601926, + 0.0038349426, + 0.0030679568, + 0.002300969, + 0.0015339801, + 0.0007669903, +} diff --git a/src/golang.org/x/mobile/exp/f32/vec3.go b/src/golang.org/x/mobile/exp/f32/vec3.go new file mode 100644 index 0000000000..f966ce64f7 --- /dev/null +++ b/src/golang.org/x/mobile/exp/f32/vec3.go @@ -0,0 +1,49 @@ +// Copyright 2014 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. + +package f32 + +import "fmt" + +type Vec3 [3]float32 + +func (v Vec3) String() string { + return fmt.Sprintf("Vec3[% 0.3f, % 0.3f, % 0.3f]", v[0], v[1], v[2]) +} + +func (v *Vec3) Normalize() { + sq := v.Dot(v) + inv := 1 / Sqrt(sq) + v[0] *= inv + v[1] *= inv + v[2] *= inv +} + +func (v *Vec3) Sub(v0, v1 *Vec3) { + v[0] = v0[0] - v1[0] + v[1] = v0[1] - v1[1] + v[2] = v0[2] - v1[2] +} + +func (v *Vec3) Add(v0, v1 *Vec3) { + v[0] = v0[0] + v1[0] + v[1] = v0[1] + v1[1] + v[2] = v0[2] + v1[2] +} + +func (v *Vec3) Mul(v0, v1 *Vec3) { + v[0] = v0[0] * v1[0] + v[1] = v0[1] * v1[1] + v[2] = v0[2] * v1[2] +} + +func (v *Vec3) Cross(v0, v1 *Vec3) { + v[0] = v0[1]*v1[2] - v0[2]*v1[1] + v[1] = v0[2]*v1[0] - v0[0]*v1[2] + v[2] = v0[0]*v1[1] - v0[1]*v1[0] +} + +func (v *Vec3) Dot(v1 *Vec3) float32 { + return v[0]*v1[0] + v[1]*v1[1] + v[2]*v1[2] +} diff --git a/src/golang.org/x/mobile/exp/f32/vec4.go b/src/golang.org/x/mobile/exp/f32/vec4.go new file mode 100644 index 0000000000..ac7f7e0960 --- /dev/null +++ b/src/golang.org/x/mobile/exp/f32/vec4.go @@ -0,0 +1,47 @@ +// Copyright 2014 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. + +package f32 + +import "fmt" + +type Vec4 [4]float32 + +func (v Vec4) String() string { + return fmt.Sprintf("Vec4[% 0.3f, % 0.3f, % 0.3f, % 0.3f]", v[0], v[1], v[2], v[3]) +} + +func (v *Vec4) Normalize() { + sq := v.Dot(v) + inv := 1 / Sqrt(sq) + v[0] *= inv + v[1] *= inv + v[2] *= inv + v[3] *= inv +} + +func (v *Vec4) Sub(v0, v1 *Vec4) { + v[0] = v0[0] - v1[0] + v[1] = v0[1] - v1[1] + v[2] = v0[2] - v1[2] + v[3] = v0[3] - v1[3] +} + +func (v *Vec4) Add(v0, v1 *Vec4) { + v[0] = v0[0] + v1[0] + v[1] = v0[1] + v1[1] + v[2] = v0[2] + v1[2] + v[3] = v0[3] + v1[3] +} + +func (v *Vec4) Mul(v0, v1 *Vec4) { + v[0] = v0[0] * v1[0] + v[1] = v0[1] * v1[1] + v[2] = v0[2] * v1[2] + v[3] = v0[3] * v1[3] +} + +func (v *Vec4) Dot(v1 *Vec4) float32 { + return v[0]*v1[0] + v[1]*v1[1] + v[2]*v1[2] + v[3]*v1[3] +} diff --git a/src/golang.org/x/mobile/exp/font/doc.go b/src/golang.org/x/mobile/exp/font/doc.go new file mode 100644 index 0000000000..5ae720518f --- /dev/null +++ b/src/golang.org/x/mobile/exp/font/doc.go @@ -0,0 +1,6 @@ +// Copyright 2014 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. + +// Package font provides platform independent access to system fonts. +package font // import "golang.org/x/mobile/exp/font" diff --git a/src/golang.org/x/mobile/exp/font/font.go b/src/golang.org/x/mobile/exp/font/font.go new file mode 100644 index 0000000000..38f6ba6761 --- /dev/null +++ b/src/golang.org/x/mobile/exp/font/font.go @@ -0,0 +1,27 @@ +// Copyright 2014 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 + +package font + +// Default returns the default system font. +// The font is encoded as a TTF. +func Default() []byte { + b, err := buildDefault() + if err != nil { + panic(err) + } + return b +} + +// Monospace returns the default system fixed-pitch font. +// The font is encoded as a TTF. +func Monospace() []byte { + b, err := buildMonospace() + if err != nil { + panic(err) + } + return b +} diff --git a/src/golang.org/x/mobile/exp/font/font_android.go b/src/golang.org/x/mobile/exp/font/font_android.go new file mode 100644 index 0000000000..3503ec991e --- /dev/null +++ b/src/golang.org/x/mobile/exp/font/font_android.go @@ -0,0 +1,15 @@ +// Copyright 2014 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. + +package font + +import "io/ioutil" + +func buildDefault() ([]byte, error) { + return ioutil.ReadFile("/system/fonts/DroidSans.ttf") +} + +func buildMonospace() ([]byte, error) { + return ioutil.ReadFile("/system/fonts/DroidSansMono.ttf") +} diff --git a/src/golang.org/x/mobile/exp/font/font_darwin.go b/src/golang.org/x/mobile/exp/font/font_darwin.go new file mode 100644 index 0000000000..e82713c059 --- /dev/null +++ b/src/golang.org/x/mobile/exp/font/font_darwin.go @@ -0,0 +1,76 @@ +// Copyright 2014 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. + +package font + +/* +#cgo darwin LDFLAGS: -framework CoreFoundation -framework CoreText +#include +#include +#include +#include +*/ +import "C" +import "unsafe" + +func buildFont(f C.CTFontRef) []byte { + ctags := C.CTFontCopyAvailableTables(f, C.kCTFontTableOptionExcludeSynthetic) + tagsCount := C.CFArrayGetCount(ctags) + + var tags []uint32 + var dataRefs []C.CFDataRef + var dataLens []uint32 + + for i := C.CFIndex(0); i < tagsCount; i++ { + tag := (C.CTFontTableTag)((uintptr)(C.CFArrayGetValueAtIndex(ctags, i))) + dataRef := C.CTFontCopyTable(f, tag, 0) // retained + tags = append(tags, uint32(tag)) + dataRefs = append(dataRefs, dataRef) + dataLens = append(dataLens, uint32(C.CFDataGetLength(dataRef))) + } + + totalLen := 0 + for _, l := range dataLens { + totalLen += int(l) + } + + // Big-endian output. + buf := make([]byte, 0, 12+16*len(tags)+totalLen) + write16 := func(x uint16) { buf = append(buf, byte(x>>8), byte(x)) } + write32 := func(x uint32) { buf = append(buf, byte(x>>24), byte(x>>16), byte(x>>8), byte(x)) } + + // File format description: http://www.microsoft.com/typography/otspec/otff.htm + write32(0x00010000) // version 1.0 + write16(uint16(len(tags))) // numTables + write16(0) // searchRange + write16(0) // entrySelector + write16(0) // rangeShift + + // Table tags, includes offsets into following data segments. + offset := uint32(12 + 16*len(tags)) // offset starts after table tags + for i, tag := range tags { + write32(tag) + write32(0) + write32(offset) + write32(dataLens[i]) + offset += dataLens[i] + } + + // Data segments. + for i, dataRef := range dataRefs { + data := (*[1<<31 - 2]byte)((unsafe.Pointer)(C.CFDataGetBytePtr(dataRef)))[:dataLens[i]] + buf = append(buf, data...) + C.CFRelease(C.CFTypeRef(dataRef)) + } + + return buf +} + +func buildDefault() ([]byte, error) { + return buildFont(C.CTFontCreateUIFontForLanguage(C.kCTFontSystemFontType, 0, nil)), nil +} + +func buildMonospace() ([]byte, error) { + return buildFont(C.CTFontCreateUIFontForLanguage(C.kCTFontUserFixedPitchFontType, 0, nil)), nil +} diff --git a/src/golang.org/x/mobile/exp/font/font_linux.go b/src/golang.org/x/mobile/exp/font/font_linux.go new file mode 100644 index 0000000000..ac3ea91c68 --- /dev/null +++ b/src/golang.org/x/mobile/exp/font/font_linux.go @@ -0,0 +1,17 @@ +// Copyright 2014 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 !android + +package font + +import "io/ioutil" + +func buildDefault() ([]byte, error) { + return ioutil.ReadFile("/usr/share/fonts/truetype/droid/DroidSans.ttf") +} + +func buildMonospace() ([]byte, error) { + return ioutil.ReadFile("/usr/share/fonts/truetype/droid/DroidSansMono.ttf") +} diff --git a/src/golang.org/x/mobile/exp/font/font_test.go b/src/golang.org/x/mobile/exp/font/font_test.go new file mode 100644 index 0000000000..f1e8b87fde --- /dev/null +++ b/src/golang.org/x/mobile/exp/font/font_test.go @@ -0,0 +1,46 @@ +// Copyright 2014 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 + +package font + +import ( + "bytes" + "fmt" + "testing" +) + +func looksLikeATTF(b []byte) error { + if len(b) < 256 { + return fmt.Errorf("not a TTF: not enough data") + } + b = b[:256] + + // Look for the 4-byte magic header. See + // http://www.microsoft.com/typography/otspec/otff.htm + switch string(b[:4]) { + case "\x00\x01\x00\x00", "ttcf": + // No-op. + default: + return fmt.Errorf("not a TTF: missing magic header") + } + + // Look for a glyf table. + if i := bytes.Index(b, []byte("glyf")); i < 0 { + return fmt.Errorf(`not a TTF: missing "glyf" table`) + } else if i%0x10 != 0x0c { + return fmt.Errorf(`not a TTF: invalid "glyf" offset 0x%02x`, i) + } + return nil +} + +func TestLoadFonts(t *testing.T) { + if err := looksLikeATTF(Default()); err != nil { + t.Errorf("default font: %v", err) + } + if err := looksLikeATTF(Monospace()); err != nil { + t.Errorf("monospace font: %v", err) + } +} diff --git a/src/golang.org/x/mobile/exp/gl/glutil/context_darwin_amd64.go b/src/golang.org/x/mobile/exp/gl/glutil/context_darwin_amd64.go new file mode 100644 index 0000000000..63cbc10a59 --- /dev/null +++ b/src/golang.org/x/mobile/exp/gl/glutil/context_darwin_amd64.go @@ -0,0 +1,75 @@ +// Copyright 2014 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 darwin + +package glutil + +// TODO(crawshaw): Only used in glutil tests for now (cgo is not support in _test.go files). +// TODO(crawshaw): Export some kind of Context. Work out what we can offer, where. Maybe just for tests. +// TODO(crawshaw): Support android and windows. + +/* +#cgo LDFLAGS: -framework OpenGL +#import +#import + +void CGCreate(CGLContextObj* ctx) { + CGLPixelFormatAttribute attributes[] = { + kCGLPFAOpenGLProfile, (CGLPixelFormatAttribute)kCGLOGLPVersion_3_2_Core, + kCGLPFAColorSize, (CGLPixelFormatAttribute)24, + kCGLPFAAlphaSize, (CGLPixelFormatAttribute)8, + kCGLPFADepthSize, (CGLPixelFormatAttribute)16, + kCGLPFAAccelerated, + kCGLPFADoubleBuffer, + (CGLPixelFormatAttribute) 0 + }; + CGLPixelFormatObj pix; + GLint num; + CGLChoosePixelFormat(attributes, &pix, &num); + CGLCreateContext(pix, 0, ctx); + CGLDestroyPixelFormat(pix); + CGLSetCurrentContext(*ctx); + CGLLockContext(*ctx); +} +*/ +import "C" + +import "runtime" + +// contextGL holds a copy of the OpenGL Context from thread-local storage. +// +// Do not move a contextGL between goroutines or OS threads. +type contextGL struct { + ctx C.CGLContextObj +} + +// createContext creates an OpenGL context, binds it as the current context +// stored in thread-local storage, and locks the current goroutine to an os +// thread. +func createContext() *contextGL { + // The OpenGL active context is stored in TLS. + runtime.LockOSThread() + + c := new(contextGL) + C.CGCreate(&c.ctx) + + // Using attribute arrays in OpenGL 3.3 requires the use of a VBA. + // But VBAs don't exist in ES 2. So we bind a default one. + var id C.GLuint + C.glGenVertexArrays(1, &id) + C.glBindVertexArray(id) + + return c +} + +// destroy destroys an OpenGL context and unlocks the current goroutine from +// its os thread. +func (c *contextGL) destroy() { + C.CGLUnlockContext(c.ctx) + C.CGLSetCurrentContext(nil) + C.CGLDestroyContext(c.ctx) + c.ctx = nil + runtime.UnlockOSThread() +} diff --git a/src/golang.org/x/mobile/exp/gl/glutil/context_x11.go b/src/golang.org/x/mobile/exp/gl/glutil/context_x11.go new file mode 100644 index 0000000000..01c44f5f57 --- /dev/null +++ b/src/golang.org/x/mobile/exp/gl/glutil/context_x11.go @@ -0,0 +1,105 @@ +// Copyright 2014 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,!android + +package glutil + +/* +#cgo LDFLAGS: -lEGL +#include +#include +#include + +void createContext(EGLDisplay *out_dpy, EGLContext *out_ctx, EGLSurface *out_surf) { + EGLDisplay e_dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (!e_dpy) { + fprintf(stderr, "eglGetDisplay failed\n"); + exit(1); + } + EGLint e_major, e_minor; + if (!eglInitialize(e_dpy, &e_major, &e_minor)) { + fprintf(stderr, "eglInitialize failed\n"); + exit(1); + } + eglBindAPI(EGL_OPENGL_ES_API); + static const EGLint config_attribs[] = { + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, + EGL_BLUE_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_RED_SIZE, 8, + EGL_CONFIG_CAVEAT, EGL_NONE, + EGL_NONE + }; + EGLConfig config; + EGLint num_configs; + if (!eglChooseConfig(e_dpy, config_attribs, &config, 1, &num_configs)) { + fprintf(stderr, "eglChooseConfig failed\n"); + exit(1); + } + static const EGLint ctx_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + EGLContext e_ctx = eglCreateContext(e_dpy, config, EGL_NO_CONTEXT, ctx_attribs); + if (e_ctx == EGL_NO_CONTEXT) { + fprintf(stderr, "eglCreateContext failed\n"); + exit(1); + } + static const EGLint pbuf_attribs[] = { + EGL_NONE + }; + EGLSurface e_surf = eglCreatePbufferSurface(e_dpy, config, pbuf_attribs); + if (e_surf == EGL_NO_SURFACE) { + fprintf(stderr, "eglCreatePbufferSurface failed\n"); + exit(1); + } + if (!eglMakeCurrent(e_dpy, e_surf, e_surf, e_ctx)) { + fprintf(stderr, "eglMakeCurrent failed\n"); + exit(1); + } + *out_surf = e_surf; + *out_ctx = e_ctx; + *out_dpy = e_dpy; +} + +void destroyContext(EGLDisplay e_dpy, EGLContext e_ctx, EGLSurface e_surf) { + if (!eglMakeCurrent(e_dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { + fprintf(stderr, "eglMakeCurrent failed\n"); + exit(1); + } + if (!eglDestroySurface(e_dpy, e_surf)) { + fprintf(stderr, "eglDestroySurface failed\n"); + exit(1); + } + if (!eglDestroyContext(e_dpy, e_ctx)) { + fprintf(stderr, "eglDestroyContext failed\n"); + exit(1); + } +} +*/ +import "C" + +import ( + "runtime" +) + +type contextGL struct { + dpy C.EGLDisplay + ctx C.EGLContext + surf C.EGLSurface +} + +func createContext() *contextGL { + runtime.LockOSThread() + c := &contextGL{} + C.createContext(&c.dpy, &c.ctx, &c.surf) + return c +} + +func (c *contextGL) destroy() { + C.destroyContext(c.dpy, c.ctx, c.surf) + runtime.UnlockOSThread() +} diff --git a/src/golang.org/x/mobile/exp/gl/glutil/glimage.go b/src/golang.org/x/mobile/exp/gl/glutil/glimage.go new file mode 100644 index 0000000000..5b010936eb --- /dev/null +++ b/src/golang.org/x/mobile/exp/gl/glutil/glimage.go @@ -0,0 +1,391 @@ +// Copyright 2014 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 + +package glutil + +import ( + "encoding/binary" + "fmt" + "image" + "runtime" + "sync" + + "golang.org/x/mobile/app" + "golang.org/x/mobile/event/config" + "golang.org/x/mobile/event/lifecycle" + "golang.org/x/mobile/exp/f32" + "golang.org/x/mobile/geom" + "golang.org/x/mobile/gl" +) + +var glimage struct { + quadXY gl.Buffer + quadUV gl.Buffer + program gl.Program + pos gl.Attrib + mvp gl.Uniform + uvp gl.Uniform + inUV gl.Attrib + textureSample gl.Uniform +} + +func init() { + app.RegisterFilter(func(e interface{}) interface{} { + if e, ok := e.(lifecycle.Event); ok { + switch e.Crosses(lifecycle.StageVisible) { + case lifecycle.CrossOn: + start() + case lifecycle.CrossOff: + stop() + } + } + return e + }) +} + +func start() { + var err error + glimage.program, err = CreateProgram(vertexShader, fragmentShader) + if err != nil { + panic(err) + } + + glimage.quadXY = gl.CreateBuffer() + glimage.quadUV = gl.CreateBuffer() + + gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadXY) + gl.BufferData(gl.ARRAY_BUFFER, quadXYCoords, gl.STATIC_DRAW) + gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadUV) + gl.BufferData(gl.ARRAY_BUFFER, quadUVCoords, gl.STATIC_DRAW) + + glimage.pos = gl.GetAttribLocation(glimage.program, "pos") + glimage.mvp = gl.GetUniformLocation(glimage.program, "mvp") + glimage.uvp = gl.GetUniformLocation(glimage.program, "uvp") + glimage.inUV = gl.GetAttribLocation(glimage.program, "inUV") + glimage.textureSample = gl.GetUniformLocation(glimage.program, "textureSample") + + texmap.Lock() + defer texmap.Unlock() + for key, tex := range texmap.texs { + texmap.init(key) + tex.needsUpload = true + } +} + +func stop() { + gl.DeleteProgram(glimage.program) + gl.DeleteBuffer(glimage.quadXY) + gl.DeleteBuffer(glimage.quadUV) + + texmap.Lock() + for _, t := range texmap.texs { + if t.gltex.Value != 0 { + gl.DeleteTexture(t.gltex) + } + t.gltex = gl.Texture{} + } + texmap.Unlock() +} + +type texture struct { + gltex gl.Texture + width int + height int + needsUpload bool +} + +var texmap = &texmapCache{ + texs: make(map[texmapKey]*texture), + next: 1, // avoid using 0 to aid debugging +} + +type texmapKey int + +type texmapCache struct { + sync.Mutex + texs map[texmapKey]*texture + next texmapKey + + // TODO(crawshaw): This is a workaround for having nowhere better to clean up deleted textures. + // Better: app.UI(func() { gl.DeleteTexture(t) } in texmap.delete + // Best: Redesign the gl package to do away with this painful notion of a UI thread. + toDelete []gl.Texture +} + +func (tm *texmapCache) create(dx, dy int) *texmapKey { + tm.Lock() + defer tm.Unlock() + key := tm.next + tm.next++ + tm.texs[key] = &texture{ + width: dx, + height: dy, + } + tm.init(key) + return &key +} + +// init creates an underlying GL texture for a key. +// Must be called with a valid GL context. +// Must hold tm.Mutex before calling. +func (tm *texmapCache) init(key texmapKey) { + tex := tm.texs[key] + if tex.gltex.Value != 0 { + panic(fmt.Sprintf("attempting to init key (%v) with valid texture", key)) + } + tex.gltex = gl.CreateTexture() + + gl.BindTexture(gl.TEXTURE_2D, tex.gltex) + gl.TexImage2D(gl.TEXTURE_2D, 0, tex.width, tex.height, gl.RGBA, gl.UNSIGNED_BYTE, nil) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) + + for _, t := range tm.toDelete { + gl.DeleteTexture(t) + } + tm.toDelete = nil +} + +func (tm *texmapCache) delete(key texmapKey) { + tm.Lock() + defer tm.Unlock() + tex := tm.texs[key] + delete(tm.texs, key) + if tex == nil { + return + } + tm.toDelete = append(tm.toDelete, tex.gltex) +} + +func (tm *texmapCache) get(key texmapKey) *texture { + tm.Lock() + defer tm.Unlock() + return tm.texs[key] +} + +// Image bridges between an *image.RGBA and an OpenGL texture. +// +// The contents of the *image.RGBA can be uploaded as a texture and drawn as a +// 2D quad. +// +// The number of active Images must fit in the system's OpenGL texture limit. +// The typical use of an Image is as a texture atlas. +type Image struct { + RGBA *image.RGBA + key *texmapKey +} + +// NewImage creates an Image of the given size. +// +// Both a host-memory *image.RGBA and a GL texture are created. +func NewImage(w, h int) *Image { + dx := roundToPower2(w) + dy := roundToPower2(h) + + // TODO(crawshaw): Using VertexAttribPointer we can pass texture + // data with a stride, which would let us use the exact number of + // pixels on the host instead of the rounded up power 2 size. + m := image.NewRGBA(image.Rect(0, 0, dx, dy)) + + img := &Image{ + RGBA: m.SubImage(image.Rect(0, 0, w, h)).(*image.RGBA), + key: texmap.create(dx, dy), + } + runtime.SetFinalizer(img.key, func(key *texmapKey) { + texmap.delete(*key) + }) + return img +} + +func roundToPower2(x int) int { + x2 := 1 + for x2 < x { + x2 *= 2 + } + return x2 +} + +// Upload copies the host image data to the GL device. +func (img *Image) Upload() { + tex := texmap.get(*img.key) + gl.BindTexture(gl.TEXTURE_2D, tex.gltex) + gl.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, tex.width, tex.height, gl.RGBA, gl.UNSIGNED_BYTE, img.RGBA.Pix) +} + +// Delete invalidates the Image and removes any underlying data structures. +// The Image cannot be used after being deleted. +func (img *Image) Delete() { + texmap.delete(*img.key) +} + +// Draw draws the srcBounds part of the image onto a parallelogram, defined by +// three of its corners, in the current GL framebuffer. +func (img *Image) Draw(c config.Event, topLeft, topRight, bottomLeft geom.Point, srcBounds image.Rectangle) { + // TODO(crawshaw): Adjust viewport for the top bar on android? + gl.UseProgram(glimage.program) + tex := texmap.get(*img.key) + if tex.needsUpload { + img.Upload() + tex.needsUpload = false + } + + { + // We are drawing a parallelogram PQRS, defined by three of its + // corners, onto the entire GL framebuffer ABCD. The two quads may + // actually be equal, but in the general case, PQRS can be smaller, + // and PQRS is not necessarily axis-aligned. + // + // A +---------------+ B + // | P +-----+ Q | + // | | | | + // | S +-----+ R | + // D +---------------+ C + // + // There are two co-ordinate spaces: geom space and framebuffer space. + // In geom space, the ABCD rectangle is: + // + // (0, 0) (geom.Width, 0) + // (0, geom.Height) (geom.Width, geom.Height) + // + // and the PQRS quad is: + // + // (topLeft.X, topLeft.Y) (topRight.X, topRight.Y) + // (bottomLeft.X, bottomLeft.Y) (implicit, implicit) + // + // In framebuffer space, the ABCD rectangle is: + // + // (-1, +1) (+1, +1) + // (-1, -1) (+1, -1) + // + // First of all, convert from geom space to framebuffer space. For + // later convenience, we divide everything by 2 here: px2 is half of + // the P.X co-ordinate (in framebuffer space). + px2 := -0.5 + float32(topLeft.X/c.WidthPt) + py2 := +0.5 - float32(topLeft.Y/c.HeightPt) + qx2 := -0.5 + float32(topRight.X/c.WidthPt) + qy2 := +0.5 - float32(topRight.Y/c.HeightPt) + sx2 := -0.5 + float32(bottomLeft.X/c.WidthPt) + sy2 := +0.5 - float32(bottomLeft.Y/c.HeightPt) + // Next, solve for the affine transformation matrix + // [ a00 a01 a02 ] + // a = [ a10 a11 a12 ] + // [ 0 0 1 ] + // that maps A to P: + // a × [ -1 +1 1 ]' = [ 2*px2 2*py2 1 ]' + // and likewise maps B to Q and D to S. Solving those three constraints + // implies that C maps to R, since affine transformations keep parallel + // lines parallel. This gives 6 equations in 6 unknowns: + // -a00 + a01 + a02 = 2*px2 + // -a10 + a11 + a12 = 2*py2 + // +a00 + a01 + a02 = 2*qx2 + // +a10 + a11 + a12 = 2*qy2 + // -a00 - a01 + a02 = 2*sx2 + // -a10 - a11 + a12 = 2*sy2 + // which gives: + // a00 = (2*qx2 - 2*px2) / 2 = qx2 - px2 + // and similarly for the other elements of a. + writeAffine(glimage.mvp, &f32.Affine{{ + qx2 - px2, + px2 - sx2, + qx2 + sx2, + }, { + qy2 - py2, + py2 - sy2, + qy2 + sy2, + }}) + } + + { + // Mapping texture co-ordinates is similar, except that in texture + // space, the ABCD rectangle is: + // + // (0,0) (1,0) + // (0,1) (1,1) + // + // and the PQRS quad is always axis-aligned. First of all, convert + // from pixel space to texture space. + w := float32(tex.width) + h := float32(tex.height) + px := float32(srcBounds.Min.X-img.RGBA.Rect.Min.X) / w + py := float32(srcBounds.Min.Y-img.RGBA.Rect.Min.Y) / h + qx := float32(srcBounds.Max.X-img.RGBA.Rect.Min.X) / w + sy := float32(srcBounds.Max.Y-img.RGBA.Rect.Min.Y) / h + // Due to axis alignment, qy = py and sx = px. + // + // The simultaneous equations are: + // 0 + 0 + a02 = px + // 0 + 0 + a12 = py + // a00 + 0 + a02 = qx + // a10 + 0 + a12 = qy = py + // 0 + a01 + a02 = sx = px + // 0 + a11 + a12 = sy + writeAffine(glimage.uvp, &f32.Affine{{ + qx - px, + 0, + px, + }, { + 0, + sy - py, + py, + }}) + } + + gl.ActiveTexture(gl.TEXTURE0) + gl.BindTexture(gl.TEXTURE_2D, tex.gltex) + gl.Uniform1i(glimage.textureSample, 0) + + gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadXY) + gl.EnableVertexAttribArray(glimage.pos) + gl.VertexAttribPointer(glimage.pos, 2, gl.FLOAT, false, 0, 0) + + gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadUV) + gl.EnableVertexAttribArray(glimage.inUV) + gl.VertexAttribPointer(glimage.inUV, 2, gl.FLOAT, false, 0, 0) + + gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) + + gl.DisableVertexAttribArray(glimage.pos) + gl.DisableVertexAttribArray(glimage.inUV) +} + +var quadXYCoords = f32.Bytes(binary.LittleEndian, + -1, +1, // top left + +1, +1, // top right + -1, -1, // bottom left + +1, -1, // bottom right +) + +var quadUVCoords = f32.Bytes(binary.LittleEndian, + 0, 0, // top left + 1, 0, // top right + 0, 1, // bottom left + 1, 1, // bottom right +) + +const vertexShader = `#version 100 +uniform mat3 mvp; +uniform mat3 uvp; +attribute vec3 pos; +attribute vec2 inUV; +varying vec2 UV; +void main() { + vec3 p = pos; + p.z = 1.0; + gl_Position = vec4(mvp * p, 1); + UV = (uvp * vec3(inUV, 1)).xy; +} +` + +const fragmentShader = `#version 100 +precision mediump float; +varying vec2 UV; +uniform sampler2D textureSample; +void main(){ + gl_FragColor = texture2D(textureSample, UV); +} +` diff --git a/src/golang.org/x/mobile/exp/gl/glutil/glimage_test.go b/src/golang.org/x/mobile/exp/gl/glutil/glimage_test.go new file mode 100644 index 0000000000..ab643792e8 --- /dev/null +++ b/src/golang.org/x/mobile/exp/gl/glutil/glimage_test.go @@ -0,0 +1,216 @@ +// Copyright 2014 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 darwin linux,!android + +// TODO(crawshaw): Run tests on other OSs when more contexts are supported. + +package glutil + +import ( + "image" + "image/color" + "image/draw" + "image/png" + "io/ioutil" + "os" + "runtime" + "testing" + + "golang.org/x/mobile/event/config" + "golang.org/x/mobile/geom" + "golang.org/x/mobile/gl" +) + +func TestImage(t *testing.T) { + done := make(chan struct{}) + defer close(done) + go func() { + runtime.LockOSThread() + ctx := createContext() + for { + select { + case <-gl.WorkAvailable: + gl.DoWork() + case <-done: + ctx.destroy() + return + } + } + }() + start() + defer stop() + + // GL testing strategy: + // 1. Create an offscreen framebuffer object. + // 2. Configure framebuffer to render to a GL texture. + // 3. Run test code: use glimage to draw testdata. + // 4. Copy GL texture back into system memory. + // 5. Compare to a pre-computed image. + + f, err := os.Open("../../../testdata/testpattern.png") + if err != nil { + t.Fatal(err) + } + defer f.Close() + src, _, err := image.Decode(f) + if err != nil { + t.Fatal(err) + } + + const ( + pixW = 100 + pixH = 100 + ptW = geom.Pt(50) + ptH = geom.Pt(50) + ) + cfg := config.Event{ + WidthPx: pixW, + HeightPx: pixH, + WidthPt: ptW, + HeightPt: ptH, + PixelsPerPt: float32(pixW) / float32(ptW), + } + + fBuf := gl.CreateFramebuffer() + gl.BindFramebuffer(gl.FRAMEBUFFER, fBuf) + colorBuf := gl.CreateRenderbuffer() + gl.BindRenderbuffer(gl.RENDERBUFFER, colorBuf) + // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glRenderbufferStorage.xml + // says that the internalFormat "must be one of the following symbolic constants: + // GL_RGBA4, GL_RGB565, GL_RGB5_A1, GL_DEPTH_COMPONENT16, or GL_STENCIL_INDEX8". + gl.RenderbufferStorage(gl.RENDERBUFFER, gl.RGB565, pixW, pixH) + gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorBuf) + + if status := gl.CheckFramebufferStatus(gl.FRAMEBUFFER); status != gl.FRAMEBUFFER_COMPLETE { + t.Fatalf("framebuffer create failed: %v", status) + } + + allocs := testing.AllocsPerRun(100, func() { + gl.ClearColor(0, 0, 1, 1) // blue + }) + if allocs != 0 { + t.Errorf("unexpected allocations from calling gl.ClearColor: %f", allocs) + } + gl.Clear(gl.COLOR_BUFFER_BIT) + gl.Viewport(0, 0, pixW, pixH) + + m := NewImage(src.Bounds().Dx(), src.Bounds().Dy()) + b := m.RGBA.Bounds() + draw.Draw(m.RGBA, b, src, src.Bounds().Min, draw.Src) + m.Upload() + b.Min.X += 10 + b.Max.Y /= 2 + + // All-integer right-angled triangles offsetting the + // box: 24-32-40, 12-16-20. + ptTopLeft := geom.Point{0, 24} + ptTopRight := geom.Point{32, 0} + ptBottomLeft := geom.Point{12, 24 + 16} + ptBottomRight := geom.Point{12 + 32, 16} + m.Draw(cfg, ptTopLeft, ptTopRight, ptBottomLeft, b) + + // For unknown reasons, a windowless OpenGL context renders upside- + // down. That is, a quad covering the initial viewport spans: + // + // (-1, -1) ( 1, -1) + // (-1, 1) ( 1, 1) + // + // To avoid modifying live code for tests, we flip the rows + // recovered from the renderbuffer. We are not the first: + // + // http://lists.apple.com/archives/mac-opengl/2010/Jun/msg00080.html + got := image.NewRGBA(image.Rect(0, 0, pixW, pixH)) + upsideDownPix := make([]byte, len(got.Pix)) + gl.ReadPixels(upsideDownPix, 0, 0, pixW, pixH, gl.RGBA, gl.UNSIGNED_BYTE) + for y := 0; y < pixH; y++ { + i0 := (pixH - 1 - y) * got.Stride + i1 := i0 + pixW*4 + copy(got.Pix[y*got.Stride:], upsideDownPix[i0:i1]) + } + + drawCross(got, 0, 0) + drawCross(got, int(ptTopLeft.X.Px(cfg.PixelsPerPt)), int(ptTopLeft.Y.Px(cfg.PixelsPerPt))) + drawCross(got, int(ptBottomRight.X.Px(cfg.PixelsPerPt)), int(ptBottomRight.Y.Px(cfg.PixelsPerPt))) + drawCross(got, pixW-1, pixH-1) + + const wantPath = "../../../testdata/testpattern-window.png" + f, err = os.Open(wantPath) + if err != nil { + t.Fatal(err) + } + defer f.Close() + wantSrc, _, err := image.Decode(f) + if err != nil { + t.Fatal(err) + } + want, ok := wantSrc.(*image.RGBA) + if !ok { + b := wantSrc.Bounds() + want = image.NewRGBA(b) + draw.Draw(want, b, wantSrc, b.Min, draw.Src) + } + + if !imageEq(got, want) { + // Write out the image we got. + f, err = ioutil.TempFile("", "testpattern-window-got") + if err != nil { + t.Fatal(err) + } + f.Close() + gotPath := f.Name() + ".png" + f, err = os.Create(gotPath) + if err != nil { + t.Fatal(err) + } + if err := png.Encode(f, got); err != nil { + t.Fatal(err) + } + if err := f.Close(); err != nil { + t.Fatal(err) + } + t.Errorf("got\n%s\nwant\n%s", gotPath, wantPath) + } +} + +func drawCross(m *image.RGBA, x, y int) { + c := color.RGBA{0xff, 0, 0, 0xff} // red + m.SetRGBA(x+0, y-2, c) + m.SetRGBA(x+0, y-1, c) + m.SetRGBA(x-2, y+0, c) + m.SetRGBA(x-1, y+0, c) + m.SetRGBA(x+0, y+0, c) + m.SetRGBA(x+1, y+0, c) + m.SetRGBA(x+2, y+0, c) + m.SetRGBA(x+0, y+1, c) + m.SetRGBA(x+0, y+2, c) +} + +func eqEpsilon(x, y uint8) bool { + const epsilon = 9 + return x-y < epsilon || y-x < epsilon +} + +func colorEq(c0, c1 color.RGBA) bool { + return eqEpsilon(c0.R, c1.R) && eqEpsilon(c0.G, c1.G) && eqEpsilon(c0.B, c1.B) && eqEpsilon(c0.A, c1.A) +} + +func imageEq(m0, m1 *image.RGBA) bool { + b0 := m0.Bounds() + b1 := m1.Bounds() + if b0 != b1 { + return false + } + badPx := 0 + for y := b0.Min.Y; y < b0.Max.Y; y++ { + for x := b0.Min.X; x < b0.Max.X; x++ { + c0, c1 := m0.At(x, y).(color.RGBA), m1.At(x, y).(color.RGBA) + if !colorEq(c0, c1) { + badPx++ + } + } + } + badFrac := float64(badPx) / float64(b0.Dx()*b0.Dy()) + return badFrac < 0.01 +} diff --git a/src/golang.org/x/mobile/exp/gl/glutil/glutil.go b/src/golang.org/x/mobile/exp/gl/glutil/glutil.go new file mode 100644 index 0000000000..babb1c9170 --- /dev/null +++ b/src/golang.org/x/mobile/exp/gl/glutil/glutil.go @@ -0,0 +1,74 @@ +// Copyright 2014 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. + +// Package glutil implements OpenGL utility functions. +package glutil // import "golang.org/x/mobile/exp/gl/glutil" + +import ( + "fmt" + + "golang.org/x/mobile/exp/f32" + "golang.org/x/mobile/gl" +) + +// CreateProgram creates, compiles, and links a gl.Program. +func CreateProgram(vertexSrc, fragmentSrc string) (gl.Program, error) { + program := gl.CreateProgram() + if program.Value == 0 { + return gl.Program{}, fmt.Errorf("glutil: no programs available") + } + + vertexShader, err := loadShader(gl.VERTEX_SHADER, vertexSrc) + if err != nil { + return gl.Program{}, err + } + fragmentShader, err := loadShader(gl.FRAGMENT_SHADER, fragmentSrc) + if err != nil { + gl.DeleteShader(vertexShader) + return gl.Program{}, err + } + + gl.AttachShader(program, vertexShader) + gl.AttachShader(program, fragmentShader) + gl.LinkProgram(program) + + // Flag shaders for deletion when program is unlinked. + gl.DeleteShader(vertexShader) + gl.DeleteShader(fragmentShader) + + if gl.GetProgrami(program, gl.LINK_STATUS) == 0 { + defer gl.DeleteProgram(program) + return gl.Program{}, fmt.Errorf("glutil: %s", gl.GetProgramInfoLog(program)) + } + return program, nil +} + +func loadShader(shaderType gl.Enum, src string) (gl.Shader, error) { + shader := gl.CreateShader(shaderType) + if shader.Value == 0 { + return gl.Shader{}, fmt.Errorf("glutil: could not create shader (type %v)", shaderType) + } + gl.ShaderSource(shader, src) + gl.CompileShader(shader) + if gl.GetShaderi(shader, gl.COMPILE_STATUS) == 0 { + defer gl.DeleteShader(shader) + return gl.Shader{}, fmt.Errorf("shader compile: %s", gl.GetShaderInfoLog(shader)) + } + return shader, nil +} + +// writeAffine writes the contents of an Affine to a 3x3 matrix GL uniform. +func writeAffine(u gl.Uniform, a *f32.Affine) { + var m [9]float32 + m[0*3+0] = a[0][0] + m[0*3+1] = a[1][0] + m[0*3+2] = 0 + m[1*3+0] = a[0][1] + m[1*3+1] = a[1][1] + m[1*3+2] = 0 + m[2*3+0] = a[0][2] + m[2*3+1] = a[1][2] + m[2*3+2] = 1 + gl.UniformMatrix3fv(u, m[:]) +} diff --git a/src/golang.org/x/mobile/exp/sensor/sensor.go b/src/golang.org/x/mobile/exp/sensor/sensor.go new file mode 100644 index 0000000000..8e93feb8f9 --- /dev/null +++ b/src/golang.org/x/mobile/exp/sensor/sensor.go @@ -0,0 +1,118 @@ +// Copyright 2015 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. + +// Package sensor provides sensor events from various movement sensors. +package sensor // import "golang.org/x/mobile/exp/sensor" + +import ( + "errors" + "time" +) + +// Type represents a sensor type. +type Type int + +var sensorNames = map[Type]string{ + Accelerometer: "Accelerometer", + Gyroscope: "Gyrsocope", + Magnetometer: "Magnetometer", +} + +// String returns the string representation of the sensor type. +func (t Type) String() string { + if n, ok := sensorNames[t]; ok { + return n + } + return "Unknown sensor" +} + +const ( + Accelerometer = Type(0) + Gyroscope = Type(1) + Magnetometer = Type(2) +) + +// Event represents a sensor event. +type Event struct { + // Sensor is the type of the sensor the event is coming from. + Sensor Type + + // Timestamp is a device specific event time in nanoseconds. + // Timestamps are not Unix times, they represent a time that is + // only valid for the device's default sensor. + Timestamp int64 + + // Data is the event data. + // + // If the event source is Accelerometer, + // - Data[0]: acceleration force in x axis in m/s^2 + // - Data[1]: acceleration force in y axis in m/s^2 + // - Data[2]: acceleration force in z axis in m/s^2 + // + // If the event source is Gyroscope, + // - Data[0]: rate of rotation around the x axis in rad/s + // - Data[1]: rate of rotation around the y axis in rad/s + // - Data[2]: rate of rotation around the z axis in rad/s + // + // If the event source is Magnetometer, + // - Data[0]: force of gravity along the x axis in m/s^2 + // - Data[1]: force of gravity along the y axis in m/s^2 + // - Data[2]: force of gravity along the z axis in m/s^2 + // + Data []float64 +} + +// Manager multiplexes sensor event data from various sensor sources. +type Manager struct { + m *manager // platform-specific implementation of the underlying manager +} + +// Enable enables a sensor with the specified delay rate. +// If there are multiple sensors of type t on the device, Enable uses +// the default one. +// If there is no default sensor of type t on the device, an error returned. +// Valid sensor types supported by this package are Accelerometer, +// Gyroscope, Magnetometer and Altimeter. +func (m *Manager) Enable(t Type, delay time.Duration) error { + if m.m == nil { + m.m = new(manager) + m.m.initialize() + } + if t < 0 || int(t) >= len(sensorNames) { + return errors.New("sensor: unknown sensor type") + } + return m.m.enable(t, delay) +} + +// Disable disables to feed the manager with the specified sensor. +func (m *Manager) Disable(t Type) error { + if m.m == nil { + m.m = new(manager) + m.m.initialize() + } + if t < 0 || int(t) >= len(sensorNames) { + return errors.New("sensor: unknown sensor type") + } + return m.m.disable(t) +} + +// Read reads a series of events from the manager. +// It may read up to len(e) number of events, but will return +// less events if timeout occurs. +func (m *Manager) Read(e []Event) (n int, err error) { + if m.m == nil { + m.m = new(manager) + m.m.initialize() + } + return m.m.read(e) +} + +// Close stops the manager and frees the related resources. +// Once Close is called, Manager becomes invalid to use. +func (m *Manager) Close() error { + if m.m == nil { + return nil + } + return m.m.close() +} diff --git a/src/golang.org/x/mobile/exp/sensor/sensor_android.go b/src/golang.org/x/mobile/exp/sensor/sensor_android.go new file mode 100644 index 0000000000..b7f4255139 --- /dev/null +++ b/src/golang.org/x/mobile/exp/sensor/sensor_android.go @@ -0,0 +1,196 @@ +// Copyright 2015 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. + +package sensor + +/* +#cgo LDFLAGS: -landroid + +#include +#include + +#include "sensors_android.h" +*/ +import "C" +import ( + "fmt" + "runtime" + "sync/atomic" + "time" + "unsafe" +) + +var nextLooperID int64 // each underlying ALooper should have a unique ID. + +// initSignal initializes an underlying looper and event queue. +type initSignal struct{} + +// closeSignal destroys the underlying looper and event queue. +type closeSignal struct{} + +// readSignal reads up to len(dst) events and mutates n with +// the number of returned events. If error occurs during the read, +// it mutates err. +type readSignal struct { + dst []Event + n *int + err *error +} + +// enableSignal enables the sensors events on the underlying +// event queue for the specified sensor type with the specified +// latency criterion. +type enableSignal struct { + t Type + delay time.Duration + err *error +} + +// disableSignal disables the events on the underlying event queue +// from the sensor specified. +type disableSignal struct { + t Type +} + +type inOut struct { + in interface{} + out chan struct{} +} + +// manager is the Android-specific implementation of Manager. +type manager struct { + m *C.android_SensorManager + inout chan inOut +} + +// initialize inits the manager and creates a goroutine to proxy the CGO calls. +// All actions related to an ALooper needs to be performed from the same +// OS thread. The goroutine proxy locks itself to an OS thread and handles the +// CGO traffic on the same thread. +func (m *manager) initialize() { + m.inout = make(chan inOut) + + go func() { + runtime.LockOSThread() + for { + v := <-m.inout + switch s := v.in.(type) { + case initSignal: + id := atomic.AddInt64(&nextLooperID, int64(1)) + var mgr C.android_SensorManager + C.android_createManager(C.int(id), &mgr) + m.m = &mgr + case enableSignal: + usecsDelay := s.delay.Nanoseconds() * 1000 + code := int(C.android_enableSensor(m.m.queue, typeToInt(s.t), C.int32_t(usecsDelay))) + if code != 0 { + *s.err = fmt.Errorf("sensor: no default %v sensor on the device", s.t) + } + case disableSignal: + C.android_disableSensor(m.m.queue, typeToInt(s.t)) + case readSignal: + n, err := readEvents(m, s.dst) + *s.n = n + *s.err = err + case closeSignal: + C.android_destroyManager(m.m) + close(v.out) + return // we don't need this goroutine anymore + } + close(v.out) + } + }() + + if m.m == nil { + done := make(chan struct{}) + m.inout <- inOut{ + in: initSignal{}, + out: done, + } + <-done + } +} + +func (m *manager) enable(t Type, delay time.Duration) error { + var err error + done := make(chan struct{}) + m.inout <- inOut{ + in: enableSignal{t: t, delay: delay, err: &err}, + out: done, + } + <-done + return err +} + +func (m *manager) disable(t Type) error { + done := make(chan struct{}) + m.inout <- inOut{ + in: disableSignal{t: t}, + out: done, + } + <-done + return nil +} + +func (m *manager) read(e []Event) (n int, err error) { + done := make(chan struct{}) + m.inout <- inOut{ + in: readSignal{dst: e, n: &n, err: &err}, + out: done, + } + <-done + return +} + +func readEvents(m *manager, e []Event) (n int, err error) { + num := len(e) + types := make([]C.int32_t, num) + timestamps := make([]C.int64_t, num) + vectors := make([]C.float, 3*num) + + n = int(C.android_readQueue( + m.m.looperId, m.m.queue, + C.int(num), + (*C.int32_t)(unsafe.Pointer(&types[0])), + (*C.int64_t)(unsafe.Pointer(×tamps[0])), + (*C.float)(unsafe.Pointer(&vectors[0]))), + ) + for i := 0; i < n; i++ { + e[i] = Event{ + Sensor: intToType[int(types[i])], + Timestamp: int64(timestamps[i]), + Data: []float64{ + float64(vectors[i*3]), + float64(vectors[i*3+1]), + float64(vectors[i*3+2]), + }, + } + } + return +} + +func (m *manager) close() error { + done := make(chan struct{}) + m.inout <- inOut{ + in: closeSignal{}, + out: done, + } + <-done + return nil +} + +var intToType = map[int]Type{ + C.ASENSOR_TYPE_ACCELEROMETER: Accelerometer, + C.ASENSOR_TYPE_GYROSCOPE: Gyroscope, + C.ASENSOR_TYPE_MAGNETIC_FIELD: Magnetometer, +} + +func typeToInt(t Type) C.int { + for k, v := range intToType { + if v == t { + return C.int(k) + } + } + return C.int(-1) +} diff --git a/src/golang.org/x/mobile/exp/sensor/sensor_notandroid.go b/src/golang.org/x/mobile/exp/sensor/sensor_notandroid.go new file mode 100644 index 0000000000..5a05ce8f75 --- /dev/null +++ b/src/golang.org/x/mobile/exp/sensor/sensor_notandroid.go @@ -0,0 +1,34 @@ +// Copyright 2015 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 !android + +package sensor + +import ( + "errors" + "time" +) + +type manager struct { +} + +func (m *manager) initialize() { +} + +func (m *manager) enable(t Type, delay time.Duration) error { + return errors.New("sensor: no sensors available") +} + +func (m *manager) disable(t Type) error { + return errors.New("sensor: no sensors available") +} + +func (m *manager) read(e []Event) (n int, err error) { + return 0, errors.New("sensor: no sensor data available") +} + +func (m *manager) close() error { + return nil +} diff --git a/src/golang.org/x/mobile/exp/sensor/sensors_android.c b/src/golang.org/x/mobile/exp/sensor/sensors_android.c new file mode 100644 index 0000000000..2a6d597b7b --- /dev/null +++ b/src/golang.org/x/mobile/exp/sensor/sensors_android.c @@ -0,0 +1,71 @@ +// Copyright 2015 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. + +#include +#include + +#include + +#include "sensors_android.h" + +void android_createManager(int looperId, android_SensorManager* dst) { + ASensorManager* manager = ASensorManager_getInstance(); + + ALooper* looper = ALooper_forThread(); + if (looper == NULL) { + looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS); + } + ASensorEventQueue* queue = ASensorManager_createEventQueue(manager, looper, looperId, NULL, NULL); + dst->looper = looper; + dst->queue = queue; + dst->looperId = looperId; +} + +int android_enableSensor(ASensorEventQueue* q, int s, int32_t usec) { + ASensorManager* manager = ASensorManager_getInstance(); + const ASensor* sensor = ASensorManager_getDefaultSensor(manager, s); + if (sensor == NULL) { + return 1; + } + ASensorEventQueue_enableSensor(q, sensor); + ASensorEventQueue_setEventRate(q, sensor, usec); + return 0; +} + +void android_disableSensor(ASensorEventQueue* q, int s) { + ASensorManager* manager = ASensorManager_getInstance(); + const ASensor* sensor = ASensorManager_getDefaultSensor(manager, s); + ASensorEventQueue_disableSensor(q, sensor); +} + +int android_readQueue(int looperId, ASensorEventQueue* q, int n, int32_t* types, int64_t* timestamps, float* vectors) { + int id; + int events; + ASensorEvent event; + int i = 0; + // Block for 30 secs at most, timeout if nothing happens. + // Try n times read from the event queue. + // If anytime timeout occurs, don't retry to read and immediately return. + // Consume the event queue entirely between polls. + while (i < n && (id = ALooper_pollAll(30*1000, NULL, &events, NULL)) >= 0) { + if (id != looperId) { + continue; + } + while (i < n && ASensorEventQueue_getEvents(q, &event, 1)) { + types[i] = event.type; + timestamps[i] = event.timestamp; + vectors[i*3] = event.vector.x; + vectors[i*3+1] = event.vector.y; + vectors[i*3+2] = event.vector.z; + i++; + } + } + return i; +} + +void android_destroyManager(android_SensorManager* m) { + ASensorManager* manager = ASensorManager_getInstance(); + ASensorManager_destroyEventQueue(manager, m->queue); + ALooper_release(m->looper); +} diff --git a/src/golang.org/x/mobile/exp/sensor/sensors_android.h b/src/golang.org/x/mobile/exp/sensor/sensors_android.h new file mode 100644 index 0000000000..6f7d8206ba --- /dev/null +++ b/src/golang.org/x/mobile/exp/sensor/sensors_android.h @@ -0,0 +1,20 @@ +// Copyright 2015 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. + +#ifndef SENSORS_ANDROID_H +#define SENSORS_ANDROID_H + +typedef struct android_SensorManager { + ASensorEventQueue* queue; + ALooper* looper; + int looperId; +} android_SensorManager; + +void android_createManager(int looperId, android_SensorManager* dst); +void android_destroyManager(android_SensorManager* m); +int android_enableSensor(ASensorEventQueue*, int, int32_t); +void android_disableSensor(ASensorEventQueue*, int); +int android_readQueue(int looperId, ASensorEventQueue* q, int n, int32_t* types, int64_t* timestamps, float* vectors); + +#endif diff --git a/src/golang.org/x/mobile/exp/sprite/clock/clock.go b/src/golang.org/x/mobile/exp/sprite/clock/clock.go new file mode 100644 index 0000000000..a468e66e70 --- /dev/null +++ b/src/golang.org/x/mobile/exp/sprite/clock/clock.go @@ -0,0 +1,26 @@ +// Copyright 2014 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. + +// Package clock provides a clock and time functions for a sprite engine. +package clock // import "golang.org/x/mobile/exp/sprite/clock" + +// A Time represents an instant in sprite time. +// +// The application using the sprite engine is responsible for +// determining sprite time. +// +// Typically time 0 is when the app is initialized and time is +// quantized at the intended frame rate. For example, an app may +// record wall time when it is initialized +// +// var start = time.Now() +// +// and then compute the current instant in time for 60 FPS: +// +// now := clock.Time(time.Since(start) * 60 / time.Second) +// +// An application can pause or reset sprite time, but it must be aware +// of any stateful sprite.Arranger instances that expect time to +// continue. +type Time int32 diff --git a/src/golang.org/x/mobile/exp/sprite/clock/tween.go b/src/golang.org/x/mobile/exp/sprite/clock/tween.go new file mode 100644 index 0000000000..f5f2a038cb --- /dev/null +++ b/src/golang.org/x/mobile/exp/sprite/clock/tween.go @@ -0,0 +1,83 @@ +// Copyright 2014 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. + +package clock + +// Standard tween functions. +// +// Easing means a slowing near the timing boundary, as defined by +// a cubic bezier curve. Exact parameters match the CSS properties. +var ( + EaseIn = CubicBezier(0.42, 0, 1, 1) + EaseOut = CubicBezier(0, 0, 0.58, 1) + EaseInOut = CubicBezier(0.42, 0, 0.58, 1) +) + +// Linear computes the fraction [0,1] that t lies between [t0,t1]. +func Linear(t0, t1, t Time) float32 { + if t >= t1 { + return 1 + } + if t <= t0 { + return 0 + } + return float32(t-t0) / float32(t1-t0) +} + +// CubicBezier generates a tween function determined by a Cubic Bézier curve. +// +// The parameters are cubic control parameters. The curve starts at (0,0) +// going toward (x0,y0), and arrives at (1,1) coming from (x1,y1). +func CubicBezier(x0, y0, x1, y1 float32) func(t0, t1, t Time) float32 { + return func(start, end, now Time) float32 { + // A Cubic-Bezier curve restricted to starting at (0,0) and + // ending at (1,1) is defined as + // + // B(t) = 3*(1-t)^2*t*P0 + 3*(1-t)*t^2*P1 + t^3 + // + // with derivative + // + // B'(t) = 3*(1-t)^2*P0 + 6*(1-t)*t*(P1-P0) + 3*t^2*(1-P1) + // + // Given a value x ∈ [0,1], we solve for t using Newton's + // method and solve for y using t. + + x := Linear(start, end, now) + + // Solve for t using x. + t := x + for i := 0; i < 5; i++ { + t2 := t * t + t3 := t2 * t + d := 1 - t + d2 := d * d + + nx := 3*d2*t*x0 + 3*d*t2*x1 + t3 + dxdt := 3*d2*x0 + 6*d*t*(x1-x0) + 3*t2*(1-x1) + if dxdt == 0 { + break + } + + t -= (nx - x) / dxdt + if t <= 0 || t >= 1 { + break + } + } + if t < 0 { + t = 0 + } + if t > 1 { + t = 1 + } + + // Solve for y using t. + t2 := t * t + t3 := t2 * t + d := 1 - t + d2 := d * d + y := 3*d2*t*y0 + 3*d*t2*y1 + t3 + + return y + } +} diff --git a/src/golang.org/x/mobile/exp/sprite/clock/tween_test.go b/src/golang.org/x/mobile/exp/sprite/clock/tween_test.go new file mode 100644 index 0000000000..2bcf1012b0 --- /dev/null +++ b/src/golang.org/x/mobile/exp/sprite/clock/tween_test.go @@ -0,0 +1,53 @@ +// Copyright 2014 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. + +package clock + +import "testing" + +func TestLinear(t *testing.T) { + t0 := Time(0) + t1 := Time(6 * 60) + now := Time(3 * 60) + + if c := Linear(t0, t1, now); c != 0.5 { + t.Errorf("c=%.2f, want 0.5", c) + } +} + +func TestCubicBezier(t *testing.T) { + t0 := Time(0) + t1 := Time(1e6) + + tests := []struct { + x0, y0, x1, y1 float32 + x, y float32 + }{ + {0.00, 0.1, 0.4, 1.00, 0.0, 0.00}, + {0.00, 0.1, 0.4, 1.00, 0.1, 0.26}, + {0.00, 0.1, 0.4, 1.00, 0.5, 0.79}, + {0.00, 0.1, 0.4, 1.00, 0.9, 0.99}, + {0.00, 0.1, 0.4, 1.00, 1.0, 1.00}, + {0.36, 0.2, 0.3, 0.85, 0.0, 0.0}, + {0.36, 0.2, 0.3, 0.85, 0.3059, 0.3952}, + {0.36, 0.2, 0.3, 0.85, 0.4493, 0.6408}, + {0.36, 0.2, 0.3, 0.85, 0.8116, 0.9410}, + {0.00, 0.0, 1.0, 1.00, 0.1, 0.1}, + {0.00, 0.0, 1.0, 1.00, 0.5, 0.5}, + {0.00, 0.0, 1.0, 1.00, 0.9, 0.9}, + {0.42, 0.0, 1.0, 1.00, 0.0, 0.0}, + } + + for _, test := range tests { + cb := CubicBezier(test.x0, test.y0, test.x1, test.y1) + now := t0 + Time(float32(t1-t0)*test.x) + y := cb(t0, t1, now) + + const epsilon = 0.01 + diff := y - test.y + if diff < -epsilon || +epsilon < diff { + t.Errorf("CubicBezier(%.2f,%.2f,%.2f,%.2f): for x=%.2f got y=%.2f, want %.2f", test.x0, test.y0, test.x1, test.y1, test.x, y, test.y) + } + } +} diff --git a/src/golang.org/x/mobile/exp/sprite/glsprite/glsprite.go b/src/golang.org/x/mobile/exp/sprite/glsprite/glsprite.go new file mode 100644 index 0000000000..e1bc4b6b93 --- /dev/null +++ b/src/golang.org/x/mobile/exp/sprite/glsprite/glsprite.go @@ -0,0 +1,142 @@ +// Copyright 2014 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. + +// Package glsprite implements a sprite Engine using OpenGL ES 2. +// +// Each sprite.Texture is loaded as a GL texture object and drawn +// to the screen via an affine transform done in a simple shader. +package glsprite // import "golang.org/x/mobile/exp/sprite/glsprite" + +import ( + "image" + "image/draw" + + "golang.org/x/mobile/event/config" + "golang.org/x/mobile/exp/f32" + "golang.org/x/mobile/exp/gl/glutil" + "golang.org/x/mobile/exp/sprite" + "golang.org/x/mobile/exp/sprite/clock" + "golang.org/x/mobile/geom" +) + +type node struct { + // TODO: move this into package sprite as Node.EngineFields.RelTransform?? + relTransform f32.Affine +} + +type texture struct { + glImage *glutil.Image + b image.Rectangle +} + +func (t *texture) Bounds() (w, h int) { return t.b.Dx(), t.b.Dy() } + +func (t *texture) Download(r image.Rectangle, dst draw.Image) { + panic("TODO") +} + +func (t *texture) Upload(r image.Rectangle, src image.Image) { + draw.Draw(t.glImage.RGBA, r, src, src.Bounds().Min, draw.Src) + t.glImage.Upload() +} + +func (t *texture) Unload() { + panic("TODO") +} + +func Engine() sprite.Engine { + return &engine{ + nodes: []*node{nil}, + } +} + +type engine struct { + glImages map[sprite.Texture]*glutil.Image + nodes []*node + + absTransforms []f32.Affine +} + +func (e *engine) Register(n *sprite.Node) { + if n.EngineFields.Index != 0 { + panic("glsprite: sprite.Node already registered") + } + o := &node{} + o.relTransform.Identity() + + e.nodes = append(e.nodes, o) + n.EngineFields.Index = int32(len(e.nodes) - 1) +} + +func (e *engine) Unregister(n *sprite.Node) { + panic("todo") +} + +func (e *engine) LoadTexture(src image.Image) (sprite.Texture, error) { + b := src.Bounds() + t := &texture{glutil.NewImage(b.Dx(), b.Dy()), b} + t.Upload(b, src) + // TODO: set "glImage.Pix = nil"?? We don't need the CPU-side image any more. + return t, nil +} + +func (e *engine) SetSubTex(n *sprite.Node, x sprite.SubTex) { + n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree? + n.EngineFields.SubTex = x +} + +func (e *engine) SetTransform(n *sprite.Node, m f32.Affine) { + n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree? + e.nodes[n.EngineFields.Index].relTransform = m +} + +func (e *engine) Render(scene *sprite.Node, t clock.Time, cfg config.Event) { + e.absTransforms = append(e.absTransforms[:0], f32.Affine{ + {1, 0, 0}, + {0, 1, 0}, + }) + e.render(scene, t, cfg) +} + +func (e *engine) render(n *sprite.Node, t clock.Time, cfg config.Event) { + if n.EngineFields.Index == 0 { + panic("glsprite: sprite.Node not registered") + } + if n.Arranger != nil { + n.Arranger.Arrange(e, n, t) + } + + // Push absTransforms. + // TODO: cache absolute transforms and use EngineFields.Dirty? + rel := &e.nodes[n.EngineFields.Index].relTransform + m := f32.Affine{} + m.Mul(&e.absTransforms[len(e.absTransforms)-1], rel) + e.absTransforms = append(e.absTransforms, m) + + if x := n.EngineFields.SubTex; x.T != nil { + x.T.(*texture).glImage.Draw( + cfg, + geom.Point{ + geom.Pt(m[0][2]), + geom.Pt(m[1][2]), + }, + geom.Point{ + geom.Pt(m[0][2] + m[0][0]), + geom.Pt(m[1][2] + m[1][0]), + }, + geom.Point{ + geom.Pt(m[0][2] + m[0][1]), + geom.Pt(m[1][2] + m[1][1]), + }, + x.R, + ) + } + + for c := n.FirstChild; c != nil; c = c.NextSibling { + e.render(c, t, cfg) + } + + // Pop absTransforms. + e.absTransforms = e.absTransforms[:len(e.absTransforms)-1] +} diff --git a/src/golang.org/x/mobile/exp/sprite/portable/affine_test.go b/src/golang.org/x/mobile/exp/sprite/portable/affine_test.go new file mode 100644 index 0000000000..3401c23f14 --- /dev/null +++ b/src/golang.org/x/mobile/exp/sprite/portable/affine_test.go @@ -0,0 +1,199 @@ +// Copyright 2014 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. + +package portable + +import ( + "image" + "image/color" + "image/draw" + "image/png" + "io/ioutil" + "math" + "os" + "testing" + + "golang.org/x/mobile/event/config" + "golang.org/x/mobile/exp/f32" + "golang.org/x/mobile/geom" +) + +func TestAffine(t *testing.T) { + f, err := os.Open("../../../testdata/testpattern.png") + if err != nil { + t.Fatal(err) + } + defer f.Close() + srcOrig, _, err := image.Decode(f) + if err != nil { + t.Fatal(err) + } + src := image.NewRGBA(srcOrig.Bounds()) + draw.Draw(src, src.Rect, srcOrig, srcOrig.Bounds().Min, draw.Src) + + const ( + pixW = 100 + pixH = 100 + ptW = geom.Pt(50) + ptH = geom.Pt(50) + ) + cfg := config.Event{ + WidthPx: pixW, + HeightPx: pixH, + WidthPt: ptW, + HeightPt: ptH, + PixelsPerPt: float32(pixW) / float32(ptW), + } + + got := image.NewRGBA(image.Rect(0, 0, pixW, pixH)) + blue := image.NewUniform(color.RGBA{B: 0xff, A: 0xff}) + draw.Draw(got, got.Bounds(), blue, image.Point{}, draw.Src) + + b := src.Bounds() + b.Min.X += 10 + b.Max.Y /= 2 + + var a f32.Affine + a.Identity() + a.Scale(&a, cfg.PixelsPerPt, cfg.PixelsPerPt) + a.Translate(&a, 0, 24) + a.Rotate(&a, float32(math.Asin(12./20))) + // See commentary in the render method defined in portable.go. + a.Scale(&a, 40/float32(b.Dx()), 20/float32(b.Dy())) + a.Inverse(&a) + + affine(got, src, b, nil, &a, draw.Over) + + ptTopLeft := geom.Point{0, 24} + ptBottomRight := geom.Point{12 + 32, 16} + + drawCross(got, 0, 0) + drawCross(got, int(ptTopLeft.X.Px(cfg.PixelsPerPt)), int(ptTopLeft.Y.Px(cfg.PixelsPerPt))) + drawCross(got, int(ptBottomRight.X.Px(cfg.PixelsPerPt)), int(ptBottomRight.Y.Px(cfg.PixelsPerPt))) + drawCross(got, pixW-1, pixH-1) + + const wantPath = "../../../testdata/testpattern-window.png" + f, err = os.Open(wantPath) + if err != nil { + t.Fatal(err) + } + defer f.Close() + wantSrc, _, err := image.Decode(f) + if err != nil { + t.Fatal(err) + } + want, ok := wantSrc.(*image.RGBA) + if !ok { + b := wantSrc.Bounds() + want = image.NewRGBA(b) + draw.Draw(want, b, wantSrc, b.Min, draw.Src) + } + + if !imageEq(got, want) { + gotPath, err := writeTempPNG("testpattern-window-got", got) + if err != nil { + t.Fatal(err) + } + t.Errorf("got\n%s\nwant\n%s", gotPath, wantPath) + } +} + +func TestAffineMask(t *testing.T) { + f, err := os.Open("../../../testdata/testpattern.png") + if err != nil { + t.Fatal(err) + } + defer f.Close() + srcOrig, _, err := image.Decode(f) + if err != nil { + t.Fatal(err) + } + b := srcOrig.Bounds() + src := image.NewRGBA(b) + draw.Draw(src, src.Rect, srcOrig, b.Min, draw.Src) + mask := image.NewAlpha(b) + for y := b.Min.Y; y < b.Max.Y; y++ { + for x := b.Min.X; x < b.Max.X; x++ { + mask.Set(x, y, color.Alpha{A: uint8(x - b.Min.X)}) + } + } + want := image.NewRGBA(b) + draw.DrawMask(want, want.Rect, src, b.Min, mask, b.Min, draw.Src) + + a := new(f32.Affine) + a.Identity() + got := image.NewRGBA(b) + affine(got, src, b, mask, a, draw.Src) + + if !imageEq(got, want) { + gotPath, err := writeTempPNG("testpattern-mask-got", got) + if err != nil { + t.Fatal(err) + } + wantPath, err := writeTempPNG("testpattern-mask-want", want) + if err != nil { + t.Fatal(err) + } + t.Errorf("got\n%s\nwant\n%s", gotPath, wantPath) + } +} + +func writeTempPNG(prefix string, m image.Image) (string, error) { + f, err := ioutil.TempFile("", prefix+"-") + if err != nil { + return "", err + } + f.Close() + path := f.Name() + ".png" + f, err = os.Create(path) + if err != nil { + return "", err + } + if err := png.Encode(f, m); err != nil { + f.Close() + return "", err + } + return path, f.Close() +} + +func drawCross(m *image.RGBA, x, y int) { + c := color.RGBA{0xff, 0, 0, 0xff} // red + m.SetRGBA(x+0, y-2, c) + m.SetRGBA(x+0, y-1, c) + m.SetRGBA(x-2, y+0, c) + m.SetRGBA(x-1, y+0, c) + m.SetRGBA(x+0, y+0, c) + m.SetRGBA(x+1, y+0, c) + m.SetRGBA(x+2, y+0, c) + m.SetRGBA(x+0, y+1, c) + m.SetRGBA(x+0, y+2, c) +} + +func eqEpsilon(x, y uint8) bool { + const epsilon = 9 + return x-y < epsilon || y-x < epsilon +} + +func colorEq(c0, c1 color.RGBA) bool { + return eqEpsilon(c0.R, c1.R) && eqEpsilon(c0.G, c1.G) && eqEpsilon(c0.B, c1.B) && eqEpsilon(c0.A, c1.A) +} + +func imageEq(m0, m1 *image.RGBA) bool { + b0 := m0.Bounds() + b1 := m1.Bounds() + if b0 != b1 { + return false + } + badPx := 0 + for y := b0.Min.Y; y < b0.Max.Y; y++ { + for x := b0.Min.X; x < b0.Max.X; x++ { + c0, c1 := m0.At(x, y).(color.RGBA), m1.At(x, y).(color.RGBA) + if !colorEq(c0, c1) { + badPx++ + } + } + } + badFrac := float64(badPx) / float64(b0.Dx()*b0.Dy()) + return badFrac < 0.01 +} diff --git a/src/golang.org/x/mobile/exp/sprite/portable/portable.go b/src/golang.org/x/mobile/exp/sprite/portable/portable.go new file mode 100644 index 0000000000..7d8c2471c7 --- /dev/null +++ b/src/golang.org/x/mobile/exp/sprite/portable/portable.go @@ -0,0 +1,191 @@ +// Copyright 2014 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. + +// Package portable implements a sprite Engine using the image package. +// +// It is intended to serve as a reference implementation for testing +// other sprite Engines written against OpenGL, or other more exotic +// modern hardware interfaces. +package portable // import "golang.org/x/mobile/exp/sprite/portable" + +import ( + "image" + "image/draw" + + xdraw "golang.org/x/image/draw" + "golang.org/x/image/math/f64" + "golang.org/x/mobile/event/config" + "golang.org/x/mobile/exp/f32" + "golang.org/x/mobile/exp/sprite" + "golang.org/x/mobile/exp/sprite/clock" +) + +// Engine builds a sprite Engine that renders onto dst. +func Engine(dst *image.RGBA) sprite.Engine { + return &engine{ + dst: dst, + nodes: []*node{nil}, + } +} + +type node struct { + // TODO: move this into package sprite as Node.EngineFields.RelTransform?? + relTransform f32.Affine +} + +type texture struct { + m *image.RGBA +} + +func (t *texture) Bounds() (w, h int) { + b := t.m.Bounds() + return b.Dx(), b.Dy() +} + +func (t *texture) Download(r image.Rectangle, dst draw.Image) { + draw.Draw(dst, r, t.m, t.m.Bounds().Min, draw.Src) +} + +func (t *texture) Upload(r image.Rectangle, src image.Image) { + draw.Draw(t.m, r, src, src.Bounds().Min, draw.Src) +} + +func (t *texture) Unload() { panic("TODO") } + +type engine struct { + dst *image.RGBA + nodes []*node + absTransforms []f32.Affine +} + +func (e *engine) Register(n *sprite.Node) { + if n.EngineFields.Index != 0 { + panic("portable: sprite.Node already registered") + } + + o := &node{} + o.relTransform.Identity() + + e.nodes = append(e.nodes, o) + n.EngineFields.Index = int32(len(e.nodes) - 1) +} + +func (e *engine) Unregister(n *sprite.Node) { + panic("todo") +} + +func (e *engine) LoadTexture(m image.Image) (sprite.Texture, error) { + b := m.Bounds() + w, h := b.Dx(), b.Dy() + + t := &texture{m: image.NewRGBA(image.Rect(0, 0, w, h))} + t.Upload(b, m) + return t, nil +} + +func (e *engine) SetSubTex(n *sprite.Node, x sprite.SubTex) { + n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree? + n.EngineFields.SubTex = x +} + +func (e *engine) SetTransform(n *sprite.Node, m f32.Affine) { + n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree? + e.nodes[n.EngineFields.Index].relTransform = m +} + +func (e *engine) Render(scene *sprite.Node, t clock.Time, cfg config.Event) { + // Affine transforms are done in geom.Pt. When finally drawing + // the geom.Pt onto an image.Image we need to convert to system + // pixels. We scale by cfg.PixelsPerPt to do this. + e.absTransforms = append(e.absTransforms[:0], f32.Affine{ + {cfg.PixelsPerPt, 0, 0}, + {0, cfg.PixelsPerPt, 0}, + }) + e.render(scene, t) +} + +func (e *engine) render(n *sprite.Node, t clock.Time) { + if n.EngineFields.Index == 0 { + panic("portable: sprite.Node not registered") + } + if n.Arranger != nil { + n.Arranger.Arrange(e, n, t) + } + + // Push absTransforms. + // TODO: cache absolute transforms and use EngineFields.Dirty? + rel := &e.nodes[n.EngineFields.Index].relTransform + m := f32.Affine{} + m.Mul(&e.absTransforms[len(e.absTransforms)-1], rel) + e.absTransforms = append(e.absTransforms, m) + + if x := n.EngineFields.SubTex; x.T != nil { + // Affine transforms work in geom.Pt, which is entirely + // independent of the number of pixels in a texture. A texture + // of any image.Rectangle bounds rendered with + // + // Affine{{1, 0, 0}, {0, 1, 0}} + // + // should have the dimensions (1pt, 1pt). To do this we divide + // by the pixel width and height, reducing the texture to + // (1px, 1px) of the destination image. Multiplying by + // cfg.PixelsPerPt, done in Render above, makes it (1pt, 1pt). + dx, dy := x.R.Dx(), x.R.Dy() + if dx > 0 && dy > 0 { + m.Scale(&m, 1/float32(dx), 1/float32(dy)) + // TODO(nigeltao): delete the double-inverse: one here and one + // inside func affine. + m.Inverse(&m) // See the documentation on the affine function. + affine(e.dst, x.T.(*texture).m, x.R, nil, &m, draw.Over) + } + } + + for c := n.FirstChild; c != nil; c = c.NextSibling { + e.render(c, t) + } + + // Pop absTransforms. + e.absTransforms = e.absTransforms[:len(e.absTransforms)-1] +} + +// affine draws each pixel of dst using bilinear interpolation of the +// affine-transformed position in src. This is equivalent to: +// +// for each (x,y) in dst: +// dst(x,y) = bilinear interpolation of src(a*(x,y)) +// +// While this is the simpler implementation, it can be counter- +// intuitive as an affine transformation is usually described in terms +// of the source, not the destination. For example, a scale transform +// +// Affine{{2, 0, 0}, {0, 2, 0}} +// +// will produce a dst that is half the size of src. To perform a +// traditional affine transform, use the inverse of the affine matrix. +func affine(dst *image.RGBA, src image.Image, srcb image.Rectangle, mask image.Image, a *f32.Affine, op draw.Op) { + // For legacy compatibility reasons, the matrix a transforms from dst-space + // to src-space. The golang.org/x/image/draw package's matrices transform + // from src-space to dst-space, so we invert (and adjust for different + // origins). + i := *a + i[0][2] += float32(srcb.Min.X) + i[1][2] += float32(srcb.Min.Y) + i.Inverse(&i) + i[0][2] += float32(dst.Rect.Min.X) + i[1][2] += float32(dst.Rect.Min.Y) + m := f64.Aff3{ + float64(i[0][0]), + float64(i[0][1]), + float64(i[0][2]), + float64(i[1][0]), + float64(i[1][1]), + float64(i[1][2]), + } + // TODO(nigeltao): is the caller or callee responsible for detecting + // transforms that are simple copies or scales, for which there are faster + // implementations in the xdraw package. + xdraw.ApproxBiLinear.Transform(dst, m, src, srcb, xdraw.Op(op), &xdraw.Options{ + SrcMask: mask, + }) +} diff --git a/src/golang.org/x/mobile/exp/sprite/sprite.go b/src/golang.org/x/mobile/exp/sprite/sprite.go new file mode 100644 index 0000000000..3028a7524e --- /dev/null +++ b/src/golang.org/x/mobile/exp/sprite/sprite.go @@ -0,0 +1,124 @@ +// Copyright 2014 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. + +// Package sprite provides a 2D scene graph for rendering and animation. +// +// A tree of nodes is drawn by a rendering Engine, provided by another +// package. The OS-independent Go version based on the image package is: +// +// golang.org/x/mobile/exp/sprite/portable +// +// An Engine draws a screen starting at a root Node. The tree is walked +// depth-first, with affine transformations applied at each level. +// +// Nodes are rendered relative to their parent. +// +// Typical main loop: +// +// for each frame { +// quantize time.Now() to a clock.Time +// process UI events +// modify the scene's nodes and animations (Arranger values) +// e.Render(scene, t, c) +// } +package sprite // import "golang.org/x/mobile/exp/sprite" + +import ( + "image" + "image/draw" + + "golang.org/x/mobile/event/config" + "golang.org/x/mobile/exp/f32" + "golang.org/x/mobile/exp/sprite/clock" +) + +type Arranger interface { + Arrange(e Engine, n *Node, t clock.Time) +} + +type Texture interface { + Bounds() (w, h int) + Download(r image.Rectangle, dst draw.Image) + Upload(r image.Rectangle, src image.Image) + Unload() +} + +type SubTex struct { + T Texture + R image.Rectangle +} + +type Engine interface { + Register(n *Node) + Unregister(n *Node) + + LoadTexture(a image.Image) (Texture, error) + + SetSubTex(n *Node, x SubTex) + SetTransform(n *Node, m f32.Affine) // sets transform relative to parent. + + // Render renders the scene arranged at the given time, for the given + // window configuration (dimensions and resolution). + Render(scene *Node, t clock.Time, c config.Event) +} + +// A Node is a renderable element and forms a tree of Nodes. +type Node struct { + Parent, FirstChild, LastChild, PrevSibling, NextSibling *Node + + Arranger Arranger + + // EngineFields contains fields that should only be accessed by Engine + // implementations. It is exported because such implementations can be + // in other packages. + EngineFields struct { + // TODO: separate TexDirty and TransformDirty bits? + Dirty bool + Index int32 + SubTex SubTex + } +} + +// AppendChild adds a node c as a child of n. +// +// It will panic if c already has a parent or siblings. +func (n *Node) AppendChild(c *Node) { + if c.Parent != nil || c.PrevSibling != nil || c.NextSibling != nil { + panic("sprite: AppendChild called for an attached child Node") + } + last := n.LastChild + if last != nil { + last.NextSibling = c + } else { + n.FirstChild = c + } + n.LastChild = c + c.Parent = n + c.PrevSibling = last +} + +// RemoveChild removes a node c that is a child of n. Afterwards, c will have +// no parent and no siblings. +// +// It will panic if c's parent is not n. +func (n *Node) RemoveChild(c *Node) { + if c.Parent != n { + panic("sprite: RemoveChild called for a non-child Node") + } + if n.FirstChild == c { + n.FirstChild = c.NextSibling + } + if c.NextSibling != nil { + c.NextSibling.PrevSibling = c.PrevSibling + } + if n.LastChild == c { + n.LastChild = c.PrevSibling + } + if c.PrevSibling != nil { + c.PrevSibling.NextSibling = c.NextSibling + } + c.Parent = nil + c.PrevSibling = nil + c.NextSibling = nil +} diff --git a/src/golang.org/x/mobile/geom/geom.go b/src/golang.org/x/mobile/geom/geom.go new file mode 100644 index 0000000000..23cf67bea1 --- /dev/null +++ b/src/golang.org/x/mobile/geom/geom.go @@ -0,0 +1,102 @@ +// Copyright 2014 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. + +/* +Package geom defines a two-dimensional coordinate system. + +The coordinate system is based on an left-handed Cartesian plane. +That is, X increases to the right and Y increases down. For (x,y), + + (0,0) → (1,0) + ↓ ↘ + (0,1) (1,1) + +The display window places the origin (0, 0) in the upper-left corner of +the screen. Positions on the plane are measured in typographic points, +1/72 of an inch, which is represented by the Pt type. + +Any interface that draws to the screen using types from the geom package +scales the number of pixels to maintain a Pt as 1/72 of an inch. +*/ +package geom // import "golang.org/x/mobile/geom" + +/* +Notes on the various underlying coordinate systems. + +Both Android and iOS (UIKit) use upper-left-origin coordinate systems +with for events, however they have different units. + +UIKit measures distance in points. A point is a single-pixel on a +pre-Retina display. UIKit maintains a scale factor that to turn points +into pixels. On current retina devices, the scale factor is 2.0. + +A UIKit point does not correspond to a fixed physical distance, as the +iPhone has a 163 DPI/PPI (326 PPI retina) display, and the iPad has a +132 PPI (264 retina) display. Points are 32-bit floats. + +Even though point is the official UIKit term, they are commonly called +pixels. Indeed, the units were equivalent until the retina display was +introduced. + +N.b. as a UIKit point is unrelated to a typographic point, it is not +related to this packages's Pt and Point types. + +More details about iOS drawing: + +https://developer.apple.com/library/ios/documentation/2ddrawing/conceptual/drawingprintingios/GraphicsDrawingOverview/GraphicsDrawingOverview.html + +Android uses pixels. Sub-pixel precision is possible, so pixels are +represented as 32-bit floats. The ACONFIGURATION_DENSITY enum provides +the screen DPI/PPI, which varies frequently between devices. + +It would be tempting to adopt the pixel, given the clear pixel/DPI split +in the core android events API. However, the plot thickens: + +http://developer.android.com/training/multiscreen/screendensities.html + +Android promotes the notion of a density-independent pixel in many of +their interfaces, often prefixed by "dp". 1dp is a real physical length, +as "independent" means it is assumed to be 1/160th of an inch and is +adjusted for the current screen. + +In addition, android has a scale-indepdendent pixel used for expressing +a user's preferred text size. The user text size preference is a useful +notion not yet expressed in the geom package. + +For the sake of clarity when working across platforms, the geom package +tries to put distance between it and the word pixel. +*/ + +import "fmt" + +// Pt is a length. +// +// The unit Pt is a typographical point, 1/72 of an inch (0.3527 mm). +// +// It can be be converted to a length in current device pixels by +// multiplying with PixelsPerPt after app initialization is complete. +type Pt float32 + +// Px converts the length to current device pixels. +func (p Pt) Px(pixelsPerPt float32) float32 { return float32(p) * pixelsPerPt } + +// String returns a string representation of p like "3.2pt". +func (p Pt) String() string { return fmt.Sprintf("%.2fpt", p) } + +// Point is a point in a two-dimensional plane. +type Point struct { + X, Y Pt +} + +// String returns a string representation of p like "(1.2,3.4)". +func (p Point) String() string { return fmt.Sprintf("(%.2f,%.2f)", p.X, p.Y) } + +// A Rectangle is region of points. +// The top-left point is Min, and the bottom-right point is Max. +type Rectangle struct { + Min, Max Point +} + +// String returns a string representation of r like "(3,4)-(6,5)". +func (r Rectangle) String() string { return r.Min.String() + "-" + r.Max.String() } diff --git a/src/golang.org/x/mobile/gl/consts.go b/src/golang.org/x/mobile/gl/consts.go new file mode 100644 index 0000000000..10d56f357d --- /dev/null +++ b/src/golang.org/x/mobile/gl/consts.go @@ -0,0 +1,348 @@ +// Copyright 2014 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. + +package gl + +/* +Partially generated from the Khronos OpenGL API specification in XML +format, which is covered by the license: + + Copyright (c) 2013-2014 The Khronos Group Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and/or associated documentation files (the + "Materials"), to deal in the Materials without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Materials, and to + permit persons to whom the Materials are 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 Materials. + + THE MATERIALS ARE 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 + MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + +*/ + +const ( + POINTS = 0x0000 + LINES = 0x0001 + LINE_LOOP = 0x0002 + LINE_STRIP = 0x0003 + TRIANGLES = 0x0004 + TRIANGLE_STRIP = 0x0005 + TRIANGLE_FAN = 0x0006 + SRC_COLOR = 0x0300 + ONE_MINUS_SRC_COLOR = 0x0301 + SRC_ALPHA = 0x0302 + ONE_MINUS_SRC_ALPHA = 0x0303 + DST_ALPHA = 0x0304 + ONE_MINUS_DST_ALPHA = 0x0305 + DST_COLOR = 0x0306 + ONE_MINUS_DST_COLOR = 0x0307 + SRC_ALPHA_SATURATE = 0x0308 + FUNC_ADD = 0x8006 + BLEND_EQUATION = 0x8009 + BLEND_EQUATION_RGB = 0x8009 + BLEND_EQUATION_ALPHA = 0x883D + FUNC_SUBTRACT = 0x800A + FUNC_REVERSE_SUBTRACT = 0x800B + BLEND_DST_RGB = 0x80C8 + BLEND_SRC_RGB = 0x80C9 + BLEND_DST_ALPHA = 0x80CA + BLEND_SRC_ALPHA = 0x80CB + CONSTANT_COLOR = 0x8001 + ONE_MINUS_CONSTANT_COLOR = 0x8002 + CONSTANT_ALPHA = 0x8003 + ONE_MINUS_CONSTANT_ALPHA = 0x8004 + BLEND_COLOR = 0x8005 + ARRAY_BUFFER = 0x8892 + ELEMENT_ARRAY_BUFFER = 0x8893 + ARRAY_BUFFER_BINDING = 0x8894 + ELEMENT_ARRAY_BUFFER_BINDING = 0x8895 + STREAM_DRAW = 0x88E0 + STATIC_DRAW = 0x88E4 + DYNAMIC_DRAW = 0x88E8 + BUFFER_SIZE = 0x8764 + BUFFER_USAGE = 0x8765 + CURRENT_VERTEX_ATTRIB = 0x8626 + FRONT = 0x0404 + BACK = 0x0405 + FRONT_AND_BACK = 0x0408 + TEXTURE_2D = 0x0DE1 + CULL_FACE = 0x0B44 + BLEND = 0x0BE2 + DITHER = 0x0BD0 + STENCIL_TEST = 0x0B90 + DEPTH_TEST = 0x0B71 + SCISSOR_TEST = 0x0C11 + POLYGON_OFFSET_FILL = 0x8037 + SAMPLE_ALPHA_TO_COVERAGE = 0x809E + SAMPLE_COVERAGE = 0x80A0 + INVALID_ENUM = 0x0500 + INVALID_VALUE = 0x0501 + INVALID_OPERATION = 0x0502 + OUT_OF_MEMORY = 0x0505 + CW = 0x0900 + CCW = 0x0901 + LINE_WIDTH = 0x0B21 + ALIASED_POINT_SIZE_RANGE = 0x846D + ALIASED_LINE_WIDTH_RANGE = 0x846E + CULL_FACE_MODE = 0x0B45 + FRONT_FACE = 0x0B46 + DEPTH_RANGE = 0x0B70 + DEPTH_WRITEMASK = 0x0B72 + DEPTH_CLEAR_VALUE = 0x0B73 + DEPTH_FUNC = 0x0B74 + STENCIL_CLEAR_VALUE = 0x0B91 + STENCIL_FUNC = 0x0B92 + STENCIL_FAIL = 0x0B94 + STENCIL_PASS_DEPTH_FAIL = 0x0B95 + STENCIL_PASS_DEPTH_PASS = 0x0B96 + STENCIL_REF = 0x0B97 + STENCIL_VALUE_MASK = 0x0B93 + STENCIL_WRITEMASK = 0x0B98 + STENCIL_BACK_FUNC = 0x8800 + STENCIL_BACK_FAIL = 0x8801 + STENCIL_BACK_PASS_DEPTH_FAIL = 0x8802 + STENCIL_BACK_PASS_DEPTH_PASS = 0x8803 + STENCIL_BACK_REF = 0x8CA3 + STENCIL_BACK_VALUE_MASK = 0x8CA4 + STENCIL_BACK_WRITEMASK = 0x8CA5 + VIEWPORT = 0x0BA2 + SCISSOR_BOX = 0x0C10 + COLOR_CLEAR_VALUE = 0x0C22 + COLOR_WRITEMASK = 0x0C23 + UNPACK_ALIGNMENT = 0x0CF5 + PACK_ALIGNMENT = 0x0D05 + MAX_TEXTURE_SIZE = 0x0D33 + MAX_VIEWPORT_DIMS = 0x0D3A + SUBPIXEL_BITS = 0x0D50 + RED_BITS = 0x0D52 + GREEN_BITS = 0x0D53 + BLUE_BITS = 0x0D54 + ALPHA_BITS = 0x0D55 + DEPTH_BITS = 0x0D56 + STENCIL_BITS = 0x0D57 + POLYGON_OFFSET_UNITS = 0x2A00 + POLYGON_OFFSET_FACTOR = 0x8038 + TEXTURE_BINDING_2D = 0x8069 + SAMPLE_BUFFERS = 0x80A8 + SAMPLES = 0x80A9 + SAMPLE_COVERAGE_VALUE = 0x80AA + SAMPLE_COVERAGE_INVERT = 0x80AB + NUM_COMPRESSED_TEXTURE_FORMATS = 0x86A2 + COMPRESSED_TEXTURE_FORMATS = 0x86A3 + DONT_CARE = 0x1100 + FASTEST = 0x1101 + NICEST = 0x1102 + GENERATE_MIPMAP_HINT = 0x8192 + BYTE = 0x1400 + UNSIGNED_BYTE = 0x1401 + SHORT = 0x1402 + UNSIGNED_SHORT = 0x1403 + INT = 0x1404 + UNSIGNED_INT = 0x1405 + FLOAT = 0x1406 + FIXED = 0x140C + DEPTH_COMPONENT = 0x1902 + ALPHA = 0x1906 + RGB = 0x1907 + RGBA = 0x1908 + LUMINANCE = 0x1909 + LUMINANCE_ALPHA = 0x190A + UNSIGNED_SHORT_4_4_4_4 = 0x8033 + UNSIGNED_SHORT_5_5_5_1 = 0x8034 + UNSIGNED_SHORT_5_6_5 = 0x8363 + MAX_VERTEX_ATTRIBS = 0x8869 + MAX_VERTEX_UNIFORM_VECTORS = 0x8DFB + MAX_VARYING_VECTORS = 0x8DFC + MAX_COMBINED_TEXTURE_IMAGE_UNITS = 0x8B4D + MAX_VERTEX_TEXTURE_IMAGE_UNITS = 0x8B4C + MAX_TEXTURE_IMAGE_UNITS = 0x8872 + MAX_FRAGMENT_UNIFORM_VECTORS = 0x8DFD + SHADER_TYPE = 0x8B4F + DELETE_STATUS = 0x8B80 + LINK_STATUS = 0x8B82 + VALIDATE_STATUS = 0x8B83 + ATTACHED_SHADERS = 0x8B85 + ACTIVE_UNIFORMS = 0x8B86 + ACTIVE_UNIFORM_MAX_LENGTH = 0x8B87 + ACTIVE_ATTRIBUTES = 0x8B89 + ACTIVE_ATTRIBUTE_MAX_LENGTH = 0x8B8A + SHADING_LANGUAGE_VERSION = 0x8B8C + CURRENT_PROGRAM = 0x8B8D + NEVER = 0x0200 + LESS = 0x0201 + EQUAL = 0x0202 + LEQUAL = 0x0203 + GREATER = 0x0204 + NOTEQUAL = 0x0205 + GEQUAL = 0x0206 + ALWAYS = 0x0207 + KEEP = 0x1E00 + REPLACE = 0x1E01 + INCR = 0x1E02 + DECR = 0x1E03 + INVERT = 0x150A + INCR_WRAP = 0x8507 + DECR_WRAP = 0x8508 + VENDOR = 0x1F00 + RENDERER = 0x1F01 + VERSION = 0x1F02 + EXTENSIONS = 0x1F03 + NEAREST = 0x2600 + LINEAR = 0x2601 + NEAREST_MIPMAP_NEAREST = 0x2700 + LINEAR_MIPMAP_NEAREST = 0x2701 + NEAREST_MIPMAP_LINEAR = 0x2702 + LINEAR_MIPMAP_LINEAR = 0x2703 + TEXTURE_MAG_FILTER = 0x2800 + TEXTURE_MIN_FILTER = 0x2801 + TEXTURE_WRAP_S = 0x2802 + TEXTURE_WRAP_T = 0x2803 + TEXTURE = 0x1702 + TEXTURE_CUBE_MAP = 0x8513 + TEXTURE_BINDING_CUBE_MAP = 0x8514 + TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515 + TEXTURE_CUBE_MAP_NEGATIVE_X = 0x8516 + TEXTURE_CUBE_MAP_POSITIVE_Y = 0x8517 + TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518 + TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519 + TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851A + MAX_CUBE_MAP_TEXTURE_SIZE = 0x851C + TEXTURE0 = 0x84C0 + TEXTURE1 = 0x84C1 + TEXTURE2 = 0x84C2 + TEXTURE3 = 0x84C3 + TEXTURE4 = 0x84C4 + TEXTURE5 = 0x84C5 + TEXTURE6 = 0x84C6 + TEXTURE7 = 0x84C7 + TEXTURE8 = 0x84C8 + TEXTURE9 = 0x84C9 + TEXTURE10 = 0x84CA + TEXTURE11 = 0x84CB + TEXTURE12 = 0x84CC + TEXTURE13 = 0x84CD + TEXTURE14 = 0x84CE + TEXTURE15 = 0x84CF + TEXTURE16 = 0x84D0 + TEXTURE17 = 0x84D1 + TEXTURE18 = 0x84D2 + TEXTURE19 = 0x84D3 + TEXTURE20 = 0x84D4 + TEXTURE21 = 0x84D5 + TEXTURE22 = 0x84D6 + TEXTURE23 = 0x84D7 + TEXTURE24 = 0x84D8 + TEXTURE25 = 0x84D9 + TEXTURE26 = 0x84DA + TEXTURE27 = 0x84DB + TEXTURE28 = 0x84DC + TEXTURE29 = 0x84DD + TEXTURE30 = 0x84DE + TEXTURE31 = 0x84DF + ACTIVE_TEXTURE = 0x84E0 + REPEAT = 0x2901 + CLAMP_TO_EDGE = 0x812F + MIRRORED_REPEAT = 0x8370 + VERTEX_ATTRIB_ARRAY_ENABLED = 0x8622 + VERTEX_ATTRIB_ARRAY_SIZE = 0x8623 + VERTEX_ATTRIB_ARRAY_STRIDE = 0x8624 + VERTEX_ATTRIB_ARRAY_TYPE = 0x8625 + VERTEX_ATTRIB_ARRAY_NORMALIZED = 0x886A + VERTEX_ATTRIB_ARRAY_POINTER = 0x8645 + VERTEX_ATTRIB_ARRAY_BUFFER_BINDING = 0x889F + IMPLEMENTATION_COLOR_READ_TYPE = 0x8B9A + IMPLEMENTATION_COLOR_READ_FORMAT = 0x8B9B + COMPILE_STATUS = 0x8B81 + INFO_LOG_LENGTH = 0x8B84 + SHADER_SOURCE_LENGTH = 0x8B88 + SHADER_COMPILER = 0x8DFA + SHADER_BINARY_FORMATS = 0x8DF8 + NUM_SHADER_BINARY_FORMATS = 0x8DF9 + LOW_FLOAT = 0x8DF0 + MEDIUM_FLOAT = 0x8DF1 + HIGH_FLOAT = 0x8DF2 + LOW_INT = 0x8DF3 + MEDIUM_INT = 0x8DF4 + HIGH_INT = 0x8DF5 + FRAMEBUFFER = 0x8D40 + RENDERBUFFER = 0x8D41 + RGBA4 = 0x8056 + RGB5_A1 = 0x8057 + RGB565 = 0x8D62 + DEPTH_COMPONENT16 = 0x81A5 + STENCIL_INDEX8 = 0x8D48 + RENDERBUFFER_WIDTH = 0x8D42 + RENDERBUFFER_HEIGHT = 0x8D43 + RENDERBUFFER_INTERNAL_FORMAT = 0x8D44 + RENDERBUFFER_RED_SIZE = 0x8D50 + RENDERBUFFER_GREEN_SIZE = 0x8D51 + RENDERBUFFER_BLUE_SIZE = 0x8D52 + RENDERBUFFER_ALPHA_SIZE = 0x8D53 + RENDERBUFFER_DEPTH_SIZE = 0x8D54 + RENDERBUFFER_STENCIL_SIZE = 0x8D55 + FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE = 0x8CD0 + FRAMEBUFFER_ATTACHMENT_OBJECT_NAME = 0x8CD1 + FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL = 0x8CD2 + FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE = 0x8CD3 + COLOR_ATTACHMENT0 = 0x8CE0 + DEPTH_ATTACHMENT = 0x8D00 + STENCIL_ATTACHMENT = 0x8D20 + FRAMEBUFFER_COMPLETE = 0x8CD5 + FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6 + FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7 + FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9 + FRAMEBUFFER_UNSUPPORTED = 0x8CDD + FRAMEBUFFER_BINDING = 0x8CA6 + RENDERBUFFER_BINDING = 0x8CA7 + MAX_RENDERBUFFER_SIZE = 0x84E8 + INVALID_FRAMEBUFFER_OPERATION = 0x0506 +) + +const ( + DEPTH_BUFFER_BIT = 0x00000100 + STENCIL_BUFFER_BIT = 0x00000400 + COLOR_BUFFER_BIT = 0x00004000 +) + +const ( + FLOAT_VEC2 = 0x8B50 + FLOAT_VEC3 = 0x8B51 + FLOAT_VEC4 = 0x8B52 + INT_VEC2 = 0x8B53 + INT_VEC3 = 0x8B54 + INT_VEC4 = 0x8B55 + BOOL = 0x8B56 + BOOL_VEC2 = 0x8B57 + BOOL_VEC3 = 0x8B58 + BOOL_VEC4 = 0x8B59 + FLOAT_MAT2 = 0x8B5A + FLOAT_MAT3 = 0x8B5B + FLOAT_MAT4 = 0x8B5C + SAMPLER_2D = 0x8B5E + SAMPLER_CUBE = 0x8B60 +) + +const ( + FRAGMENT_SHADER = 0x8B30 + VERTEX_SHADER = 0x8B31 +) + +const ( + FALSE = 0 + TRUE = 1 + ZERO = 0 + ONE = 1 + NO_ERROR = 0 + NONE = 0 +) diff --git a/src/golang.org/x/mobile/gl/doc.go b/src/golang.org/x/mobile/gl/doc.go new file mode 100644 index 0000000000..1309f43744 --- /dev/null +++ b/src/golang.org/x/mobile/gl/doc.go @@ -0,0 +1,60 @@ +// Copyright 2014 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. + +/* +Package gl implements Go bindings for OpenGL ES 2. + +The bindings are deliberately minimal, staying as close the C API as +possible. The semantics of each function maps onto functions +described in the Khronos documentation: + +https://www.khronos.org/opengles/sdk/docs/man/ + +One notable departure from the C API is the introduction of types +to represent common uses of GLint: Texture, Surface, Buffer, etc. + +Calls are not safe for concurrent use. However calls can be made from +any goroutine, the gl package removes the notion of thread-local +context. + +A tracing version of the OpenGL bindings is behind the `gldebug` build +tag. It acts as a simplified version of apitrace. Build your Go binary +with + + -tags gldebug + +and each call to a GL function will log its input, output, and any +error messages. For example, + + I/GoLog (27668): gl.GenBuffers(1) [Buffer(70001)] + I/GoLog (27668): gl.BindBuffer(ARRAY_BUFFER, Buffer(70001)) + I/GoLog (27668): gl.BufferData(ARRAY_BUFFER, 36, len(36), STATIC_DRAW) + I/GoLog (27668): gl.BindBuffer(ARRAY_BUFFER, Buffer(70001)) + I/GoLog (27668): gl.VertexAttribPointer(Attrib(0), 6, FLOAT, false, 0, 0) error: [INVALID_VALUE] + +The gldebug tracing has very high overhead, so make sure to remove +the build tag before deploying any binaries. +*/ +package gl // import "golang.org/x/mobile/gl" + +/* +Implementation details. + +All GL function calls fill out a C.struct_fnargs and drop it on the work +queue. The Start function drains the work queue and hands over a batch +of calls to C.process which runs them. This allows multiple GL calls to +be executed in a single cgo call. + +A GL call is marked as blocking if it returns a value, or if it takes a +Go pointer. In this case the call will not return until C.process sends a +value on the retvalue channel. + +This implementation ensures any goroutine can make GL calls, but it does +not make the GL interface safe for simultaneous use by multiple goroutines. +For the purpose of analyzing this code for race conditions, picture two +separate goroutines: one blocked on gl.Start, and another making calls to +the gl package exported functions. +*/ + +//go:generate go run gendebug.go -o gldebug.go diff --git a/src/golang.org/x/mobile/gl/gendebug.go b/src/golang.org/x/mobile/gl/gendebug.go new file mode 100644 index 0000000000..67fa42de9b --- /dev/null +++ b/src/golang.org/x/mobile/gl/gendebug.go @@ -0,0 +1,316 @@ +// Copyright 2014 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 ignore + +// The gendebug program takes gl.go and generates a version of it +// where each function includes tracing code that writes its arguments +// to the standard log. +package main + +import ( + "bytes" + "flag" + "fmt" + "go/ast" + "go/format" + "go/parser" + "go/printer" + "go/token" + "io/ioutil" + "log" + "os" + "strconv" +) + +var outfile = flag.String("o", "", "result will be written to the file instead of stdout.") + +var fset = new(token.FileSet) + +func typeString(t ast.Expr) string { + buf := new(bytes.Buffer) + printer.Fprint(buf, fset, t) + return buf.String() +} + +func typePrinter(t string) string { + switch t { + case "[]float32", "[]byte": + return "len(%d)" + } + return "%v" +} + +func typePrinterArg(t, name string) string { + switch t { + case "[]float32", "[]byte": + return "len(" + name + ")" + } + return name +} + +func die(err error) { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) +} + +func main() { + flag.Parse() + + f, err := parser.ParseFile(fset, "consts.go", nil, parser.ParseComments) + if err != nil { + die(err) + } + entries := enum(f) + + f, err = parser.ParseFile(fset, "gl.go", nil, parser.ParseComments) + if err != nil { + die(err) + } + + buf := new(bytes.Buffer) + + fmt.Fprint(buf, preamble) + + fmt.Fprintf(buf, "func (v Enum) String() string {\n") + fmt.Fprintf(buf, "\tswitch v {\n") + for _, e := range dedup(entries) { + fmt.Fprintf(buf, "\tcase 0x%x: return %q\n", e.value, e.name) + } + fmt.Fprintf(buf, "\t%s\n", `default: return fmt.Sprintf("gl.Enum(0x%x)", uint32(v))`) + fmt.Fprintf(buf, "\t}\n") + fmt.Fprintf(buf, "}\n\n") + + for _, d := range f.Decls { + // Before: + // func StencilMask(mask uint32) { + // C.glStencilMask(C.GLuint(mask)) + // } + // + // After: + // func StencilMask(mask uint32) { + // defer func() { + // errstr := errDrain() + // log.Printf("gl.StencilMask(%v) %v", mask, errstr) + // }() + // C.glStencilMask(C.GLuint(mask)) + // } + fn, ok := d.(*ast.FuncDecl) + if !ok { + continue + } + if fn.Recv != nil { + continue + } + + var ( + params []string + paramTypes []string + results []string + resultTypes []string + ) + + // Print function signature. + fmt.Fprintf(buf, "func %s(", fn.Name.Name) + for i, p := range fn.Type.Params.List { + if i > 0 { + fmt.Fprint(buf, ", ") + } + ty := typeString(p.Type) + for i, n := range p.Names { + if i > 0 { + fmt.Fprint(buf, ", ") + } + fmt.Fprintf(buf, "%s ", n.Name) + params = append(params, n.Name) + paramTypes = append(paramTypes, ty) + } + fmt.Fprint(buf, ty) + } + fmt.Fprintf(buf, ") (") + if fn.Type.Results != nil { + for i, r := range fn.Type.Results.List { + if i > 0 { + fmt.Fprint(buf, ", ") + } + ty := typeString(r.Type) + if len(r.Names) == 0 { + name := fmt.Sprintf("r%d", i) + fmt.Fprintf(buf, "%s ", name) + results = append(results, name) + resultTypes = append(resultTypes, ty) + } + for i, n := range r.Names { + if i > 0 { + fmt.Fprint(buf, ", ") + } + fmt.Fprintf(buf, "%s ", n.Name) + results = append(results, n.Name) + resultTypes = append(resultTypes, ty) + } + fmt.Fprint(buf, ty) + } + } + fmt.Fprintf(buf, ") {\n") + + // gl.GetError is used by errDrain, which will be made part of + // all functions. So do not apply it to gl.GetError to avoid + // infinite recursion. + skip := fn.Name.Name == "GetError" + + if !skip { + // Insert a defer block for tracing. + fmt.Fprintf(buf, "defer func() {\n") + fmt.Fprintf(buf, "\terrstr := errDrain()\n") + switch fn.Name.Name { + case "GetUniformLocation", "GetAttribLocation": + fmt.Fprintf(buf, "\tr0.name = name\n") + } + fmt.Fprintf(buf, "\tlog.Printf(\"gl.%s(", fn.Name.Name) + for i, p := range paramTypes { + if i > 0 { + fmt.Fprint(buf, ", ") + } + fmt.Fprint(buf, typePrinter(p)) + } + fmt.Fprintf(buf, ") ") + if len(resultTypes) > 1 { + fmt.Fprint(buf, "(") + } + for i, r := range resultTypes { + if i > 0 { + fmt.Fprint(buf, ", ") + } + fmt.Fprint(buf, typePrinter(r)) + } + if len(resultTypes) > 1 { + fmt.Fprint(buf, ") ") + } + fmt.Fprintf(buf, "%%v\"") + for i, p := range paramTypes { + fmt.Fprintf(buf, ", %s", typePrinterArg(p, params[i])) + } + for i, r := range resultTypes { + fmt.Fprintf(buf, ", %s", typePrinterArg(r, results[i])) + } + fmt.Fprintf(buf, ", errstr)\n") + fmt.Fprintf(buf, "}()\n") + } + + // Print original body of function. + for _, s := range fn.Body.List { + printer.Fprint(buf, fset, s) + fmt.Fprintf(buf, "\n") + } + fmt.Fprintf(buf, "}\n\n") + } + + b, err := format.Source(buf.Bytes()) + if err != nil { + os.Stdout.Write(buf.Bytes()) + die(err) + } + + if *outfile == "" { + os.Stdout.Write(b) + return + } + if err := ioutil.WriteFile(*outfile, b, 0666); err != nil { + die(err) + } +} + +const preamble = `// Copyright 2014 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. + +// Generated from gl.go using go generate. DO NOT EDIT. +// See doc.go for details. + +// +build linux darwin +// +build gldebug + +package gl + +// #include "work.h" +import "C" + +import ( + "fmt" + "log" + "math" + "unsafe" +) + +func errDrain() string { + var errs []Enum + for { + e := GetError() + if e == 0 { + break + } + errs = append(errs, e) + } + if len(errs) > 0 { + return fmt.Sprintf(" error: %v", errs) + } + return "" +} + +` + +type entry struct { + name string + value int +} + +// enum builds a list of all GL constants that make up the gl.Enum type. +func enum(f *ast.File) []entry { + var entries []entry + for _, d := range f.Decls { + gendecl, ok := d.(*ast.GenDecl) + if !ok { + continue + } + if gendecl.Tok != token.CONST { + continue + } + for _, s := range gendecl.Specs { + v, ok := s.(*ast.ValueSpec) + if !ok { + continue + } + if len(v.Names) != 1 || len(v.Values) != 1 { + continue + } + val, err := strconv.ParseInt(v.Values[0].(*ast.BasicLit).Value, 0, 32) + if err != nil { + log.Fatalf("enum %s: %v", v.Names[0].Name, err) + } + entries = append(entries, entry{v.Names[0].Name, int(val)}) + } + } + return entries +} + +func dedup(entries []entry) []entry { + // Find all duplicates. Use "%d" as the name of any value with duplicates. + seen := make(map[int]int) + for _, e := range entries { + seen[e.value]++ + } + var dedup []entry + for _, e := range entries { + switch seen[e.value] { + case 0: // skip, already here + case 1: + dedup = append(dedup, e) + default: + // value is duplicated + dedup = append(dedup, entry{fmt.Sprintf("%d", e.value), e.value}) + seen[e.value] = 0 + } + } + return dedup +} diff --git a/src/golang.org/x/mobile/gl/gl.go b/src/golang.org/x/mobile/gl/gl.go new file mode 100644 index 0000000000..bc91216873 --- /dev/null +++ b/src/golang.org/x/mobile/gl/gl.go @@ -0,0 +1,2205 @@ +// Copyright 2014 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 +// +build !gldebug + +package gl + +// TODO(crawshaw): build on more host platforms (makes it easier to gobind). +// TODO(crawshaw): expand to cover OpenGL ES 3. +// TODO(crawshaw): should functions on specific types become methods? E.g. +// func (t Texture) Bind(target Enum) +// this seems natural in Go, but moves us slightly +// further away from the underlying OpenGL spec. + +// #include "work.h" +import "C" + +import ( + "math" + "unsafe" +) + +// ActiveTexture sets the active texture unit. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glActiveTexture.xhtml +func ActiveTexture(texture Enum) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnActiveTexture, + a0: texture.c(), + }, + }) +} + +// AttachShader attaches a shader to a program. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glAttachShader.xhtml +func AttachShader(p Program, s Shader) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnAttachShader, + a0: p.c(), + a1: s.c(), + }, + }) +} + +// BindAttribLocation binds a vertex attribute index with a named +// variable. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glBindAttribLocation.xhtml +func BindAttribLocation(p Program, a Attrib, name string) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBindAttribLocation, + a0: p.c(), + a1: a.c(), + a2: C.uintptr_t(uintptr(unsafe.Pointer(C.CString(name)))), + }, + }) +} + +// BindBuffer binds a buffer. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glBindBuffer.xhtml +func BindBuffer(target Enum, b Buffer) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBindBuffer, + a0: target.c(), + a1: b.c(), + }, + }) +} + +// BindFramebuffer binds a framebuffer. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glBindFramebuffer.xhtml +func BindFramebuffer(target Enum, fb Framebuffer) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBindFramebuffer, + a0: target.c(), + a1: fb.c(), + }, + }) +} + +// BindRenderbuffer binds a render buffer. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glBindRenderbuffer.xhtml +func BindRenderbuffer(target Enum, rb Renderbuffer) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBindRenderbuffer, + a0: target.c(), + a1: rb.c(), + }, + }) +} + +// BindTexture binds a texture. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glBindTexture.xhtml +func BindTexture(target Enum, t Texture) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBindTexture, + a0: target.c(), + a1: t.c(), + }, + }) +} + +// BlendColor sets the blend color. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glBlendColor.xhtml +func BlendColor(red, green, blue, alpha float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBlendColor, + a0: C.uintptr_t(math.Float32bits(red)), + a1: C.uintptr_t(math.Float32bits(green)), + a2: C.uintptr_t(math.Float32bits(blue)), + a3: C.uintptr_t(math.Float32bits(alpha)), + }, + }) +} + +// BlendEquation sets both RGB and alpha blend equations. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glBlendEquation.xhtml +func BlendEquation(mode Enum) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBlendEquation, + a0: mode.c(), + }, + }) +} + +// BlendEquationSeparate sets RGB and alpha blend equations separately. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glBlendEquationSeparate.xhtml +func BlendEquationSeparate(modeRGB, modeAlpha Enum) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBlendEquationSeparate, + a0: modeRGB.c(), + a1: modeAlpha.c(), + }, + }) +} + +// BlendFunc sets the pixel blending factors. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glBlendFunc.xhtml +func BlendFunc(sfactor, dfactor Enum) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBlendFunc, + a0: sfactor.c(), + a1: dfactor.c(), + }, + }) +} + +// BlendFunc sets the pixel RGB and alpha blending factors separately. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glBlendFuncSeparate.xhtml +func BlendFuncSeparate(sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha Enum) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBlendFuncSeparate, + a0: sfactorRGB.c(), + a1: dfactorRGB.c(), + a2: sfactorAlpha.c(), + a3: dfactorAlpha.c(), + }, + }) +} + +// BufferData creates a new data store for the bound buffer object. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glBufferData.xhtml +func BufferData(target Enum, src []byte, usage Enum) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBufferData, + a0: target.c(), + a1: C.uintptr_t(len(src)), + a2: (C.uintptr_t)(uintptr(unsafe.Pointer(&src[0]))), + a3: usage.c(), + }, + blocking: true, + }) +} + +// BufferInit creates a new uninitialized data store for the bound buffer object. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glBufferData.xhtml +func BufferInit(target Enum, size int, usage Enum) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBufferData, + a0: target.c(), + a1: C.uintptr_t(size), + a2: 0, + a3: usage.c(), + }, + }) +} + +// BufferSubData sets some of data in the bound buffer object. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glBufferSubData.xhtml +func BufferSubData(target Enum, offset int, data []byte) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBufferSubData, + a0: target.c(), + a1: C.uintptr_t(offset), + a2: C.uintptr_t(len(data)), + a3: (C.uintptr_t)(uintptr(unsafe.Pointer(&data[0]))), + }, + blocking: true, + }) +} + +// CheckFramebufferStatus reports the completeness status of the +// active framebuffer. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glCheckFramebufferStatus.xhtml +func CheckFramebufferStatus(target Enum) Enum { + return Enum(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnCheckFramebufferStatus, + a0: target.c(), + }, + blocking: true, + })) +} + +// Clear clears the window. +// +// The behavior of Clear is influenced by the pixel ownership test, +// the scissor test, dithering, and the buffer writemasks. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glClear.xhtml +func Clear(mask Enum) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnClear, + a0: C.uintptr_t(mask), + }, + }) +} + +// ClearColor specifies the RGBA values used to clear color buffers. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glClearColor.xhtml +func ClearColor(red, green, blue, alpha float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnClearColor, + a0: C.uintptr_t(math.Float32bits(red)), + a1: C.uintptr_t(math.Float32bits(green)), + a2: C.uintptr_t(math.Float32bits(blue)), + a3: C.uintptr_t(math.Float32bits(alpha)), + }, + }) +} + +// ClearDepthf sets the depth value used to clear the depth buffer. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glClearDepthf.xhtml +func ClearDepthf(d float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnClearDepthf, + a0: C.uintptr_t(math.Float32bits(d)), + }, + }) +} + +// ClearStencil sets the index used to clear the stencil buffer. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glClearStencil.xhtml +func ClearStencil(s int) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnClearStencil, + a0: C.uintptr_t(s), + }, + }) +} + +// ColorMask specifies whether color components in the framebuffer +// can be written. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glColorMask.xhtml +func ColorMask(red, green, blue, alpha bool) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnColorMask, + a0: glBoolean(red), + a1: glBoolean(green), + a2: glBoolean(blue), + a3: glBoolean(alpha), + }, + }) +} + +// CompileShader compiles the source code of s. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glCompileShader.xhtml +func CompileShader(s Shader) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnCompileShader, + a0: s.c(), + }, + }) +} + +// CompressedTexImage2D writes a compressed 2D texture. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glCompressedTexImage2D.xhtml +func CompressedTexImage2D(target Enum, level int, internalformat Enum, width, height, border int, data []byte) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnCompressedTexImage2D, + a0: target.c(), + a1: C.uintptr_t(level), + a2: internalformat.c(), + a3: C.uintptr_t(width), + a4: C.uintptr_t(height), + a5: C.uintptr_t(border), + a6: C.uintptr_t(len(data)), + a7: C.uintptr_t(uintptr(unsafe.Pointer(&data[0]))), + }, + blocking: true, + }) +} + +// CompressedTexSubImage2D writes a subregion of a compressed 2D texture. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glCompressedTexSubImage2D.xhtml +func CompressedTexSubImage2D(target Enum, level, xoffset, yoffset, width, height int, format Enum, data []byte) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnCompressedTexSubImage2D, + a0: target.c(), + a1: C.uintptr_t(level), + a2: C.uintptr_t(xoffset), + a3: C.uintptr_t(yoffset), + a4: C.uintptr_t(width), + a5: C.uintptr_t(height), + a6: format.c(), + a7: C.uintptr_t(len(data)), + a8: C.uintptr_t(uintptr(unsafe.Pointer(&data[0]))), + }, + blocking: true, + }) +} + +// CopyTexImage2D writes a 2D texture from the current framebuffer. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glCopyTexImage2D.xhtml +func CopyTexImage2D(target Enum, level int, internalformat Enum, x, y, width, height, border int) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnCopyTexImage2D, + a0: target.c(), + a1: C.uintptr_t(level), + a2: internalformat.c(), + a3: C.uintptr_t(x), + a4: C.uintptr_t(y), + a5: C.uintptr_t(width), + a6: C.uintptr_t(height), + a7: C.uintptr_t(border), + }, + }) +} + +// CopyTexSubImage2D writes a 2D texture subregion from the +// current framebuffer. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glCopyTexSubImage2D.xhtml +func CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnCopyTexSubImage2D, + a0: target.c(), + a1: C.uintptr_t(level), + a2: C.uintptr_t(xoffset), + a3: C.uintptr_t(yoffset), + a4: C.uintptr_t(x), + a5: C.uintptr_t(y), + a6: C.uintptr_t(width), + a7: C.uintptr_t(height), + }, + }) +} + +// CreateBuffer creates a buffer object. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGenBuffers.xhtml +func CreateBuffer() Buffer { + return Buffer{Value: uint32(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGenBuffer, + }, + blocking: true, + }))} +} + +// CreateFramebuffer creates a framebuffer object. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGenFramebuffers.xhtml +func CreateFramebuffer() Framebuffer { + return Framebuffer{Value: uint32(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGenFramebuffer, + }, + blocking: true, + }))} +} + +// CreateProgram creates a new empty program object. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glCreateProgram.xhtml +func CreateProgram() Program { + return Program{Value: uint32(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnCreateProgram, + }, + blocking: true, + }))} +} + +// CreateRenderbuffer create a renderbuffer object. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGenRenderbuffers.xhtml +func CreateRenderbuffer() Renderbuffer { + return Renderbuffer{Value: uint32(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGenRenderbuffer, + }, + blocking: true, + }))} +} + +// CreateShader creates a new empty shader object. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glCreateShader.xhtml +func CreateShader(ty Enum) Shader { + return Shader{Value: uint32(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnCreateShader, + a0: C.uintptr_t(ty), + }, + blocking: true, + }))} +} + +// CreateTexture creates a texture object. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGenTextures.xhtml +func CreateTexture() Texture { + return Texture{Value: uint32(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGenTexture, + }, + blocking: true, + }))} +} + +// CullFace specifies which polygons are candidates for culling. +// +// Valid modes: FRONT, BACK, FRONT_AND_BACK. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glCullFace.xhtml +func CullFace(mode Enum) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnCullFace, + a0: mode.c(), + }, + }) +} + +// DeleteBuffer deletes the given buffer object. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glDeleteBuffers.xhtml +func DeleteBuffer(v Buffer) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDeleteBuffer, + a0: C.uintptr_t(v.Value), + }, + }) +} + +// DeleteFramebuffer deletes the given framebuffer object. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glDeleteFramebuffers.xhtml +func DeleteFramebuffer(v Framebuffer) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDeleteFramebuffer, + a0: C.uintptr_t(v.Value), + }, + }) +} + +// DeleteProgram deletes the given program object. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glDeleteProgram.xhtml +func DeleteProgram(p Program) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDeleteProgram, + a0: p.c(), + }, + }) +} + +// DeleteRenderbuffer deletes the given render buffer object. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glDeleteRenderbuffers.xhtml +func DeleteRenderbuffer(v Renderbuffer) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDeleteRenderbuffer, + a0: v.c(), + }, + }) +} + +// DeleteShader deletes shader s. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glDeleteShader.xhtml +func DeleteShader(s Shader) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDeleteShader, + a0: s.c(), + }, + }) +} + +// DeleteTexture deletes the given texture object. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glDeleteTextures.xhtml +func DeleteTexture(v Texture) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDeleteTexture, + a0: v.c(), + }, + }) +} + +// DepthFunc sets the function used for depth buffer comparisons. +// +// Valid fn values: +// NEVER +// LESS +// EQUAL +// LEQUAL +// GREATER +// NOTEQUAL +// GEQUAL +// ALWAYS +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glDepthFunc.xhtml +func DepthFunc(fn Enum) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDepthFunc, + a0: fn.c(), + }, + }) +} + +// DepthMask sets the depth buffer enabled for writing. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glDepthMask.xhtml +func DepthMask(flag bool) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDepthMask, + a0: glBoolean(flag), + }, + }) +} + +// DepthRangef sets the mapping from normalized device coordinates to +// window coordinates. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glDepthRangef.xhtml +func DepthRangef(n, f float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDepthRangef, + a0: C.uintptr_t(math.Float32bits(n)), + a1: C.uintptr_t(math.Float32bits(f)), + }, + }) +} + +// DetachShader detaches the shader s from the program p. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glDetachShader.xhtml +func DetachShader(p Program, s Shader) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDetachShader, + a0: p.c(), + a1: s.c(), + }, + }) +} + +// Disable disables various GL capabilities. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glDisable.xhtml +func Disable(cap Enum) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDisable, + a0: cap.c(), + }, + }) +} + +// DisableVertexAttribArray disables a vertex attribute array. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glDisableVertexAttribArray.xhtml +func DisableVertexAttribArray(a Attrib) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDisableVertexAttribArray, + a0: a.c(), + }, + }) +} + +// DrawArrays renders geometric primitives from the bound data. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glDrawArrays.xhtml +func DrawArrays(mode Enum, first, count int) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDrawArrays, + a0: mode.c(), + a1: C.uintptr_t(first), + a2: C.uintptr_t(count), + }, + }) +} + +// DrawElements renders primitives from a bound buffer. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glDrawElements.xhtml +func DrawElements(mode Enum, count int, ty Enum, offset int) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDrawElements, + a0: mode.c(), + a1: C.uintptr_t(count), + a2: ty.c(), + a3: C.uintptr_t(offset), + }, + }) +} + +// TODO(crawshaw): consider DrawElements8 / DrawElements16 / DrawElements32 + +// Enable enables various GL capabilities. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glEnable.xhtml +func Enable(cap Enum) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnEnable, + a0: cap.c(), + }, + }) +} + +// EnableVertexAttribArray enables a vertex attribute array. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glEnableVertexAttribArray.xhtml +func EnableVertexAttribArray(a Attrib) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnEnableVertexAttribArray, + a0: a.c(), + }, + }) +} + +// Finish blocks until the effects of all previously called GL +// commands are complete. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glFinish.xhtml +func Finish() { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnFinish, + }, + blocking: true, + }) +} + +// Flush empties all buffers. It does not block. +// +// An OpenGL implementation may buffer network communication, +// the command stream, or data inside the graphics accelerator. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glFlush.xhtml +func Flush() { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnFlush, + }, + blocking: true, + }) +} + +// FramebufferRenderbuffer attaches rb to the current frame buffer. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glFramebufferRenderbuffer.xhtml +func FramebufferRenderbuffer(target, attachment, rbTarget Enum, rb Renderbuffer) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnFramebufferRenderbuffer, + a0: target.c(), + a1: attachment.c(), + a2: rbTarget.c(), + a3: rb.c(), + }, + }) +} + +// FramebufferTexture2D attaches the t to the current frame buffer. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glFramebufferTexture2D.xhtml +func FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnFramebufferTexture2D, + a0: target.c(), + a1: attachment.c(), + a2: texTarget.c(), + a3: t.c(), + a4: C.uintptr_t(level), + }, + }) +} + +// FrontFace defines which polygons are front-facing. +// +// Valid modes: CW, CCW. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glFrontFace.xhtml +func FrontFace(mode Enum) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnFrontFace, + a0: mode.c(), + }, + }) +} + +// GenerateMipmap generates mipmaps for the current texture. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGenerateMipmap.xhtml +func GenerateMipmap(target Enum) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGenerateMipmap, + a0: target.c(), + }, + }) +} + +// GetActiveAttrib returns details about an active attribute variable. +// A value of 0 for index selects the first active attribute variable. +// Permissible values for index range from 0 to the number of active +// attribute variables minus 1. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGetActiveAttrib.xhtml +func GetActiveAttrib(p Program, index uint32) (name string, size int, ty Enum) { + bufSize := GetProgrami(p, ACTIVE_ATTRIBUTE_MAX_LENGTH) + buf := C.malloc(C.size_t(bufSize)) + defer C.free(buf) + var cSize C.GLint + var cType C.GLenum + + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetActiveAttrib, + a0: p.c(), + a1: C.uintptr_t(index), + a2: C.uintptr_t(bufSize), + a3: 0, + a4: C.uintptr_t(uintptr(unsafe.Pointer(&cSize))), + a5: C.uintptr_t(uintptr(unsafe.Pointer(&cType))), + a6: C.uintptr_t(uintptr(unsafe.Pointer(buf))), + }, + blocking: true, + }) + + return C.GoString((*C.char)(buf)), int(cSize), Enum(cType) +} + +// GetActiveUniform returns details about an active uniform variable. +// A value of 0 for index selects the first active uniform variable. +// Permissible values for index range from 0 to the number of active +// uniform variables minus 1. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGetActiveUniform.xhtml +func GetActiveUniform(p Program, index uint32) (name string, size int, ty Enum) { + bufSize := GetProgrami(p, ACTIVE_UNIFORM_MAX_LENGTH) + buf := C.malloc(C.size_t(bufSize)) + defer C.free(buf) + var cSize C.GLint + var cType C.GLenum + + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetActiveUniform, + a0: p.c(), + a1: C.uintptr_t(index), + a2: C.uintptr_t(bufSize), + a3: 0, + a4: C.uintptr_t(uintptr(unsafe.Pointer(&cSize))), + a5: C.uintptr_t(uintptr(unsafe.Pointer(&cType))), + a6: C.uintptr_t(uintptr(unsafe.Pointer(buf))), + }, + blocking: true, + }) + + return C.GoString((*C.char)(buf)), int(cSize), Enum(cType) +} + +// GetAttachedShaders returns the shader objects attached to program p. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGetAttachedShaders.xhtml +func GetAttachedShaders(p Program) []Shader { + shadersLen := GetProgrami(p, ATTACHED_SHADERS) + var n C.GLsizei + buf := make([]C.GLuint, shadersLen) + + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetAttachedShaders, + a0: p.c(), + a1: C.uintptr_t(shadersLen), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&n))), + a3: C.uintptr_t(uintptr(unsafe.Pointer(&buf[0]))), + }, + blocking: true, + }) + + buf = buf[:int(n)] + shaders := make([]Shader, len(buf)) + for i, s := range buf { + shaders[i] = Shader{Value: uint32(s)} + } + return shaders +} + +// GetAttribLocation returns the location of an attribute variable. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGetAttribLocation.xhtml +func GetAttribLocation(p Program, name string) Attrib { + return Attrib{Value: uint(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetAttribLocation, + a0: p.c(), + a1: C.uintptr_t(uintptr(unsafe.Pointer(C.CString(name)))), + }, + blocking: true, + }))} +} + +// GetBooleanv returns the boolean values of parameter pname. +// +// Many boolean parameters can be queried more easily using IsEnabled. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGet.xhtml +func GetBooleanv(dst []bool, pname Enum) { + buf := make([]C.GLboolean, len(dst)) + + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetBooleanv, + a0: pname.c(), + a1: C.uintptr_t(uintptr(unsafe.Pointer(&buf[0]))), + }, + blocking: true, + }) + + for i, v := range buf { + dst[i] = v != 0 + } +} + +// GetFloatv returns the float values of parameter pname. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGet.xhtml +func GetFloatv(dst []float32, pname Enum) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetFloatv, + a0: pname.c(), + a1: C.uintptr_t(uintptr(unsafe.Pointer(&dst[0]))), + }, + blocking: true, + }) +} + +// GetIntegerv returns the int values of parameter pname. +// +// Single values may be queried more easily using GetInteger. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGet.xhtml +func GetIntegerv(dst []int32, pname Enum) { + buf := make([]C.GLint, len(dst)) + + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetIntegerv, + a0: pname.c(), + a1: C.uintptr_t(uintptr(unsafe.Pointer(&buf[0]))), + }, + blocking: true, + }) + + for i, v := range buf { + dst[i] = int32(v) + } +} + +// GetInteger returns the int value of parameter pname. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGet.xhtml +func GetInteger(pname Enum) int { + var v [1]int32 + GetIntegerv(v[:], pname) + return int(v[0]) +} + +// GetBufferParameteri returns a parameter for the active buffer. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGetBufferParameter.xhtml +func GetBufferParameteri(target, value Enum) int { + return int(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetBufferParameteri, + a0: target.c(), + a1: value.c(), + }, + blocking: true, + })) +} + +// GetError returns the next error. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGetError.xhtml +func GetError() Enum { + return Enum(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetError, + }, + blocking: true, + })) +} + +// GetFramebufferAttachmentParameteri returns attachment parameters +// for the active framebuffer object. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGetFramebufferAttachmentParameteriv.xhtml +func GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int { + return int(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetFramebufferAttachmentParameteriv, + a0: target.c(), + a1: attachment.c(), + a2: pname.c(), + }, + blocking: true, + })) +} + +// GetProgrami returns a parameter value for a program. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGetProgramiv.xhtml +func GetProgrami(p Program, pname Enum) int { + return int(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetProgramiv, + a0: p.c(), + a1: pname.c(), + }, + blocking: true, + })) +} + +// GetProgramInfoLog returns the information log for a program. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGetProgramInfoLog.xhtml +func GetProgramInfoLog(p Program) string { + infoLen := GetProgrami(p, INFO_LOG_LENGTH) + buf := C.malloc(C.size_t(infoLen)) + defer C.free(buf) + + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetProgramInfoLog, + a0: p.c(), + a1: C.uintptr_t(infoLen), + a2: 0, + a3: C.uintptr_t(uintptr(buf)), + }, + blocking: true, + }) + + return C.GoString((*C.char)(buf)) +} + +// GetRenderbufferParameteri returns a parameter value for a render buffer. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGetRenderbufferParameteriv.xhtml +func GetRenderbufferParameteri(target, pname Enum) int { + return int(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetRenderbufferParameteriv, + a0: target.c(), + a1: pname.c(), + }, + blocking: true, + })) +} + +// GetShaderi returns a parameter value for a shader. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGetShaderiv.xhtml +func GetShaderi(s Shader, pname Enum) int { + return int(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetShaderiv, + a0: s.c(), + a1: pname.c(), + }, + blocking: true, + })) +} + +// GetShaderInfoLog returns the information log for a shader. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGetShaderInfoLog.xhtml +func GetShaderInfoLog(s Shader) string { + infoLen := GetShaderi(s, INFO_LOG_LENGTH) + buf := C.malloc(C.size_t(infoLen)) + defer C.free(buf) + + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetShaderInfoLog, + a0: s.c(), + a1: C.uintptr_t(infoLen), + a2: 0, + a3: C.uintptr_t(uintptr(buf)), + }, + blocking: true, + }) + + return C.GoString((*C.char)(buf)) +} + +// GetShaderPrecisionFormat returns range and precision limits for +// shader types. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGetShaderPrecisionFormat.xhtml +func GetShaderPrecisionFormat(shadertype, precisiontype Enum) (rangeLow, rangeHigh, precision int) { + var cRange [2]C.GLint + var cPrecision C.GLint + + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetShaderPrecisionFormat, + a0: shadertype.c(), + a1: precisiontype.c(), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&cRange[0]))), + a3: C.uintptr_t(uintptr(unsafe.Pointer(&cPrecision))), + }, + blocking: true, + }) + + return int(cRange[0]), int(cRange[1]), int(cPrecision) +} + +// GetShaderSource returns source code of shader s. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGetShaderSource.xhtml +func GetShaderSource(s Shader) string { + sourceLen := GetShaderi(s, SHADER_SOURCE_LENGTH) + if sourceLen == 0 { + return "" + } + buf := C.malloc(C.size_t(sourceLen)) + defer C.free(buf) + + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetShaderSource, + a0: s.c(), + a1: C.uintptr_t(sourceLen), + a2: 0, + a3: C.uintptr_t(uintptr(buf)), + }, + blocking: true, + }) + + return C.GoString((*C.char)(buf)) +} + +// GetString reports current GL state. +// +// Valid name values: +// EXTENSIONS +// RENDERER +// SHADING_LANGUAGE_VERSION +// VENDOR +// VERSION +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGetString.xhtml +func GetString(pname Enum) string { + ret := enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetString, + a0: pname.c(), + }, + blocking: true, + }) + return C.GoString((*C.char)((unsafe.Pointer(uintptr(ret))))) +} + +// GetTexParameterfv returns the float values of a texture parameter. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGetTexParameter.xhtml +func GetTexParameterfv(dst []float32, target, pname Enum) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetTexParameterfv, + a0: target.c(), + a1: pname.c(), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&dst[0]))), + }, + blocking: true, + }) +} + +// GetTexParameteriv returns the int values of a texture parameter. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGetTexParameter.xhtml +func GetTexParameteriv(dst []int32, target, pname Enum) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetTexParameteriv, + a0: target.c(), + a1: pname.c(), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&dst[0]))), + }, + blocking: true, + }) +} + +// GetUniformfv returns the float values of a uniform variable. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGetUniform.xhtml +func GetUniformfv(dst []float32, src Uniform, p Program) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetUniformfv, + a0: p.c(), + a1: src.c(), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&dst[0]))), + }, + blocking: true, + }) +} + +// GetUniformiv returns the float values of a uniform variable. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGetUniform.xhtml +func GetUniformiv(dst []int32, src Uniform, p Program) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetUniformiv, + a0: p.c(), + a1: src.c(), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&dst[0]))), + }, + blocking: true, + }) +} + +// GetUniformLocation returns the location of a uniform variable. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGetUniformLocation.xhtml +func GetUniformLocation(p Program, name string) Uniform { + return Uniform{Value: int32(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetUniformLocation, + a0: p.c(), + a1: C.uintptr_t(uintptr(unsafe.Pointer(C.CString(name)))), + }, + blocking: true, + }))} +} + +// GetVertexAttribf reads the float value of a vertex attribute. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGetVertexAttrib.xhtml +func GetVertexAttribf(src Attrib, pname Enum) float32 { + var params [1]float32 + GetVertexAttribfv(params[:], src, pname) + return params[0] +} + +// GetVertexAttribfv reads float values of a vertex attribute. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGetVertexAttrib.xhtml +func GetVertexAttribfv(dst []float32, src Attrib, pname Enum) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetVertexAttribfv, + a0: src.c(), + a1: pname.c(), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&dst[0]))), + }, + blocking: true, + }) +} + +// GetVertexAttribi reads the int value of a vertex attribute. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGetVertexAttrib.xhtml +func GetVertexAttribi(src Attrib, pname Enum) int32 { + var params [1]int32 + GetVertexAttribiv(params[:], src, pname) + return params[0] +} + +// GetVertexAttribiv reads int values of a vertex attribute. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glGetVertexAttrib.xhtml +func GetVertexAttribiv(dst []int32, src Attrib, pname Enum) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetVertexAttribiv, + a0: src.c(), + a1: pname.c(), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&dst[0]))), + }, + blocking: true, + }) +} + +// TODO(crawshaw): glGetVertexAttribPointerv + +// Hint sets implementation-specific modes. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glHint.xhtml +func Hint(target, mode Enum) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnHint, + a0: target.c(), + a1: mode.c(), + }, + }) +} + +// IsBuffer reports if b is a valid buffer. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glIsBuffer.xhtml +func IsBuffer(b Buffer) bool { + return 0 != enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnIsBuffer, + a0: b.c(), + }, + blocking: true, + }) +} + +// IsEnabled reports if cap is an enabled capability. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glIsEnabled.xhtml +func IsEnabled(cap Enum) bool { + return 0 != enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnIsEnabled, + a0: cap.c(), + }, + blocking: true, + }) +} + +// IsFramebuffer reports if fb is a valid frame buffer. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glIsFramebuffer.xhtml +func IsFramebuffer(fb Framebuffer) bool { + return 0 != enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnIsFramebuffer, + a0: fb.c(), + }, + blocking: true, + }) +} + +// IsProgram reports if p is a valid program object. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glIsProgram.xhtml +func IsProgram(p Program) bool { + return 0 != enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnIsProgram, + a0: p.c(), + }, + blocking: true, + }) +} + +// IsRenderbuffer reports if rb is a valid render buffer. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glIsRenderbuffer.xhtml +func IsRenderbuffer(rb Renderbuffer) bool { + return 0 != enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnIsRenderbuffer, + a0: rb.c(), + }, + blocking: true, + }) +} + +// IsShader reports if s is valid shader. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glIsShader.xhtml +func IsShader(s Shader) bool { + return 0 != enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnIsShader, + a0: s.c(), + }, + blocking: true, + }) +} + +// IsTexture reports if t is a valid texture. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glIsTexture.xhtml +func IsTexture(t Texture) bool { + return 0 != enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnIsTexture, + a0: t.c(), + }, + blocking: true, + }) +} + +// LineWidth specifies the width of lines. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glLineWidth.xhtml +func LineWidth(width float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnLineWidth, + a0: C.uintptr_t(math.Float32bits(width)), + }, + }) +} + +// LinkProgram links the specified program. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glLinkProgram.xhtml +func LinkProgram(p Program) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnLinkProgram, + a0: p.c(), + }, + }) +} + +// PixelStorei sets pixel storage parameters. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glPixelStorei.xhtml +func PixelStorei(pname Enum, param int32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnPixelStorei, + a0: pname.c(), + a1: C.uintptr_t(param), + }, + }) +} + +// PolygonOffset sets the scaling factors for depth offsets. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glPolygonOffset.xhtml +func PolygonOffset(factor, units float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnPolygonOffset, + a0: C.uintptr_t(math.Float32bits(factor)), + a1: C.uintptr_t(math.Float32bits(units)), + }, + }) +} + +// ReadPixels returns pixel data from a buffer. +// +// In GLES 3, the source buffer is controlled with ReadBuffer. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glReadPixels.xhtml +func ReadPixels(dst []byte, x, y, width, height int, format, ty Enum) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnReadPixels, + // TODO(crawshaw): support PIXEL_PACK_BUFFER in GLES3, uses offset. + a0: C.uintptr_t(x), + a1: C.uintptr_t(y), + a2: C.uintptr_t(width), + a3: C.uintptr_t(height), + a4: format.c(), + a5: ty.c(), + a6: C.uintptr_t(uintptr(unsafe.Pointer(&dst[0]))), + }, + blocking: true, + }) +} + +// ReleaseShaderCompiler frees resources allocated by the shader compiler. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glReleaseShaderCompiler.xhtml +func ReleaseShaderCompiler() { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnReleaseShaderCompiler, + }, + }) +} + +// RenderbufferStorage establishes the data storage, format, and +// dimensions of a renderbuffer object's image. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glRenderbufferStorage.xhtml +func RenderbufferStorage(target, internalFormat Enum, width, height int) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnRenderbufferStorage, + a0: target.c(), + a1: internalFormat.c(), + a2: C.uintptr_t(width), + a3: C.uintptr_t(height), + }, + }) +} + +// SampleCoverage sets multisample coverage parameters. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glSampleCoverage.xhtml +func SampleCoverage(value float32, invert bool) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnSampleCoverage, + a0: C.uintptr_t(math.Float32bits(value)), + a1: glBoolean(invert), + }, + }) +} + +// Scissor defines the scissor box rectangle, in window coordinates. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glScissor.xhtml +func Scissor(x, y, width, height int32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnScissor, + a0: C.uintptr_t(x), + a1: C.uintptr_t(y), + a2: C.uintptr_t(width), + a3: C.uintptr_t(height), + }, + }) +} + +// TODO(crawshaw): ShaderBinary + +// ShaderSource sets the source code of s to the given source code. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glShaderSource.xhtml +func ShaderSource(s Shader, src string) { + // We are passing a char**. Make sure both the string and its + // containing 1-element array are off the stack. Both are freed + // in work.c. + cstr := C.CString(src) + cstrp := (**C.char)(C.malloc(C.size_t(unsafe.Sizeof(cstr)))) + *cstrp = cstr + + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnShaderSource, + a0: s.c(), + a1: 1, + a2: C.uintptr_t(uintptr(unsafe.Pointer(cstrp))), + }, + }) +} + +// StencilFunc sets the front and back stencil test reference value. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glStencilFunc.xhtml +func StencilFunc(fn Enum, ref int, mask uint32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnStencilFunc, + a0: fn.c(), + a1: C.uintptr_t(ref), + a2: C.uintptr_t(mask), + }, + }) +} + +// StencilFunc sets the front or back stencil test reference value. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glStencilFuncSeparate.xhtml +func StencilFuncSeparate(face, fn Enum, ref int, mask uint32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnStencilFuncSeparate, + a0: face.c(), + a1: fn.c(), + a2: C.uintptr_t(ref), + a3: C.uintptr_t(mask), + }, + }) +} + +// StencilMask controls the writing of bits in the stencil planes. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glStencilMask.xhtml +func StencilMask(mask uint32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnStencilMask, + a0: C.uintptr_t(mask), + }, + }) +} + +// StencilMaskSeparate controls the writing of bits in the stencil planes. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glStencilMaskSeparate.xhtml +func StencilMaskSeparate(face Enum, mask uint32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnStencilMaskSeparate, + a0: face.c(), + a1: C.uintptr_t(mask), + }, + }) +} + +// StencilOp sets front and back stencil test actions. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glStencilOp.xhtml +func StencilOp(fail, zfail, zpass Enum) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnStencilOp, + a0: fail.c(), + a1: zfail.c(), + a2: zpass.c(), + }, + }) +} + +// StencilOpSeparate sets front or back stencil tests. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glStencilOpSeparate.xhtml +func StencilOpSeparate(face, sfail, dpfail, dppass Enum) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnStencilOpSeparate, + a0: face.c(), + a1: sfail.c(), + a2: dpfail.c(), + a3: dppass.c(), + }, + }) +} + +// TexImage2D writes a 2D texture image. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glTexImage2D.xhtml +func TexImage2D(target Enum, level int, width, height int, format Enum, ty Enum, data []byte) { + // It is common to pass TexImage2D a nil data, indicating that a + // bound GL buffer is being used as the source. In that case, it + // is not necessary to block. + blocking, a7 := false, C.uintptr_t(0) + if len(data) > 0 { + blocking, a7 = true, C.uintptr_t(uintptr(unsafe.Pointer(&data[0]))) + } + + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnTexImage2D, + // TODO(crawshaw): GLES3 offset for PIXEL_UNPACK_BUFFER and PIXEL_PACK_BUFFER. + a0: target.c(), + a1: C.uintptr_t(level), + a2: C.uintptr_t(format), + a3: C.uintptr_t(width), + a4: C.uintptr_t(height), + a5: format.c(), + a6: ty.c(), + a7: a7, + }, + blocking: blocking, + }) +} + +// TexSubImage2D writes a subregion of a 2D texture image. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glTexSubImage2D.xhtml +func TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnTexSubImage2D, + // TODO(crawshaw): GLES3 offset for PIXEL_UNPACK_BUFFER and PIXEL_PACK_BUFFER. + a0: target.c(), + a1: C.uintptr_t(level), + a2: C.uintptr_t(x), + a3: C.uintptr_t(y), + a4: C.uintptr_t(width), + a5: C.uintptr_t(height), + a6: format.c(), + a7: ty.c(), + a8: C.uintptr_t(uintptr(unsafe.Pointer(&data[0]))), + }, + blocking: true, + }) +} + +// TexParameterf sets a float texture parameter. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glTexParameter.xhtml +func TexParameterf(target, pname Enum, param float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnTexParameterf, + a0: target.c(), + a1: pname.c(), + a2: C.uintptr_t(math.Float32bits(param)), + }, + }) +} + +// TexParameterfv sets a float texture parameter array. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glTexParameter.xhtml +func TexParameterfv(target, pname Enum, params []float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnTexParameterfv, + a0: target.c(), + a1: pname.c(), + a2: C.uintptr_t(uintptr(unsafe.Pointer(¶ms[0]))), + }, + blocking: true, + }) +} + +// TexParameteri sets an integer texture parameter. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glTexParameter.xhtml +func TexParameteri(target, pname Enum, param int) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnTexParameteri, + a0: target.c(), + a1: pname.c(), + a2: C.uintptr_t(param), + }, + }) +} + +// TexParameteriv sets an integer texture parameter array. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glTexParameter.xhtml +func TexParameteriv(target, pname Enum, params []int32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnTexParameteriv, + a0: target.c(), + a1: pname.c(), + a2: C.uintptr_t(uintptr(unsafe.Pointer(¶ms[0]))), + }, + blocking: true, + }) +} + +// Uniform1f writes a float uniform variable. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml +func Uniform1f(dst Uniform, v float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform1f, + a0: dst.c(), + a1: C.uintptr_t(math.Float32bits(v)), + }, + }) +} + +// Uniform1fv writes a [len(src)]float uniform array. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml +func Uniform1fv(dst Uniform, src []float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform1fv, + a0: dst.c(), + a1: C.uintptr_t(len(src)), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +// Uniform1i writes an int uniform variable. +// +// Uniform1i and Uniform1iv are the only two functions that may be used +// to load uniform variables defined as sampler types. Loading samplers +// with any other function will result in a INVALID_OPERATION error. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml +func Uniform1i(dst Uniform, v int) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform1i, + a0: dst.c(), + a1: C.uintptr_t(v), + }, + }) +} + +// Uniform1iv writes a int uniform array of len(src) elements. +// +// Uniform1i and Uniform1iv are the only two functions that may be used +// to load uniform variables defined as sampler types. Loading samplers +// with any other function will result in a INVALID_OPERATION error. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml +func Uniform1iv(dst Uniform, src []int32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform1iv, + a0: dst.c(), + a1: C.uintptr_t(len(src)), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +// Uniform2f writes a vec2 uniform variable. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml +func Uniform2f(dst Uniform, v0, v1 float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform2f, + a0: dst.c(), + a1: C.uintptr_t(math.Float32bits(v0)), + a2: C.uintptr_t(math.Float32bits(v1)), + }, + }) +} + +// Uniform2fv writes a vec2 uniform array of len(src)/2 elements. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml +func Uniform2fv(dst Uniform, src []float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform2fv, + a0: dst.c(), + a1: C.uintptr_t(len(src) / 2), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +// Uniform2i writes an ivec2 uniform variable. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml +func Uniform2i(dst Uniform, v0, v1 int) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform2i, + a0: dst.c(), + a1: C.uintptr_t(v0), + a2: C.uintptr_t(v1), + }, + }) +} + +// Uniform2iv writes an ivec2 uniform array of len(src)/2 elements. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml +func Uniform2iv(dst Uniform, src []int32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform2iv, + a0: dst.c(), + a1: C.uintptr_t(len(src) / 2), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +// Uniform3f writes a vec3 uniform variable. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml +func Uniform3f(dst Uniform, v0, v1, v2 float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform3f, + a0: dst.c(), + a1: C.uintptr_t(math.Float32bits(v0)), + a2: C.uintptr_t(math.Float32bits(v1)), + a3: C.uintptr_t(math.Float32bits(v2)), + }, + }) +} + +// Uniform3fv writes a vec3 uniform array of len(src)/3 elements. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml +func Uniform3fv(dst Uniform, src []float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform3fv, + a0: dst.c(), + a1: C.uintptr_t(len(src) / 3), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +// Uniform3i writes an ivec3 uniform variable. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml +func Uniform3i(dst Uniform, v0, v1, v2 int32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform3i, + a0: dst.c(), + a1: C.uintptr_t(v0), + a2: C.uintptr_t(v1), + a3: C.uintptr_t(v2), + }, + }) +} + +// Uniform3iv writes an ivec3 uniform array of len(src)/3 elements. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml +func Uniform3iv(dst Uniform, src []int32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform3iv, + a0: dst.c(), + a1: C.uintptr_t(len(src) / 3), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +// Uniform4f writes a vec4 uniform variable. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml +func Uniform4f(dst Uniform, v0, v1, v2, v3 float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform4f, + a0: dst.c(), + a1: C.uintptr_t(math.Float32bits(v0)), + a2: C.uintptr_t(math.Float32bits(v1)), + a3: C.uintptr_t(math.Float32bits(v2)), + a4: C.uintptr_t(math.Float32bits(v3)), + }, + }) +} + +// Uniform4fv writes a vec4 uniform array of len(src)/4 elements. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml +func Uniform4fv(dst Uniform, src []float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform4fv, + a0: dst.c(), + a1: C.uintptr_t(len(src) / 4), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +// Uniform4i writes an ivec4 uniform variable. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml +func Uniform4i(dst Uniform, v0, v1, v2, v3 int32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform4i, + a0: dst.c(), + a1: C.uintptr_t(v0), + a2: C.uintptr_t(v1), + a3: C.uintptr_t(v2), + a4: C.uintptr_t(v3), + }, + }) +} + +// Uniform4i writes an ivec4 uniform array of len(src)/4 elements. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml +func Uniform4iv(dst Uniform, src []int32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform4iv, + a0: dst.c(), + a1: C.uintptr_t(len(src) / 4), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +// UniformMatrix2fv writes 2x2 matrices. Each matrix uses four +// float32 values, so the number of matrices written is len(src)/4. +// +// Each matrix must be supplied in column major order. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml +func UniformMatrix2fv(dst Uniform, src []float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniformMatrix2fv, + // OpenGL ES 2 does not support transpose. + a0: dst.c(), + a1: C.uintptr_t(len(src) / 4), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +// UniformMatrix3fv writes 3x3 matrices. Each matrix uses nine +// float32 values, so the number of matrices written is len(src)/9. +// +// Each matrix must be supplied in column major order. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml +func UniformMatrix3fv(dst Uniform, src []float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniformMatrix3fv, + a0: dst.c(), + a1: C.uintptr_t(len(src) / 9), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +// UniformMatrix4fv writes 4x4 matrices. Each matrix uses 16 +// float32 values, so the number of matrices written is len(src)/16. +// +// Each matrix must be supplied in column major order. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml +func UniformMatrix4fv(dst Uniform, src []float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniformMatrix4fv, + a0: dst.c(), + a1: C.uintptr_t(len(src) / 16), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +// UseProgram sets the active program. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glUseProgram.xhtml +func UseProgram(p Program) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUseProgram, + a0: p.c(), + }, + }) +} + +// ValidateProgram checks to see whether the executables contained in +// program can execute given the current OpenGL state. +// +// Typically only used for debugging. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glValidateProgram.xhtml +func ValidateProgram(p Program) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnValidateProgram, + a0: p.c(), + }, + }) +} + +// VertexAttrib1f writes a float vertex attribute. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glVertexAttrib.xhtml +func VertexAttrib1f(dst Attrib, x float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnVertexAttrib1f, + a0: dst.c(), + a1: C.uintptr_t(math.Float32bits(x)), + }, + }) +} + +// VertexAttrib1fv writes a float vertex attribute. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glVertexAttrib.xhtml +func VertexAttrib1fv(dst Attrib, src []float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnVertexAttrib1fv, + a0: dst.c(), + a1: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +// VertexAttrib2f writes a vec2 vertex attribute. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glVertexAttrib.xhtml +func VertexAttrib2f(dst Attrib, x, y float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnVertexAttrib2f, + a0: dst.c(), + a1: C.uintptr_t(math.Float32bits(x)), + a2: C.uintptr_t(math.Float32bits(y)), + }, + }) +} + +// VertexAttrib2fv writes a vec2 vertex attribute. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glVertexAttrib.xhtml +func VertexAttrib2fv(dst Attrib, src []float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnVertexAttrib2fv, + a0: dst.c(), + a1: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +// VertexAttrib3f writes a vec3 vertex attribute. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glVertexAttrib.xhtml +func VertexAttrib3f(dst Attrib, x, y, z float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnVertexAttrib3f, + a0: dst.c(), + a1: C.uintptr_t(math.Float32bits(x)), + a2: C.uintptr_t(math.Float32bits(y)), + a3: C.uintptr_t(math.Float32bits(z)), + }, + }) +} + +// VertexAttrib3fv writes a vec3 vertex attribute. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glVertexAttrib.xhtml +func VertexAttrib3fv(dst Attrib, src []float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnVertexAttrib3fv, + a0: dst.c(), + a1: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +// VertexAttrib4f writes a vec4 vertex attribute. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glVertexAttrib.xhtml +func VertexAttrib4f(dst Attrib, x, y, z, w float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnVertexAttrib4f, + a0: dst.c(), + a1: C.uintptr_t(math.Float32bits(x)), + a2: C.uintptr_t(math.Float32bits(y)), + a3: C.uintptr_t(math.Float32bits(z)), + a4: C.uintptr_t(math.Float32bits(w)), + }, + }) +} + +// VertexAttrib4fv writes a vec4 vertex attribute. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glVertexAttrib.xhtml +func VertexAttrib4fv(dst Attrib, src []float32) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnVertexAttrib4fv, + a0: dst.c(), + a1: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +// VertexAttribPointer uses a bound buffer to define vertex attribute data. +// +// Direct use of VertexAttribPointer to load data into OpenGL is not +// supported via the Go bindings. Instead, use BindBuffer with an +// ARRAY_BUFFER and then fill it using BufferData. +// +// The size argument specifies the number of components per attribute, +// between 1-4. The stride argument specifies the byte offset between +// consecutive vertex attributes. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glVertexAttribPointer.xhtml +func VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnVertexAttribPointer, + a0: dst.c(), + a1: C.uintptr_t(size), + a2: ty.c(), + a3: glBoolean(normalized), + a4: C.uintptr_t(stride), + a5: C.uintptr_t(offset), + }, + }) +} + +// Viewport sets the viewport, an affine transformation that +// normalizes device coordinates to window coordinates. +// +// http://www.khronos.org/opengles/sdk/docs/man3/html/glViewport.xhtml +func Viewport(x, y, width, height int) { + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnViewport, + a0: C.uintptr_t(x), + a1: C.uintptr_t(y), + a2: C.uintptr_t(width), + a3: C.uintptr_t(height), + }, + }) +} diff --git a/src/golang.org/x/mobile/gl/gldebug.go b/src/golang.org/x/mobile/gl/gldebug.go new file mode 100644 index 0000000000..63f64c80fa --- /dev/null +++ b/src/golang.org/x/mobile/gl/gldebug.go @@ -0,0 +1,2846 @@ +// Copyright 2014 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. + +// Generated from gl.go using go generate. DO NOT EDIT. +// See doc.go for details. + +// +build linux darwin +// +build gldebug + +package gl + +// #include "work.h" +import "C" + +import ( + "fmt" + "log" + "math" + "unsafe" +) + +func errDrain() string { + var errs []Enum + for { + e := GetError() + if e == 0 { + break + } + errs = append(errs, e) + } + if len(errs) > 0 { + return fmt.Sprintf(" error: %v", errs) + } + return "" +} + +func (v Enum) String() string { + switch v { + case 0x0: + return "0" + case 0x1: + return "1" + case 0x2: + return "LINE_LOOP" + case 0x3: + return "LINE_STRIP" + case 0x4: + return "TRIANGLES" + case 0x5: + return "TRIANGLE_STRIP" + case 0x6: + return "TRIANGLE_FAN" + case 0x300: + return "SRC_COLOR" + case 0x301: + return "ONE_MINUS_SRC_COLOR" + case 0x302: + return "SRC_ALPHA" + case 0x303: + return "ONE_MINUS_SRC_ALPHA" + case 0x304: + return "DST_ALPHA" + case 0x305: + return "ONE_MINUS_DST_ALPHA" + case 0x306: + return "DST_COLOR" + case 0x307: + return "ONE_MINUS_DST_COLOR" + case 0x308: + return "SRC_ALPHA_SATURATE" + case 0x8006: + return "FUNC_ADD" + case 0x8009: + return "32777" + case 0x883d: + return "BLEND_EQUATION_ALPHA" + case 0x800a: + return "FUNC_SUBTRACT" + case 0x800b: + return "FUNC_REVERSE_SUBTRACT" + case 0x80c8: + return "BLEND_DST_RGB" + case 0x80c9: + return "BLEND_SRC_RGB" + case 0x80ca: + return "BLEND_DST_ALPHA" + case 0x80cb: + return "BLEND_SRC_ALPHA" + case 0x8001: + return "CONSTANT_COLOR" + case 0x8002: + return "ONE_MINUS_CONSTANT_COLOR" + case 0x8003: + return "CONSTANT_ALPHA" + case 0x8004: + return "ONE_MINUS_CONSTANT_ALPHA" + case 0x8005: + return "BLEND_COLOR" + case 0x8892: + return "ARRAY_BUFFER" + case 0x8893: + return "ELEMENT_ARRAY_BUFFER" + case 0x8894: + return "ARRAY_BUFFER_BINDING" + case 0x8895: + return "ELEMENT_ARRAY_BUFFER_BINDING" + case 0x88e0: + return "STREAM_DRAW" + case 0x88e4: + return "STATIC_DRAW" + case 0x88e8: + return "DYNAMIC_DRAW" + case 0x8764: + return "BUFFER_SIZE" + case 0x8765: + return "BUFFER_USAGE" + case 0x8626: + return "CURRENT_VERTEX_ATTRIB" + case 0x404: + return "FRONT" + case 0x405: + return "BACK" + case 0x408: + return "FRONT_AND_BACK" + case 0xde1: + return "TEXTURE_2D" + case 0xb44: + return "CULL_FACE" + case 0xbe2: + return "BLEND" + case 0xbd0: + return "DITHER" + case 0xb90: + return "STENCIL_TEST" + case 0xb71: + return "DEPTH_TEST" + case 0xc11: + return "SCISSOR_TEST" + case 0x8037: + return "POLYGON_OFFSET_FILL" + case 0x809e: + return "SAMPLE_ALPHA_TO_COVERAGE" + case 0x80a0: + return "SAMPLE_COVERAGE" + case 0x500: + return "INVALID_ENUM" + case 0x501: + return "INVALID_VALUE" + case 0x502: + return "INVALID_OPERATION" + case 0x505: + return "OUT_OF_MEMORY" + case 0x900: + return "CW" + case 0x901: + return "CCW" + case 0xb21: + return "LINE_WIDTH" + case 0x846d: + return "ALIASED_POINT_SIZE_RANGE" + case 0x846e: + return "ALIASED_LINE_WIDTH_RANGE" + case 0xb45: + return "CULL_FACE_MODE" + case 0xb46: + return "FRONT_FACE" + case 0xb70: + return "DEPTH_RANGE" + case 0xb72: + return "DEPTH_WRITEMASK" + case 0xb73: + return "DEPTH_CLEAR_VALUE" + case 0xb74: + return "DEPTH_FUNC" + case 0xb91: + return "STENCIL_CLEAR_VALUE" + case 0xb92: + return "STENCIL_FUNC" + case 0xb94: + return "STENCIL_FAIL" + case 0xb95: + return "STENCIL_PASS_DEPTH_FAIL" + case 0xb96: + return "STENCIL_PASS_DEPTH_PASS" + case 0xb97: + return "STENCIL_REF" + case 0xb93: + return "STENCIL_VALUE_MASK" + case 0xb98: + return "STENCIL_WRITEMASK" + case 0x8800: + return "STENCIL_BACK_FUNC" + case 0x8801: + return "STENCIL_BACK_FAIL" + case 0x8802: + return "STENCIL_BACK_PASS_DEPTH_FAIL" + case 0x8803: + return "STENCIL_BACK_PASS_DEPTH_PASS" + case 0x8ca3: + return "STENCIL_BACK_REF" + case 0x8ca4: + return "STENCIL_BACK_VALUE_MASK" + case 0x8ca5: + return "STENCIL_BACK_WRITEMASK" + case 0xba2: + return "VIEWPORT" + case 0xc10: + return "SCISSOR_BOX" + case 0xc22: + return "COLOR_CLEAR_VALUE" + case 0xc23: + return "COLOR_WRITEMASK" + case 0xcf5: + return "UNPACK_ALIGNMENT" + case 0xd05: + return "PACK_ALIGNMENT" + case 0xd33: + return "MAX_TEXTURE_SIZE" + case 0xd3a: + return "MAX_VIEWPORT_DIMS" + case 0xd50: + return "SUBPIXEL_BITS" + case 0xd52: + return "RED_BITS" + case 0xd53: + return "GREEN_BITS" + case 0xd54: + return "BLUE_BITS" + case 0xd55: + return "ALPHA_BITS" + case 0xd56: + return "DEPTH_BITS" + case 0xd57: + return "STENCIL_BITS" + case 0x2a00: + return "POLYGON_OFFSET_UNITS" + case 0x8038: + return "POLYGON_OFFSET_FACTOR" + case 0x8069: + return "TEXTURE_BINDING_2D" + case 0x80a8: + return "SAMPLE_BUFFERS" + case 0x80a9: + return "SAMPLES" + case 0x80aa: + return "SAMPLE_COVERAGE_VALUE" + case 0x80ab: + return "SAMPLE_COVERAGE_INVERT" + case 0x86a2: + return "NUM_COMPRESSED_TEXTURE_FORMATS" + case 0x86a3: + return "COMPRESSED_TEXTURE_FORMATS" + case 0x1100: + return "DONT_CARE" + case 0x1101: + return "FASTEST" + case 0x1102: + return "NICEST" + case 0x8192: + return "GENERATE_MIPMAP_HINT" + case 0x1400: + return "BYTE" + case 0x1401: + return "UNSIGNED_BYTE" + case 0x1402: + return "SHORT" + case 0x1403: + return "UNSIGNED_SHORT" + case 0x1404: + return "INT" + case 0x1405: + return "UNSIGNED_INT" + case 0x1406: + return "FLOAT" + case 0x140c: + return "FIXED" + case 0x1902: + return "DEPTH_COMPONENT" + case 0x1906: + return "ALPHA" + case 0x1907: + return "RGB" + case 0x1908: + return "RGBA" + case 0x1909: + return "LUMINANCE" + case 0x190a: + return "LUMINANCE_ALPHA" + case 0x8033: + return "UNSIGNED_SHORT_4_4_4_4" + case 0x8034: + return "UNSIGNED_SHORT_5_5_5_1" + case 0x8363: + return "UNSIGNED_SHORT_5_6_5" + case 0x8869: + return "MAX_VERTEX_ATTRIBS" + case 0x8dfb: + return "MAX_VERTEX_UNIFORM_VECTORS" + case 0x8dfc: + return "MAX_VARYING_VECTORS" + case 0x8b4d: + return "MAX_COMBINED_TEXTURE_IMAGE_UNITS" + case 0x8b4c: + return "MAX_VERTEX_TEXTURE_IMAGE_UNITS" + case 0x8872: + return "MAX_TEXTURE_IMAGE_UNITS" + case 0x8dfd: + return "MAX_FRAGMENT_UNIFORM_VECTORS" + case 0x8b4f: + return "SHADER_TYPE" + case 0x8b80: + return "DELETE_STATUS" + case 0x8b82: + return "LINK_STATUS" + case 0x8b83: + return "VALIDATE_STATUS" + case 0x8b85: + return "ATTACHED_SHADERS" + case 0x8b86: + return "ACTIVE_UNIFORMS" + case 0x8b87: + return "ACTIVE_UNIFORM_MAX_LENGTH" + case 0x8b89: + return "ACTIVE_ATTRIBUTES" + case 0x8b8a: + return "ACTIVE_ATTRIBUTE_MAX_LENGTH" + case 0x8b8c: + return "SHADING_LANGUAGE_VERSION" + case 0x8b8d: + return "CURRENT_PROGRAM" + case 0x200: + return "NEVER" + case 0x201: + return "LESS" + case 0x202: + return "EQUAL" + case 0x203: + return "LEQUAL" + case 0x204: + return "GREATER" + case 0x205: + return "NOTEQUAL" + case 0x206: + return "GEQUAL" + case 0x207: + return "ALWAYS" + case 0x1e00: + return "KEEP" + case 0x1e01: + return "REPLACE" + case 0x1e02: + return "INCR" + case 0x1e03: + return "DECR" + case 0x150a: + return "INVERT" + case 0x8507: + return "INCR_WRAP" + case 0x8508: + return "DECR_WRAP" + case 0x1f00: + return "VENDOR" + case 0x1f01: + return "RENDERER" + case 0x1f02: + return "VERSION" + case 0x1f03: + return "EXTENSIONS" + case 0x2600: + return "NEAREST" + case 0x2601: + return "LINEAR" + case 0x2700: + return "NEAREST_MIPMAP_NEAREST" + case 0x2701: + return "LINEAR_MIPMAP_NEAREST" + case 0x2702: + return "NEAREST_MIPMAP_LINEAR" + case 0x2703: + return "LINEAR_MIPMAP_LINEAR" + case 0x2800: + return "TEXTURE_MAG_FILTER" + case 0x2801: + return "TEXTURE_MIN_FILTER" + case 0x2802: + return "TEXTURE_WRAP_S" + case 0x2803: + return "TEXTURE_WRAP_T" + case 0x1702: + return "TEXTURE" + case 0x8513: + return "TEXTURE_CUBE_MAP" + case 0x8514: + return "TEXTURE_BINDING_CUBE_MAP" + case 0x8515: + return "TEXTURE_CUBE_MAP_POSITIVE_X" + case 0x8516: + return "TEXTURE_CUBE_MAP_NEGATIVE_X" + case 0x8517: + return "TEXTURE_CUBE_MAP_POSITIVE_Y" + case 0x8518: + return "TEXTURE_CUBE_MAP_NEGATIVE_Y" + case 0x8519: + return "TEXTURE_CUBE_MAP_POSITIVE_Z" + case 0x851a: + return "TEXTURE_CUBE_MAP_NEGATIVE_Z" + case 0x851c: + return "MAX_CUBE_MAP_TEXTURE_SIZE" + case 0x84c0: + return "TEXTURE0" + case 0x84c1: + return "TEXTURE1" + case 0x84c2: + return "TEXTURE2" + case 0x84c3: + return "TEXTURE3" + case 0x84c4: + return "TEXTURE4" + case 0x84c5: + return "TEXTURE5" + case 0x84c6: + return "TEXTURE6" + case 0x84c7: + return "TEXTURE7" + case 0x84c8: + return "TEXTURE8" + case 0x84c9: + return "TEXTURE9" + case 0x84ca: + return "TEXTURE10" + case 0x84cb: + return "TEXTURE11" + case 0x84cc: + return "TEXTURE12" + case 0x84cd: + return "TEXTURE13" + case 0x84ce: + return "TEXTURE14" + case 0x84cf: + return "TEXTURE15" + case 0x84d0: + return "TEXTURE16" + case 0x84d1: + return "TEXTURE17" + case 0x84d2: + return "TEXTURE18" + case 0x84d3: + return "TEXTURE19" + case 0x84d4: + return "TEXTURE20" + case 0x84d5: + return "TEXTURE21" + case 0x84d6: + return "TEXTURE22" + case 0x84d7: + return "TEXTURE23" + case 0x84d8: + return "TEXTURE24" + case 0x84d9: + return "TEXTURE25" + case 0x84da: + return "TEXTURE26" + case 0x84db: + return "TEXTURE27" + case 0x84dc: + return "TEXTURE28" + case 0x84dd: + return "TEXTURE29" + case 0x84de: + return "TEXTURE30" + case 0x84df: + return "TEXTURE31" + case 0x84e0: + return "ACTIVE_TEXTURE" + case 0x2901: + return "REPEAT" + case 0x812f: + return "CLAMP_TO_EDGE" + case 0x8370: + return "MIRRORED_REPEAT" + case 0x8622: + return "VERTEX_ATTRIB_ARRAY_ENABLED" + case 0x8623: + return "VERTEX_ATTRIB_ARRAY_SIZE" + case 0x8624: + return "VERTEX_ATTRIB_ARRAY_STRIDE" + case 0x8625: + return "VERTEX_ATTRIB_ARRAY_TYPE" + case 0x886a: + return "VERTEX_ATTRIB_ARRAY_NORMALIZED" + case 0x8645: + return "VERTEX_ATTRIB_ARRAY_POINTER" + case 0x889f: + return "VERTEX_ATTRIB_ARRAY_BUFFER_BINDING" + case 0x8b9a: + return "IMPLEMENTATION_COLOR_READ_TYPE" + case 0x8b9b: + return "IMPLEMENTATION_COLOR_READ_FORMAT" + case 0x8b81: + return "COMPILE_STATUS" + case 0x8b84: + return "INFO_LOG_LENGTH" + case 0x8b88: + return "SHADER_SOURCE_LENGTH" + case 0x8dfa: + return "SHADER_COMPILER" + case 0x8df8: + return "SHADER_BINARY_FORMATS" + case 0x8df9: + return "NUM_SHADER_BINARY_FORMATS" + case 0x8df0: + return "LOW_FLOAT" + case 0x8df1: + return "MEDIUM_FLOAT" + case 0x8df2: + return "HIGH_FLOAT" + case 0x8df3: + return "LOW_INT" + case 0x8df4: + return "MEDIUM_INT" + case 0x8df5: + return "HIGH_INT" + case 0x8d40: + return "FRAMEBUFFER" + case 0x8d41: + return "RENDERBUFFER" + case 0x8056: + return "RGBA4" + case 0x8057: + return "RGB5_A1" + case 0x8d62: + return "RGB565" + case 0x81a5: + return "DEPTH_COMPONENT16" + case 0x8d48: + return "STENCIL_INDEX8" + case 0x8d42: + return "RENDERBUFFER_WIDTH" + case 0x8d43: + return "RENDERBUFFER_HEIGHT" + case 0x8d44: + return "RENDERBUFFER_INTERNAL_FORMAT" + case 0x8d50: + return "RENDERBUFFER_RED_SIZE" + case 0x8d51: + return "RENDERBUFFER_GREEN_SIZE" + case 0x8d52: + return "RENDERBUFFER_BLUE_SIZE" + case 0x8d53: + return "RENDERBUFFER_ALPHA_SIZE" + case 0x8d54: + return "RENDERBUFFER_DEPTH_SIZE" + case 0x8d55: + return "RENDERBUFFER_STENCIL_SIZE" + case 0x8cd0: + return "FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE" + case 0x8cd1: + return "FRAMEBUFFER_ATTACHMENT_OBJECT_NAME" + case 0x8cd2: + return "FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL" + case 0x8cd3: + return "FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE" + case 0x8ce0: + return "COLOR_ATTACHMENT0" + case 0x8d00: + return "DEPTH_ATTACHMENT" + case 0x8d20: + return "STENCIL_ATTACHMENT" + case 0x8cd5: + return "FRAMEBUFFER_COMPLETE" + case 0x8cd6: + return "FRAMEBUFFER_INCOMPLETE_ATTACHMENT" + case 0x8cd7: + return "FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT" + case 0x8cd9: + return "FRAMEBUFFER_INCOMPLETE_DIMENSIONS" + case 0x8cdd: + return "FRAMEBUFFER_UNSUPPORTED" + case 0x8ca6: + return "FRAMEBUFFER_BINDING" + case 0x8ca7: + return "RENDERBUFFER_BINDING" + case 0x84e8: + return "MAX_RENDERBUFFER_SIZE" + case 0x506: + return "INVALID_FRAMEBUFFER_OPERATION" + case 0x100: + return "DEPTH_BUFFER_BIT" + case 0x400: + return "STENCIL_BUFFER_BIT" + case 0x4000: + return "COLOR_BUFFER_BIT" + case 0x8b50: + return "FLOAT_VEC2" + case 0x8b51: + return "FLOAT_VEC3" + case 0x8b52: + return "FLOAT_VEC4" + case 0x8b53: + return "INT_VEC2" + case 0x8b54: + return "INT_VEC3" + case 0x8b55: + return "INT_VEC4" + case 0x8b56: + return "BOOL" + case 0x8b57: + return "BOOL_VEC2" + case 0x8b58: + return "BOOL_VEC3" + case 0x8b59: + return "BOOL_VEC4" + case 0x8b5a: + return "FLOAT_MAT2" + case 0x8b5b: + return "FLOAT_MAT3" + case 0x8b5c: + return "FLOAT_MAT4" + case 0x8b5e: + return "SAMPLER_2D" + case 0x8b60: + return "SAMPLER_CUBE" + case 0x8b30: + return "FRAGMENT_SHADER" + case 0x8b31: + return "VERTEX_SHADER" + default: + return fmt.Sprintf("gl.Enum(0x%x)", uint32(v)) + } +} + +func ActiveTexture(texture Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.ActiveTexture(%v) %v", texture, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnActiveTexture, + a0: texture.c(), + }, + }) +} + +func AttachShader(p Program, s Shader) { + defer func() { + errstr := errDrain() + log.Printf("gl.AttachShader(%v, %v) %v", p, s, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnAttachShader, + a0: p.c(), + a1: s.c(), + }, + }) +} + +func BindAttribLocation(p Program, a Attrib, name string) { + defer func() { + errstr := errDrain() + log.Printf("gl.BindAttribLocation(%v, %v, %v) %v", p, a, name, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBindAttribLocation, + a0: p.c(), + a1: a.c(), + a2: C.uintptr_t(uintptr(unsafe.Pointer(C.CString(name)))), + }, + }) +} + +func BindBuffer(target Enum, b Buffer) { + defer func() { + errstr := errDrain() + log.Printf("gl.BindBuffer(%v, %v) %v", target, b, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBindBuffer, + a0: target.c(), + a1: b.c(), + }, + }) +} + +func BindFramebuffer(target Enum, fb Framebuffer) { + defer func() { + errstr := errDrain() + log.Printf("gl.BindFramebuffer(%v, %v) %v", target, fb, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBindFramebuffer, + a0: target.c(), + a1: fb.c(), + }, + }) +} + +func BindRenderbuffer(target Enum, rb Renderbuffer) { + defer func() { + errstr := errDrain() + log.Printf("gl.BindRenderbuffer(%v, %v) %v", target, rb, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBindRenderbuffer, + a0: target.c(), + a1: rb.c(), + }, + }) +} + +func BindTexture(target Enum, t Texture) { + defer func() { + errstr := errDrain() + log.Printf("gl.BindTexture(%v, %v) %v", target, t, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBindTexture, + a0: target.c(), + a1: t.c(), + }, + }) +} + +func BlendColor(red, green, blue, alpha float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.BlendColor(%v, %v, %v, %v) %v", red, green, blue, alpha, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBlendColor, + a0: C.uintptr_t(math.Float32bits(red)), + a1: C.uintptr_t(math.Float32bits(green)), + a2: C.uintptr_t(math.Float32bits(blue)), + a3: C.uintptr_t(math.Float32bits(alpha)), + }, + }) +} + +func BlendEquation(mode Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.BlendEquation(%v) %v", mode, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBlendEquation, + a0: mode.c(), + }, + }) +} + +func BlendEquationSeparate(modeRGB, modeAlpha Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.BlendEquationSeparate(%v, %v) %v", modeRGB, modeAlpha, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBlendEquationSeparate, + a0: modeRGB.c(), + a1: modeAlpha.c(), + }, + }) +} + +func BlendFunc(sfactor, dfactor Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.BlendFunc(%v, %v) %v", sfactor, dfactor, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBlendFunc, + a0: sfactor.c(), + a1: dfactor.c(), + }, + }) +} + +func BlendFuncSeparate(sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.BlendFuncSeparate(%v, %v, %v, %v) %v", sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBlendFuncSeparate, + a0: sfactorRGB.c(), + a1: dfactorRGB.c(), + a2: sfactorAlpha.c(), + a3: dfactorAlpha.c(), + }, + }) +} + +func BufferData(target Enum, src []byte, usage Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.BufferData(%v, len(%d), %v) %v", target, len(src), usage, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBufferData, + a0: target.c(), + a1: C.uintptr_t(len(src)), + a2: (C.uintptr_t)(uintptr(unsafe.Pointer(&src[0]))), + a3: usage.c(), + }, + blocking: true, + }) +} + +func BufferInit(target Enum, size int, usage Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.BufferInit(%v, %v, %v) %v", target, size, usage, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBufferData, + a0: target.c(), + a1: C.uintptr_t(size), + a2: 0, + a3: usage.c(), + }, + }) +} + +func BufferSubData(target Enum, offset int, data []byte) { + defer func() { + errstr := errDrain() + log.Printf("gl.BufferSubData(%v, %v, len(%d)) %v", target, offset, len(data), errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnBufferSubData, + a0: target.c(), + a1: C.uintptr_t(offset), + a2: C.uintptr_t(len(data)), + a3: (C.uintptr_t)(uintptr(unsafe.Pointer(&data[0]))), + }, + blocking: true, + }) +} + +func CheckFramebufferStatus(target Enum) (r0 Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.CheckFramebufferStatus(%v) %v%v", target, r0, errstr) + }() + return Enum(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnCheckFramebufferStatus, + a0: target.c(), + }, + blocking: true, + })) +} + +func Clear(mask Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.Clear(%v) %v", mask, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnClear, + a0: C.uintptr_t(mask), + }, + }) +} + +func ClearColor(red, green, blue, alpha float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.ClearColor(%v, %v, %v, %v) %v", red, green, blue, alpha, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnClearColor, + a0: C.uintptr_t(math.Float32bits(red)), + a1: C.uintptr_t(math.Float32bits(green)), + a2: C.uintptr_t(math.Float32bits(blue)), + a3: C.uintptr_t(math.Float32bits(alpha)), + }, + }) +} + +func ClearDepthf(d float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.ClearDepthf(%v) %v", d, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnClearDepthf, + a0: C.uintptr_t(math.Float32bits(d)), + }, + }) +} + +func ClearStencil(s int) { + defer func() { + errstr := errDrain() + log.Printf("gl.ClearStencil(%v) %v", s, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnClearStencil, + a0: C.uintptr_t(s), + }, + }) +} + +func ColorMask(red, green, blue, alpha bool) { + defer func() { + errstr := errDrain() + log.Printf("gl.ColorMask(%v, %v, %v, %v) %v", red, green, blue, alpha, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnColorMask, + a0: glBoolean(red), + a1: glBoolean(green), + a2: glBoolean(blue), + a3: glBoolean(alpha), + }, + }) +} + +func CompileShader(s Shader) { + defer func() { + errstr := errDrain() + log.Printf("gl.CompileShader(%v) %v", s, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnCompileShader, + a0: s.c(), + }, + }) +} + +func CompressedTexImage2D(target Enum, level int, internalformat Enum, width, height, border int, data []byte) { + defer func() { + errstr := errDrain() + log.Printf("gl.CompressedTexImage2D(%v, %v, %v, %v, %v, %v, len(%d)) %v", target, level, internalformat, width, height, border, len(data), errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnCompressedTexImage2D, + a0: target.c(), + a1: C.uintptr_t(level), + a2: internalformat.c(), + a3: C.uintptr_t(width), + a4: C.uintptr_t(height), + a5: C.uintptr_t(border), + a6: C.uintptr_t(len(data)), + a7: C.uintptr_t(uintptr(unsafe.Pointer(&data[0]))), + }, + blocking: true, + }) +} + +func CompressedTexSubImage2D(target Enum, level, xoffset, yoffset, width, height int, format Enum, data []byte) { + defer func() { + errstr := errDrain() + log.Printf("gl.CompressedTexSubImage2D(%v, %v, %v, %v, %v, %v, %v, len(%d)) %v", target, level, xoffset, yoffset, width, height, format, len(data), errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnCompressedTexSubImage2D, + a0: target.c(), + a1: C.uintptr_t(level), + a2: C.uintptr_t(xoffset), + a3: C.uintptr_t(yoffset), + a4: C.uintptr_t(width), + a5: C.uintptr_t(height), + a6: format.c(), + a7: C.uintptr_t(len(data)), + a8: C.uintptr_t(uintptr(unsafe.Pointer(&data[0]))), + }, + blocking: true, + }) +} + +func CopyTexImage2D(target Enum, level int, internalformat Enum, x, y, width, height, border int) { + defer func() { + errstr := errDrain() + log.Printf("gl.CopyTexImage2D(%v, %v, %v, %v, %v, %v, %v, %v) %v", target, level, internalformat, x, y, width, height, border, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnCopyTexImage2D, + a0: target.c(), + a1: C.uintptr_t(level), + a2: internalformat.c(), + a3: C.uintptr_t(x), + a4: C.uintptr_t(y), + a5: C.uintptr_t(width), + a6: C.uintptr_t(height), + a7: C.uintptr_t(border), + }, + }) +} + +func CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) { + defer func() { + errstr := errDrain() + log.Printf("gl.CopyTexSubImage2D(%v, %v, %v, %v, %v, %v, %v, %v) %v", target, level, xoffset, yoffset, x, y, width, height, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnCopyTexSubImage2D, + a0: target.c(), + a1: C.uintptr_t(level), + a2: C.uintptr_t(xoffset), + a3: C.uintptr_t(yoffset), + a4: C.uintptr_t(x), + a5: C.uintptr_t(y), + a6: C.uintptr_t(width), + a7: C.uintptr_t(height), + }, + }) +} + +func CreateBuffer() (r0 Buffer) { + defer func() { + errstr := errDrain() + log.Printf("gl.CreateBuffer() %v%v", r0, errstr) + }() + return Buffer{Value: uint32(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGenBuffer, + }, + blocking: true, + }))} +} + +func CreateFramebuffer() (r0 Framebuffer) { + defer func() { + errstr := errDrain() + log.Printf("gl.CreateFramebuffer() %v%v", r0, errstr) + }() + return Framebuffer{Value: uint32(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGenFramebuffer, + }, + blocking: true, + }))} +} + +func CreateProgram() (r0 Program) { + defer func() { + errstr := errDrain() + log.Printf("gl.CreateProgram() %v%v", r0, errstr) + }() + return Program{Value: uint32(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnCreateProgram, + }, + blocking: true, + }))} +} + +func CreateRenderbuffer() (r0 Renderbuffer) { + defer func() { + errstr := errDrain() + log.Printf("gl.CreateRenderbuffer() %v%v", r0, errstr) + }() + return Renderbuffer{Value: uint32(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGenRenderbuffer, + }, + blocking: true, + }))} +} + +func CreateShader(ty Enum) (r0 Shader) { + defer func() { + errstr := errDrain() + log.Printf("gl.CreateShader(%v) %v%v", ty, r0, errstr) + }() + return Shader{Value: uint32(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnCreateShader, + a0: C.uintptr_t(ty), + }, + blocking: true, + }))} +} + +func CreateTexture() (r0 Texture) { + defer func() { + errstr := errDrain() + log.Printf("gl.CreateTexture() %v%v", r0, errstr) + }() + return Texture{Value: uint32(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGenTexture, + }, + blocking: true, + }))} +} + +func CullFace(mode Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.CullFace(%v) %v", mode, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnCullFace, + a0: mode.c(), + }, + }) +} + +func DeleteBuffer(v Buffer) { + defer func() { + errstr := errDrain() + log.Printf("gl.DeleteBuffer(%v) %v", v, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDeleteBuffer, + a0: C.uintptr_t(v.Value), + }, + }) +} + +func DeleteFramebuffer(v Framebuffer) { + defer func() { + errstr := errDrain() + log.Printf("gl.DeleteFramebuffer(%v) %v", v, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDeleteFramebuffer, + a0: C.uintptr_t(v.Value), + }, + }) +} + +func DeleteProgram(p Program) { + defer func() { + errstr := errDrain() + log.Printf("gl.DeleteProgram(%v) %v", p, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDeleteProgram, + a0: p.c(), + }, + }) +} + +func DeleteRenderbuffer(v Renderbuffer) { + defer func() { + errstr := errDrain() + log.Printf("gl.DeleteRenderbuffer(%v) %v", v, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDeleteRenderbuffer, + a0: v.c(), + }, + }) +} + +func DeleteShader(s Shader) { + defer func() { + errstr := errDrain() + log.Printf("gl.DeleteShader(%v) %v", s, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDeleteShader, + a0: s.c(), + }, + }) +} + +func DeleteTexture(v Texture) { + defer func() { + errstr := errDrain() + log.Printf("gl.DeleteTexture(%v) %v", v, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDeleteTexture, + a0: v.c(), + }, + }) +} + +func DepthFunc(fn Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.DepthFunc(%v) %v", fn, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDepthFunc, + a0: fn.c(), + }, + }) +} + +func DepthMask(flag bool) { + defer func() { + errstr := errDrain() + log.Printf("gl.DepthMask(%v) %v", flag, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDepthMask, + a0: glBoolean(flag), + }, + }) +} + +func DepthRangef(n, f float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.DepthRangef(%v, %v) %v", n, f, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDepthRangef, + a0: C.uintptr_t(math.Float32bits(n)), + a1: C.uintptr_t(math.Float32bits(f)), + }, + }) +} + +func DetachShader(p Program, s Shader) { + defer func() { + errstr := errDrain() + log.Printf("gl.DetachShader(%v, %v) %v", p, s, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDetachShader, + a0: p.c(), + a1: s.c(), + }, + }) +} + +func Disable(cap Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.Disable(%v) %v", cap, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDisable, + a0: cap.c(), + }, + }) +} + +func DisableVertexAttribArray(a Attrib) { + defer func() { + errstr := errDrain() + log.Printf("gl.DisableVertexAttribArray(%v) %v", a, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDisableVertexAttribArray, + a0: a.c(), + }, + }) +} + +func DrawArrays(mode Enum, first, count int) { + defer func() { + errstr := errDrain() + log.Printf("gl.DrawArrays(%v, %v, %v) %v", mode, first, count, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDrawArrays, + a0: mode.c(), + a1: C.uintptr_t(first), + a2: C.uintptr_t(count), + }, + }) +} + +func DrawElements(mode Enum, count int, ty Enum, offset int) { + defer func() { + errstr := errDrain() + log.Printf("gl.DrawElements(%v, %v, %v, %v) %v", mode, count, ty, offset, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnDrawElements, + a0: mode.c(), + a1: C.uintptr_t(count), + a2: ty.c(), + a3: C.uintptr_t(offset), + }, + }) +} + +func Enable(cap Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.Enable(%v) %v", cap, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnEnable, + a0: cap.c(), + }, + }) +} + +func EnableVertexAttribArray(a Attrib) { + defer func() { + errstr := errDrain() + log.Printf("gl.EnableVertexAttribArray(%v) %v", a, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnEnableVertexAttribArray, + a0: a.c(), + }, + }) +} + +func Finish() { + defer func() { + errstr := errDrain() + log.Printf("gl.Finish() %v", errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnFinish, + }, + blocking: true, + }) +} + +func Flush() { + defer func() { + errstr := errDrain() + log.Printf("gl.Flush() %v", errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnFlush, + }, + blocking: true, + }) +} + +func FramebufferRenderbuffer(target, attachment, rbTarget Enum, rb Renderbuffer) { + defer func() { + errstr := errDrain() + log.Printf("gl.FramebufferRenderbuffer(%v, %v, %v, %v) %v", target, attachment, rbTarget, rb, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnFramebufferRenderbuffer, + a0: target.c(), + a1: attachment.c(), + a2: rbTarget.c(), + a3: rb.c(), + }, + }) +} + +func FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) { + defer func() { + errstr := errDrain() + log.Printf("gl.FramebufferTexture2D(%v, %v, %v, %v, %v) %v", target, attachment, texTarget, t, level, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnFramebufferTexture2D, + a0: target.c(), + a1: attachment.c(), + a2: texTarget.c(), + a3: t.c(), + a4: C.uintptr_t(level), + }, + }) +} + +func FrontFace(mode Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.FrontFace(%v) %v", mode, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnFrontFace, + a0: mode.c(), + }, + }) +} + +func GenerateMipmap(target Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.GenerateMipmap(%v) %v", target, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGenerateMipmap, + a0: target.c(), + }, + }) +} + +func GetActiveAttrib(p Program, index uint32) (name string, size int, ty Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetActiveAttrib(%v, %v) (%v, %v, %v) %v", p, index, name, size, ty, errstr) + }() + bufSize := GetProgrami(p, ACTIVE_ATTRIBUTE_MAX_LENGTH) + buf := C.malloc(C.size_t(bufSize)) + defer C.free(buf) + var cSize C.GLint + var cType C.GLenum + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetActiveAttrib, + a0: p.c(), + a1: C.uintptr_t(index), + a2: C.uintptr_t(bufSize), + a3: 0, + a4: C.uintptr_t(uintptr(unsafe.Pointer(&cSize))), + a5: C.uintptr_t(uintptr(unsafe.Pointer(&cType))), + a6: C.uintptr_t(uintptr(unsafe.Pointer(buf))), + }, + blocking: true, + }) + return C.GoString((*C.char)(buf)), int(cSize), Enum(cType) +} + +func GetActiveUniform(p Program, index uint32) (name string, size int, ty Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetActiveUniform(%v, %v) (%v, %v, %v) %v", p, index, name, size, ty, errstr) + }() + bufSize := GetProgrami(p, ACTIVE_UNIFORM_MAX_LENGTH) + buf := C.malloc(C.size_t(bufSize)) + defer C.free(buf) + var cSize C.GLint + var cType C.GLenum + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetActiveUniform, + a0: p.c(), + a1: C.uintptr_t(index), + a2: C.uintptr_t(bufSize), + a3: 0, + a4: C.uintptr_t(uintptr(unsafe.Pointer(&cSize))), + a5: C.uintptr_t(uintptr(unsafe.Pointer(&cType))), + a6: C.uintptr_t(uintptr(unsafe.Pointer(buf))), + }, + blocking: true, + }) + return C.GoString((*C.char)(buf)), int(cSize), Enum(cType) +} + +func GetAttachedShaders(p Program) (r0 []Shader) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetAttachedShaders(%v) %v%v", p, r0, errstr) + }() + shadersLen := GetProgrami(p, ATTACHED_SHADERS) + var n C.GLsizei + buf := make([]C.GLuint, shadersLen) + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetAttachedShaders, + a0: p.c(), + a1: C.uintptr_t(shadersLen), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&n))), + a3: C.uintptr_t(uintptr(unsafe.Pointer(&buf[0]))), + }, + blocking: true, + }) + buf = buf[:int(n)] + shaders := make([]Shader, len(buf)) + for i, s := range buf { + shaders[i] = Shader{Value: uint32(s)} + } + return shaders +} + +func GetAttribLocation(p Program, name string) (r0 Attrib) { + defer func() { + errstr := errDrain() + r0.name = name + log.Printf("gl.GetAttribLocation(%v, %v) %v%v", p, name, r0, errstr) + }() + return Attrib{Value: uint(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetAttribLocation, + a0: p.c(), + a1: C.uintptr_t(uintptr(unsafe.Pointer(C.CString(name)))), + }, + blocking: true, + }))} +} + +func GetBooleanv(dst []bool, pname Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetBooleanv(%v, %v) %v", dst, pname, errstr) + }() + buf := make([]C.GLboolean, len(dst)) + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetBooleanv, + a0: pname.c(), + a1: C.uintptr_t(uintptr(unsafe.Pointer(&buf[0]))), + }, + blocking: true, + }) + for i, v := range buf { + dst[i] = v != 0 + } +} + +func GetFloatv(dst []float32, pname Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetFloatv(len(%d), %v) %v", len(dst), pname, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetFloatv, + a0: pname.c(), + a1: C.uintptr_t(uintptr(unsafe.Pointer(&dst[0]))), + }, + blocking: true, + }) +} + +func GetIntegerv(dst []int32, pname Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetIntegerv(%v, %v) %v", dst, pname, errstr) + }() + buf := make([]C.GLint, len(dst)) + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetIntegerv, + a0: pname.c(), + a1: C.uintptr_t(uintptr(unsafe.Pointer(&buf[0]))), + }, + blocking: true, + }) + for i, v := range buf { + dst[i] = int32(v) + } +} + +func GetInteger(pname Enum) (r0 int) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetInteger(%v) %v%v", pname, r0, errstr) + }() + var v [1]int32 + GetIntegerv(v[:], pname) + return int(v[0]) +} + +func GetBufferParameteri(target, value Enum) (r0 int) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetBufferParameteri(%v, %v) %v%v", target, value, r0, errstr) + }() + return int(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetBufferParameteri, + a0: target.c(), + a1: value.c(), + }, + blocking: true, + })) +} + +func GetError() (r0 Enum) { + return Enum(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetError, + }, + blocking: true, + })) +} + +func GetFramebufferAttachmentParameteri(target, attachment, pname Enum) (r0 int) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetFramebufferAttachmentParameteri(%v, %v, %v) %v%v", target, attachment, pname, r0, errstr) + }() + return int(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetFramebufferAttachmentParameteriv, + a0: target.c(), + a1: attachment.c(), + a2: pname.c(), + }, + blocking: true, + })) +} + +func GetProgrami(p Program, pname Enum) (r0 int) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetProgrami(%v, %v) %v%v", p, pname, r0, errstr) + }() + return int(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetProgramiv, + a0: p.c(), + a1: pname.c(), + }, + blocking: true, + })) +} + +func GetProgramInfoLog(p Program) (r0 string) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetProgramInfoLog(%v) %v%v", p, r0, errstr) + }() + infoLen := GetProgrami(p, INFO_LOG_LENGTH) + buf := C.malloc(C.size_t(infoLen)) + defer C.free(buf) + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetProgramInfoLog, + a0: p.c(), + a1: C.uintptr_t(infoLen), + a2: 0, + a3: C.uintptr_t(uintptr(buf)), + }, + blocking: true, + }) + return C.GoString((*C.char)(buf)) +} + +func GetRenderbufferParameteri(target, pname Enum) (r0 int) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetRenderbufferParameteri(%v, %v) %v%v", target, pname, r0, errstr) + }() + return int(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetRenderbufferParameteriv, + a0: target.c(), + a1: pname.c(), + }, + blocking: true, + })) +} + +func GetShaderi(s Shader, pname Enum) (r0 int) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetShaderi(%v, %v) %v%v", s, pname, r0, errstr) + }() + return int(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetShaderiv, + a0: s.c(), + a1: pname.c(), + }, + blocking: true, + })) +} + +func GetShaderInfoLog(s Shader) (r0 string) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetShaderInfoLog(%v) %v%v", s, r0, errstr) + }() + infoLen := GetShaderi(s, INFO_LOG_LENGTH) + buf := C.malloc(C.size_t(infoLen)) + defer C.free(buf) + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetShaderInfoLog, + a0: s.c(), + a1: C.uintptr_t(infoLen), + a2: 0, + a3: C.uintptr_t(uintptr(buf)), + }, + blocking: true, + }) + return C.GoString((*C.char)(buf)) +} + +func GetShaderPrecisionFormat(shadertype, precisiontype Enum) (rangeLow, rangeHigh, precision int) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetShaderPrecisionFormat(%v, %v) (%v, %v, %v) %v", shadertype, precisiontype, rangeLow, rangeHigh, precision, errstr) + }() + var cRange [2]C.GLint + var cPrecision C.GLint + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetShaderPrecisionFormat, + a0: shadertype.c(), + a1: precisiontype.c(), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&cRange[0]))), + a3: C.uintptr_t(uintptr(unsafe.Pointer(&cPrecision))), + }, + blocking: true, + }) + return int(cRange[0]), int(cRange[1]), int(cPrecision) +} + +func GetShaderSource(s Shader) (r0 string) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetShaderSource(%v) %v%v", s, r0, errstr) + }() + sourceLen := GetShaderi(s, SHADER_SOURCE_LENGTH) + if sourceLen == 0 { + return "" + } + buf := C.malloc(C.size_t(sourceLen)) + defer C.free(buf) + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetShaderSource, + a0: s.c(), + a1: C.uintptr_t(sourceLen), + a2: 0, + a3: C.uintptr_t(uintptr(buf)), + }, + blocking: true, + }) + return C.GoString((*C.char)(buf)) +} + +func GetString(pname Enum) (r0 string) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetString(%v) %v%v", pname, r0, errstr) + }() + ret := enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetString, + a0: pname.c(), + }, + blocking: true, + }) + return C.GoString((*C.char)((unsafe.Pointer(uintptr(ret))))) +} + +func GetTexParameterfv(dst []float32, target, pname Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetTexParameterfv(len(%d), %v, %v) %v", len(dst), target, pname, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetTexParameterfv, + a0: target.c(), + a1: pname.c(), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&dst[0]))), + }, + blocking: true, + }) +} + +func GetTexParameteriv(dst []int32, target, pname Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetTexParameteriv(%v, %v, %v) %v", dst, target, pname, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetTexParameteriv, + a0: target.c(), + a1: pname.c(), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&dst[0]))), + }, + blocking: true, + }) +} + +func GetUniformfv(dst []float32, src Uniform, p Program) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetUniformfv(len(%d), %v, %v) %v", len(dst), src, p, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetUniformfv, + a0: p.c(), + a1: src.c(), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&dst[0]))), + }, + blocking: true, + }) +} + +func GetUniformiv(dst []int32, src Uniform, p Program) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetUniformiv(%v, %v, %v) %v", dst, src, p, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetUniformiv, + a0: p.c(), + a1: src.c(), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&dst[0]))), + }, + blocking: true, + }) +} + +func GetUniformLocation(p Program, name string) (r0 Uniform) { + defer func() { + errstr := errDrain() + r0.name = name + log.Printf("gl.GetUniformLocation(%v, %v) %v%v", p, name, r0, errstr) + }() + return Uniform{Value: int32(enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetUniformLocation, + a0: p.c(), + a1: C.uintptr_t(uintptr(unsafe.Pointer(C.CString(name)))), + }, + blocking: true, + }))} +} + +func GetVertexAttribf(src Attrib, pname Enum) (r0 float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetVertexAttribf(%v, %v) %v%v", src, pname, r0, errstr) + }() + var params [1]float32 + GetVertexAttribfv(params[:], src, pname) + return params[0] +} + +func GetVertexAttribfv(dst []float32, src Attrib, pname Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetVertexAttribfv(len(%d), %v, %v) %v", len(dst), src, pname, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetVertexAttribfv, + a0: src.c(), + a1: pname.c(), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&dst[0]))), + }, + blocking: true, + }) +} + +func GetVertexAttribi(src Attrib, pname Enum) (r0 int32) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetVertexAttribi(%v, %v) %v%v", src, pname, r0, errstr) + }() + var params [1]int32 + GetVertexAttribiv(params[:], src, pname) + return params[0] +} + +func GetVertexAttribiv(dst []int32, src Attrib, pname Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.GetVertexAttribiv(%v, %v, %v) %v", dst, src, pname, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnGetVertexAttribiv, + a0: src.c(), + a1: pname.c(), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&dst[0]))), + }, + blocking: true, + }) +} + +func Hint(target, mode Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.Hint(%v, %v) %v", target, mode, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnHint, + a0: target.c(), + a1: mode.c(), + }, + }) +} + +func IsBuffer(b Buffer) (r0 bool) { + defer func() { + errstr := errDrain() + log.Printf("gl.IsBuffer(%v) %v%v", b, r0, errstr) + }() + return 0 != enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnIsBuffer, + a0: b.c(), + }, + blocking: true, + }) +} + +func IsEnabled(cap Enum) (r0 bool) { + defer func() { + errstr := errDrain() + log.Printf("gl.IsEnabled(%v) %v%v", cap, r0, errstr) + }() + return 0 != enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnIsEnabled, + a0: cap.c(), + }, + blocking: true, + }) +} + +func IsFramebuffer(fb Framebuffer) (r0 bool) { + defer func() { + errstr := errDrain() + log.Printf("gl.IsFramebuffer(%v) %v%v", fb, r0, errstr) + }() + return 0 != enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnIsFramebuffer, + a0: fb.c(), + }, + blocking: true, + }) +} + +func IsProgram(p Program) (r0 bool) { + defer func() { + errstr := errDrain() + log.Printf("gl.IsProgram(%v) %v%v", p, r0, errstr) + }() + return 0 != enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnIsProgram, + a0: p.c(), + }, + blocking: true, + }) +} + +func IsRenderbuffer(rb Renderbuffer) (r0 bool) { + defer func() { + errstr := errDrain() + log.Printf("gl.IsRenderbuffer(%v) %v%v", rb, r0, errstr) + }() + return 0 != enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnIsRenderbuffer, + a0: rb.c(), + }, + blocking: true, + }) +} + +func IsShader(s Shader) (r0 bool) { + defer func() { + errstr := errDrain() + log.Printf("gl.IsShader(%v) %v%v", s, r0, errstr) + }() + return 0 != enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnIsShader, + a0: s.c(), + }, + blocking: true, + }) +} + +func IsTexture(t Texture) (r0 bool) { + defer func() { + errstr := errDrain() + log.Printf("gl.IsTexture(%v) %v%v", t, r0, errstr) + }() + return 0 != enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnIsTexture, + a0: t.c(), + }, + blocking: true, + }) +} + +func LineWidth(width float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.LineWidth(%v) %v", width, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnLineWidth, + a0: C.uintptr_t(math.Float32bits(width)), + }, + }) +} + +func LinkProgram(p Program) { + defer func() { + errstr := errDrain() + log.Printf("gl.LinkProgram(%v) %v", p, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnLinkProgram, + a0: p.c(), + }, + }) +} + +func PixelStorei(pname Enum, param int32) { + defer func() { + errstr := errDrain() + log.Printf("gl.PixelStorei(%v, %v) %v", pname, param, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnPixelStorei, + a0: pname.c(), + a1: C.uintptr_t(param), + }, + }) +} + +func PolygonOffset(factor, units float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.PolygonOffset(%v, %v) %v", factor, units, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnPolygonOffset, + a0: C.uintptr_t(math.Float32bits(factor)), + a1: C.uintptr_t(math.Float32bits(units)), + }, + }) +} + +func ReadPixels(dst []byte, x, y, width, height int, format, ty Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.ReadPixels(len(%d), %v, %v, %v, %v, %v, %v) %v", len(dst), x, y, width, height, format, ty, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnReadPixels, + + a0: C.uintptr_t(x), + a1: C.uintptr_t(y), + a2: C.uintptr_t(width), + a3: C.uintptr_t(height), + a4: format.c(), + a5: ty.c(), + a6: C.uintptr_t(uintptr(unsafe.Pointer(&dst[0]))), + }, + blocking: true, + }) +} + +func ReleaseShaderCompiler() { + defer func() { + errstr := errDrain() + log.Printf("gl.ReleaseShaderCompiler() %v", errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnReleaseShaderCompiler, + }, + }) +} + +func RenderbufferStorage(target, internalFormat Enum, width, height int) { + defer func() { + errstr := errDrain() + log.Printf("gl.RenderbufferStorage(%v, %v, %v, %v) %v", target, internalFormat, width, height, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnRenderbufferStorage, + a0: target.c(), + a1: internalFormat.c(), + a2: C.uintptr_t(width), + a3: C.uintptr_t(height), + }, + }) +} + +func SampleCoverage(value float32, invert bool) { + defer func() { + errstr := errDrain() + log.Printf("gl.SampleCoverage(%v, %v) %v", value, invert, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnSampleCoverage, + a0: C.uintptr_t(math.Float32bits(value)), + a1: glBoolean(invert), + }, + }) +} + +func Scissor(x, y, width, height int32) { + defer func() { + errstr := errDrain() + log.Printf("gl.Scissor(%v, %v, %v, %v) %v", x, y, width, height, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnScissor, + a0: C.uintptr_t(x), + a1: C.uintptr_t(y), + a2: C.uintptr_t(width), + a3: C.uintptr_t(height), + }, + }) +} + +func ShaderSource(s Shader, src string) { + defer func() { + errstr := errDrain() + log.Printf("gl.ShaderSource(%v, %v) %v", s, src, errstr) + }() + cstr := C.CString(src) + cstrp := (**C.char)(C.malloc(C.size_t(unsafe.Sizeof(cstr)))) + *cstrp = cstr + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnShaderSource, + a0: s.c(), + a1: 1, + a2: C.uintptr_t(uintptr(unsafe.Pointer(cstrp))), + }, + }) +} + +func StencilFunc(fn Enum, ref int, mask uint32) { + defer func() { + errstr := errDrain() + log.Printf("gl.StencilFunc(%v, %v, %v) %v", fn, ref, mask, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnStencilFunc, + a0: fn.c(), + a1: C.uintptr_t(ref), + a2: C.uintptr_t(mask), + }, + }) +} + +func StencilFuncSeparate(face, fn Enum, ref int, mask uint32) { + defer func() { + errstr := errDrain() + log.Printf("gl.StencilFuncSeparate(%v, %v, %v, %v) %v", face, fn, ref, mask, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnStencilFuncSeparate, + a0: face.c(), + a1: fn.c(), + a2: C.uintptr_t(ref), + a3: C.uintptr_t(mask), + }, + }) +} + +func StencilMask(mask uint32) { + defer func() { + errstr := errDrain() + log.Printf("gl.StencilMask(%v) %v", mask, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnStencilMask, + a0: C.uintptr_t(mask), + }, + }) +} + +func StencilMaskSeparate(face Enum, mask uint32) { + defer func() { + errstr := errDrain() + log.Printf("gl.StencilMaskSeparate(%v, %v) %v", face, mask, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnStencilMaskSeparate, + a0: face.c(), + a1: C.uintptr_t(mask), + }, + }) +} + +func StencilOp(fail, zfail, zpass Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.StencilOp(%v, %v, %v) %v", fail, zfail, zpass, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnStencilOp, + a0: fail.c(), + a1: zfail.c(), + a2: zpass.c(), + }, + }) +} + +func StencilOpSeparate(face, sfail, dpfail, dppass Enum) { + defer func() { + errstr := errDrain() + log.Printf("gl.StencilOpSeparate(%v, %v, %v, %v) %v", face, sfail, dpfail, dppass, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnStencilOpSeparate, + a0: face.c(), + a1: sfail.c(), + a2: dpfail.c(), + a3: dppass.c(), + }, + }) +} + +func TexImage2D(target Enum, level int, width, height int, format Enum, ty Enum, data []byte) { + defer func() { + errstr := errDrain() + log.Printf("gl.TexImage2D(%v, %v, %v, %v, %v, %v, len(%d)) %v", target, level, width, height, format, ty, len(data), errstr) + }() + blocking, a7 := false, C.uintptr_t(0) + if len(data) > 0 { + blocking, a7 = true, C.uintptr_t(uintptr(unsafe.Pointer(&data[0]))) + } + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnTexImage2D, + + a0: target.c(), + a1: C.uintptr_t(level), + a2: C.uintptr_t(format), + a3: C.uintptr_t(width), + a4: C.uintptr_t(height), + a5: format.c(), + a6: ty.c(), + a7: a7, + }, + blocking: blocking, + }) +} + +func TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) { + defer func() { + errstr := errDrain() + log.Printf("gl.TexSubImage2D(%v, %v, %v, %v, %v, %v, %v, %v, len(%d)) %v", target, level, x, y, width, height, format, ty, len(data), errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnTexSubImage2D, + + a0: target.c(), + a1: C.uintptr_t(level), + a2: C.uintptr_t(x), + a3: C.uintptr_t(y), + a4: C.uintptr_t(width), + a5: C.uintptr_t(height), + a6: format.c(), + a7: ty.c(), + a8: C.uintptr_t(uintptr(unsafe.Pointer(&data[0]))), + }, + blocking: true, + }) +} + +func TexParameterf(target, pname Enum, param float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.TexParameterf(%v, %v, %v) %v", target, pname, param, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnTexParameterf, + a0: target.c(), + a1: pname.c(), + a2: C.uintptr_t(math.Float32bits(param)), + }, + }) +} + +func TexParameterfv(target, pname Enum, params []float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.TexParameterfv(%v, %v, len(%d)) %v", target, pname, len(params), errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnTexParameterfv, + a0: target.c(), + a1: pname.c(), + a2: C.uintptr_t(uintptr(unsafe.Pointer(¶ms[0]))), + }, + blocking: true, + }) +} + +func TexParameteri(target, pname Enum, param int) { + defer func() { + errstr := errDrain() + log.Printf("gl.TexParameteri(%v, %v, %v) %v", target, pname, param, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnTexParameteri, + a0: target.c(), + a1: pname.c(), + a2: C.uintptr_t(param), + }, + }) +} + +func TexParameteriv(target, pname Enum, params []int32) { + defer func() { + errstr := errDrain() + log.Printf("gl.TexParameteriv(%v, %v, %v) %v", target, pname, params, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnTexParameteriv, + a0: target.c(), + a1: pname.c(), + a2: C.uintptr_t(uintptr(unsafe.Pointer(¶ms[0]))), + }, + blocking: true, + }) +} + +func Uniform1f(dst Uniform, v float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.Uniform1f(%v, %v) %v", dst, v, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform1f, + a0: dst.c(), + a1: C.uintptr_t(math.Float32bits(v)), + }, + }) +} + +func Uniform1fv(dst Uniform, src []float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.Uniform1fv(%v, len(%d)) %v", dst, len(src), errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform1fv, + a0: dst.c(), + a1: C.uintptr_t(len(src)), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +func Uniform1i(dst Uniform, v int) { + defer func() { + errstr := errDrain() + log.Printf("gl.Uniform1i(%v, %v) %v", dst, v, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform1i, + a0: dst.c(), + a1: C.uintptr_t(v), + }, + }) +} + +func Uniform1iv(dst Uniform, src []int32) { + defer func() { + errstr := errDrain() + log.Printf("gl.Uniform1iv(%v, %v) %v", dst, src, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform1iv, + a0: dst.c(), + a1: C.uintptr_t(len(src)), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +func Uniform2f(dst Uniform, v0, v1 float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.Uniform2f(%v, %v, %v) %v", dst, v0, v1, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform2f, + a0: dst.c(), + a1: C.uintptr_t(math.Float32bits(v0)), + a2: C.uintptr_t(math.Float32bits(v1)), + }, + }) +} + +func Uniform2fv(dst Uniform, src []float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.Uniform2fv(%v, len(%d)) %v", dst, len(src), errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform2fv, + a0: dst.c(), + a1: C.uintptr_t(len(src) / 2), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +func Uniform2i(dst Uniform, v0, v1 int) { + defer func() { + errstr := errDrain() + log.Printf("gl.Uniform2i(%v, %v, %v) %v", dst, v0, v1, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform2i, + a0: dst.c(), + a1: C.uintptr_t(v0), + a2: C.uintptr_t(v1), + }, + }) +} + +func Uniform2iv(dst Uniform, src []int32) { + defer func() { + errstr := errDrain() + log.Printf("gl.Uniform2iv(%v, %v) %v", dst, src, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform2iv, + a0: dst.c(), + a1: C.uintptr_t(len(src) / 2), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +func Uniform3f(dst Uniform, v0, v1, v2 float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.Uniform3f(%v, %v, %v, %v) %v", dst, v0, v1, v2, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform3f, + a0: dst.c(), + a1: C.uintptr_t(math.Float32bits(v0)), + a2: C.uintptr_t(math.Float32bits(v1)), + a3: C.uintptr_t(math.Float32bits(v2)), + }, + }) +} + +func Uniform3fv(dst Uniform, src []float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.Uniform3fv(%v, len(%d)) %v", dst, len(src), errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform3fv, + a0: dst.c(), + a1: C.uintptr_t(len(src) / 3), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +func Uniform3i(dst Uniform, v0, v1, v2 int32) { + defer func() { + errstr := errDrain() + log.Printf("gl.Uniform3i(%v, %v, %v, %v) %v", dst, v0, v1, v2, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform3i, + a0: dst.c(), + a1: C.uintptr_t(v0), + a2: C.uintptr_t(v1), + a3: C.uintptr_t(v2), + }, + }) +} + +func Uniform3iv(dst Uniform, src []int32) { + defer func() { + errstr := errDrain() + log.Printf("gl.Uniform3iv(%v, %v) %v", dst, src, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform3iv, + a0: dst.c(), + a1: C.uintptr_t(len(src) / 3), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +func Uniform4f(dst Uniform, v0, v1, v2, v3 float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.Uniform4f(%v, %v, %v, %v, %v) %v", dst, v0, v1, v2, v3, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform4f, + a0: dst.c(), + a1: C.uintptr_t(math.Float32bits(v0)), + a2: C.uintptr_t(math.Float32bits(v1)), + a3: C.uintptr_t(math.Float32bits(v2)), + a4: C.uintptr_t(math.Float32bits(v3)), + }, + }) +} + +func Uniform4fv(dst Uniform, src []float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.Uniform4fv(%v, len(%d)) %v", dst, len(src), errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform4fv, + a0: dst.c(), + a1: C.uintptr_t(len(src) / 4), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +func Uniform4i(dst Uniform, v0, v1, v2, v3 int32) { + defer func() { + errstr := errDrain() + log.Printf("gl.Uniform4i(%v, %v, %v, %v, %v) %v", dst, v0, v1, v2, v3, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform4i, + a0: dst.c(), + a1: C.uintptr_t(v0), + a2: C.uintptr_t(v1), + a3: C.uintptr_t(v2), + a4: C.uintptr_t(v3), + }, + }) +} + +func Uniform4iv(dst Uniform, src []int32) { + defer func() { + errstr := errDrain() + log.Printf("gl.Uniform4iv(%v, %v) %v", dst, src, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniform4iv, + a0: dst.c(), + a1: C.uintptr_t(len(src) / 4), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +func UniformMatrix2fv(dst Uniform, src []float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.UniformMatrix2fv(%v, len(%d)) %v", dst, len(src), errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniformMatrix2fv, + + a0: dst.c(), + a1: C.uintptr_t(len(src) / 4), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +func UniformMatrix3fv(dst Uniform, src []float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.UniformMatrix3fv(%v, len(%d)) %v", dst, len(src), errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniformMatrix3fv, + a0: dst.c(), + a1: C.uintptr_t(len(src) / 9), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +func UniformMatrix4fv(dst Uniform, src []float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.UniformMatrix4fv(%v, len(%d)) %v", dst, len(src), errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUniformMatrix4fv, + a0: dst.c(), + a1: C.uintptr_t(len(src) / 16), + a2: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +func UseProgram(p Program) { + defer func() { + errstr := errDrain() + log.Printf("gl.UseProgram(%v) %v", p, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnUseProgram, + a0: p.c(), + }, + }) +} + +func ValidateProgram(p Program) { + defer func() { + errstr := errDrain() + log.Printf("gl.ValidateProgram(%v) %v", p, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnValidateProgram, + a0: p.c(), + }, + }) +} + +func VertexAttrib1f(dst Attrib, x float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.VertexAttrib1f(%v, %v) %v", dst, x, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnVertexAttrib1f, + a0: dst.c(), + a1: C.uintptr_t(math.Float32bits(x)), + }, + }) +} + +func VertexAttrib1fv(dst Attrib, src []float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.VertexAttrib1fv(%v, len(%d)) %v", dst, len(src), errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnVertexAttrib1fv, + a0: dst.c(), + a1: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +func VertexAttrib2f(dst Attrib, x, y float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.VertexAttrib2f(%v, %v, %v) %v", dst, x, y, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnVertexAttrib2f, + a0: dst.c(), + a1: C.uintptr_t(math.Float32bits(x)), + a2: C.uintptr_t(math.Float32bits(y)), + }, + }) +} + +func VertexAttrib2fv(dst Attrib, src []float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.VertexAttrib2fv(%v, len(%d)) %v", dst, len(src), errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnVertexAttrib2fv, + a0: dst.c(), + a1: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +func VertexAttrib3f(dst Attrib, x, y, z float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.VertexAttrib3f(%v, %v, %v, %v) %v", dst, x, y, z, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnVertexAttrib3f, + a0: dst.c(), + a1: C.uintptr_t(math.Float32bits(x)), + a2: C.uintptr_t(math.Float32bits(y)), + a3: C.uintptr_t(math.Float32bits(z)), + }, + }) +} + +func VertexAttrib3fv(dst Attrib, src []float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.VertexAttrib3fv(%v, len(%d)) %v", dst, len(src), errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnVertexAttrib3fv, + a0: dst.c(), + a1: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +func VertexAttrib4f(dst Attrib, x, y, z, w float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.VertexAttrib4f(%v, %v, %v, %v, %v) %v", dst, x, y, z, w, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnVertexAttrib4f, + a0: dst.c(), + a1: C.uintptr_t(math.Float32bits(x)), + a2: C.uintptr_t(math.Float32bits(y)), + a3: C.uintptr_t(math.Float32bits(z)), + a4: C.uintptr_t(math.Float32bits(w)), + }, + }) +} + +func VertexAttrib4fv(dst Attrib, src []float32) { + defer func() { + errstr := errDrain() + log.Printf("gl.VertexAttrib4fv(%v, len(%d)) %v", dst, len(src), errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnVertexAttrib4fv, + a0: dst.c(), + a1: C.uintptr_t(uintptr(unsafe.Pointer(&src[0]))), + }, + blocking: true, + }) +} + +func VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) { + defer func() { + errstr := errDrain() + log.Printf("gl.VertexAttribPointer(%v, %v, %v, %v, %v, %v) %v", dst, size, ty, normalized, stride, offset, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnVertexAttribPointer, + a0: dst.c(), + a1: C.uintptr_t(size), + a2: ty.c(), + a3: glBoolean(normalized), + a4: C.uintptr_t(stride), + a5: C.uintptr_t(offset), + }, + }) +} + +func Viewport(x, y, width, height int) { + defer func() { + errstr := errDrain() + log.Printf("gl.Viewport(%v, %v, %v, %v) %v", x, y, width, height, errstr) + }() + enqueue(call{ + args: C.struct_fnargs{ + fn: C.glfnViewport, + a0: C.uintptr_t(x), + a1: C.uintptr_t(y), + a2: C.uintptr_t(width), + a3: C.uintptr_t(height), + }, + }) +} diff --git a/src/golang.org/x/mobile/gl/types_debug.go b/src/golang.org/x/mobile/gl/types_debug.go new file mode 100644 index 0000000000..e602f2fb37 --- /dev/null +++ b/src/golang.org/x/mobile/gl/types_debug.go @@ -0,0 +1,70 @@ +// Copyright 2014 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 +// +build gldebug + +package gl + +// Alternate versions of the types defined in types.go with extra +// debugging information attached. For documentation, see types.go. + +// #include "work.h" +import "C" +import "fmt" + +type Enum uint32 + +type Attrib struct { + Value uint + name string +} + +type Program struct { + Value uint32 +} + +type Shader struct { + Value uint32 +} + +type Buffer struct { + Value uint32 +} + +type Framebuffer struct { + Value uint32 +} + +type Renderbuffer struct { + Value uint32 +} + +type Texture struct { + Value uint32 +} + +type Uniform struct { + Value int32 + name string +} + +func (v Attrib) c() C.uintptr_t { return C.uintptr_t(v.Value) } +func (v Enum) c() C.uintptr_t { return C.uintptr_t(v) } +func (v Program) c() C.uintptr_t { return C.uintptr_t(v.Value) } +func (v Shader) c() C.uintptr_t { return C.uintptr_t(v.Value) } +func (v Buffer) c() C.uintptr_t { return C.uintptr_t(v.Value) } +func (v Framebuffer) c() C.uintptr_t { return C.uintptr_t(v.Value) } +func (v Renderbuffer) c() C.uintptr_t { return C.uintptr_t(v.Value) } +func (v Texture) c() C.uintptr_t { return C.uintptr_t(v.Value) } +func (v Uniform) c() C.uintptr_t { return C.uintptr_t(v.Value) } + +func (v Attrib) String() string { return fmt.Sprintf("Attrib(%d:%s)", v.Value, v.name) } +func (v Program) String() string { return fmt.Sprintf("Program(%d)", v.Value) } +func (v Shader) String() string { return fmt.Sprintf("Shader(%d)", v.Value) } +func (v Buffer) String() string { return fmt.Sprintf("Buffer(%d)", v.Value) } +func (v Framebuffer) String() string { return fmt.Sprintf("Framebuffer(%d)", v.Value) } +func (v Renderbuffer) String() string { return fmt.Sprintf("Renderbuffer(%d)", v.Value) } +func (v Texture) String() string { return fmt.Sprintf("Texture(%d)", v.Value) } +func (v Uniform) String() string { return fmt.Sprintf("Uniform(%d:%s)", v.Value, v.name) } diff --git a/src/golang.org/x/mobile/gl/types_prod.go b/src/golang.org/x/mobile/gl/types_prod.go new file mode 100644 index 0000000000..90ebfb3fed --- /dev/null +++ b/src/golang.org/x/mobile/gl/types_prod.go @@ -0,0 +1,78 @@ +// Copyright 2014 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 +// +build !gldebug + +package gl + +// #include "work.h" +import "C" +import "fmt" + +// Enum is equivalent to GLenum, and is normally used with one of the +// constants defined in this package. +type Enum uint32 + +// Types are defined a structs so that in debug mode they can carry +// extra information, such as a string name. See typesdebug.go. + +// Attrib identifies the location of a specific attribute variable. +type Attrib struct { + Value uint +} + +// Program identifies a compiled shader program. +type Program struct { + Value uint32 +} + +// Shader identifies a GLSL shader. +type Shader struct { + Value uint32 +} + +// Buffer identifies a GL buffer object. +type Buffer struct { + Value uint32 +} + +// Framebuffer identifies a GL framebuffer. +type Framebuffer struct { + Value uint32 +} + +// A Renderbuffer is a GL object that holds an image in an internal format. +type Renderbuffer struct { + Value uint32 +} + +// A Texture identifies a GL texture unit. +type Texture struct { + Value uint32 +} + +// Uniform identifies the location of a specific uniform variable. +type Uniform struct { + Value int32 +} + +func (v Attrib) c() C.uintptr_t { return C.uintptr_t(v.Value) } +func (v Enum) c() C.uintptr_t { return C.uintptr_t(v) } +func (v Program) c() C.uintptr_t { return C.uintptr_t(v.Value) } +func (v Shader) c() C.uintptr_t { return C.uintptr_t(v.Value) } +func (v Buffer) c() C.uintptr_t { return C.uintptr_t(v.Value) } +func (v Framebuffer) c() C.uintptr_t { return C.uintptr_t(v.Value) } +func (v Renderbuffer) c() C.uintptr_t { return C.uintptr_t(v.Value) } +func (v Texture) c() C.uintptr_t { return C.uintptr_t(v.Value) } +func (v Uniform) c() C.uintptr_t { return C.uintptr_t(v.Value) } + +func (v Attrib) String() string { return fmt.Sprintf("Attrib(%d)", v.Value) } +func (v Program) String() string { return fmt.Sprintf("Program(%d)", v.Value) } +func (v Shader) String() string { return fmt.Sprintf("Shader(%d)", v.Value) } +func (v Buffer) String() string { return fmt.Sprintf("Buffer(%d)", v.Value) } +func (v Framebuffer) String() string { return fmt.Sprintf("Framebuffer(%d)", v.Value) } +func (v Renderbuffer) String() string { return fmt.Sprintf("Renderbuffer(%d)", v.Value) } +func (v Texture) String() string { return fmt.Sprintf("Texture(%d)", v.Value) } +func (v Uniform) String() string { return fmt.Sprintf("Uniform(%d)", v.Value) } diff --git a/src/golang.org/x/mobile/gl/work.c b/src/golang.org/x/mobile/gl/work.c new file mode 100644 index 0000000000..cdea4e5901 --- /dev/null +++ b/src/golang.org/x/mobile/gl/work.c @@ -0,0 +1,476 @@ +// Copyright 2015 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. + +#include +#include "_cgo_export.h" +#include "work.h" + +void processFn(struct fnargs* args) { + switch (args->fn) { + case glfnUNDEFINED: + abort(); // bad glfn + break; + case glfnActiveTexture: + glActiveTexture((GLenum)args->a0); + break; + case glfnAttachShader: + glAttachShader((GLint)args->a0, (GLint)args->a1); + break; + case glfnBindAttribLocation: + glBindAttribLocation((GLint)args->a0, (GLint)args->a1, (GLchar*)args->a2); + free((void*)args->a2); + break; + case glfnBindBuffer: + glBindBuffer((GLenum)args->a0, (GLuint)args->a1); + break; + case glfnBindFramebuffer: + glBindFramebuffer((GLenum)args->a0, (GLint)args->a1); + break; + case glfnBindRenderbuffer: + glBindRenderbuffer((GLenum)args->a0, (GLint)args->a1); + break; + case glfnBindTexture: + glBindTexture((GLenum)args->a0, (GLint)args->a1); + break; + case glfnBlendColor: + glBlendColor(*(GLfloat*)&args->a0, *(GLfloat*)&args->a1, *(GLfloat*)&args->a2, *(GLfloat*)&args->a3); + break; + case glfnBlendEquation: + glBlendEquation((GLenum)args->a0); + break; + case glfnBlendEquationSeparate: + glBlendEquationSeparate((GLenum)args->a0, (GLenum)args->a1); + break; + case glfnBlendFunc: + glBlendFunc((GLenum)args->a0, (GLenum)args->a1); + break; + case glfnBlendFuncSeparate: + glBlendFuncSeparate((GLenum)args->a0, (GLenum)args->a1, (GLenum)args->a2, (GLenum)args->a3); + break; + case glfnBufferData: + glBufferData((GLenum)args->a0, (GLsizeiptr)args->a1, (GLvoid*)args->a2, (GLenum)args->a3); + break; + case glfnBufferSubData: + glBufferSubData((GLenum)args->a0, (GLint)args->a1, (GLsizeiptr)args->a2, (GLvoid*)args->a3); + break; + case glfnCheckFramebufferStatus: + ret = glCheckFramebufferStatus((GLenum)args->a0); + break; + case glfnClear: + glClear((GLenum)args->a0); + break; + case glfnClearColor: + glClearColor(*(GLfloat*)&args->a0, *(GLfloat*)&args->a1, *(GLfloat*)&args->a2, *(GLfloat*)&args->a3); + break; + case glfnClearDepthf: + glClearDepthf(*(GLfloat*)&args->a0); + break; + case glfnClearStencil: + glClearStencil((GLint)args->a0); + break; + case glfnColorMask: + glColorMask((GLboolean)args->a0, (GLboolean)args->a1, (GLboolean)args->a2, (GLboolean)args->a3); + break; + case glfnCompileShader: + glCompileShader((GLint)args->a0); + break; + case glfnCompressedTexImage2D: + glCompressedTexImage2D((GLenum)args->a0, (GLint)args->a1, (GLenum)args->a2, (GLint)args->a3, (GLint)args->a4, (GLint)args->a5, (GLsizeiptr)args->a6, (GLvoid*)args->a7); + break; + case glfnCompressedTexSubImage2D: + glCompressedTexSubImage2D((GLenum)args->a0, (GLint)args->a1, (GLint)args->a2, (GLint)args->a3, (GLint)args->a4, (GLint)args->a5, (GLenum)args->a6, (GLsizeiptr)args->a7, (GLvoid*)args->a8); + break; + case glfnCopyTexImage2D: + glCopyTexImage2D((GLenum)args->a0, (GLint)args->a1, (GLenum)args->a2, (GLint)args->a3, (GLint)args->a4, (GLint)args->a5, (GLint)args->a6, (GLint)args->a7); + break; + case glfnCopyTexSubImage2D: + glCopyTexSubImage2D((GLenum)args->a0, (GLint)args->a1, (GLint)args->a2, (GLint)args->a3, (GLint)args->a4, (GLint)args->a5, (GLint)args->a6, (GLint)args->a7); + break; + case glfnCreateProgram: + ret = glCreateProgram(); + break; + case glfnCreateShader: + ret = glCreateShader((GLenum)args->a0); + break; + case glfnCullFace: + glCullFace((GLenum)args->a0); + break; + case glfnDeleteBuffer: + glDeleteBuffers(1, (const GLuint*)(&args->a0)); + break; + case glfnDeleteFramebuffer: + glDeleteFramebuffers(1, (const GLuint*)(&args->a0)); + break; + case glfnDeleteProgram: + glDeleteProgram((GLint)args->a0); + break; + case glfnDeleteRenderbuffer: + glDeleteRenderbuffers(1, (const GLuint*)(&args->a0)); + break; + case glfnDeleteShader: + glDeleteShader((GLint)args->a0); + break; + case glfnDeleteTexture: + glDeleteTextures(1, (const GLuint*)(&args->a0)); + break; + case glfnDepthFunc: + glDepthFunc((GLenum)args->a0); + break; + case glfnDepthMask: + glDepthMask((GLboolean)args->a0); + break; + case glfnDepthRangef: + glDepthRangef(*(GLfloat*)&args->a0, *(GLfloat*)&args->a1); + break; + case glfnDetachShader: + glDetachShader((GLint)args->a0, (GLint)args->a1); + break; + case glfnDisable: + glDisable((GLenum)args->a0); + break; + case glfnDisableVertexAttribArray: + glDisableVertexAttribArray((GLint)args->a0); + break; + case glfnDrawArrays: + glDrawArrays((GLenum)args->a0, (GLint)args->a1, (GLint)args->a2); + break; + case glfnDrawElements: + glDrawElements((GLenum)args->a0, (GLint)args->a1, (GLenum)args->a2, (void*)args->a3); + break; + case glfnEnable: + glEnable((GLenum)args->a0); + break; + case glfnEnableVertexAttribArray: + glEnableVertexAttribArray((GLint)args->a0); + break; + case glfnFinish: + glFinish(); + break; + case glfnFlush: + glFlush(); + break; + case glfnFramebufferRenderbuffer: + glFramebufferRenderbuffer((GLenum)args->a0, (GLenum)args->a1, (GLenum)args->a2, (GLint)args->a3); + break; + case glfnFramebufferTexture2D: + glFramebufferTexture2D((GLenum)args->a0, (GLenum)args->a1, (GLenum)args->a2, (GLint)args->a3, (GLint)args->a4); + break; + case glfnFrontFace: + glFrontFace((GLenum)args->a0); + break; + case glfnGenBuffer: + glGenBuffers(1, (GLuint*)&ret); + break; + case glfnGenFramebuffer: + glGenFramebuffers(1, (GLuint*)&ret); + break; + case glfnGenRenderbuffer: + glGenRenderbuffers(1, (GLuint*)&ret); + break; + case glfnGenTexture: + glGenTextures(1, (GLuint*)&ret); + break; + case glfnGenerateMipmap: + glGenerateMipmap((GLenum)args->a0); + break; + case glfnGetActiveAttrib: + glGetActiveAttrib( + (GLuint)args->a0, + (GLuint)args->a1, + (GLsizei)args->a2, + NULL, + (GLint*)args->a4, + (GLenum*)args->a5, + (GLchar*)args->a6); + break; + case glfnGetActiveUniform: + glGetActiveUniform( + (GLuint)args->a0, + (GLuint)args->a1, + (GLsizei)args->a2, + NULL, + (GLint*)args->a4, + (GLenum*)args->a5, + (GLchar*)args->a6); + break; + case glfnGetAttachedShaders: + glGetAttachedShaders((GLuint)args->a0, (GLsizei)args->a1, (GLsizei*)args->a2, (GLuint*)args->a3); + break; + case glfnGetAttribLocation: + ret = glGetAttribLocation((GLint)args->a0, (GLchar*)args->a1); + free((void*)args->a1); + break; + case glfnGetBooleanv: + glGetBooleanv((GLenum)args->a0, (GLboolean*)args->a1); + break; + case glfnGetBufferParameteri: + glGetBufferParameteriv((GLenum)args->a0, (GLenum)args->a1, (GLint*)&ret); + break; + case glfnGetFloatv: + glGetFloatv((GLenum)args->a0, (GLfloat*)args->a1); + break; + case glfnGetIntegerv: + glGetIntegerv((GLenum)args->a0, (GLint*)args->a1); + break; + case glfnGetError: + ret = glGetError(); + break; + case glfnGetFramebufferAttachmentParameteriv: + glGetFramebufferAttachmentParameteriv((GLenum)args->a0, (GLenum)args->a1, (GLenum)args->a2, (GLint*)&ret); + break; + case glfnGetProgramiv: + glGetProgramiv((GLint)args->a0, (GLenum)args->a1, (GLint*)&ret); + break; + case glfnGetProgramInfoLog: + glGetProgramInfoLog((GLuint)args->a0, (GLsizei)args->a1, (GLsizei*)args->a2, (GLchar*)args->a3); + break; + case glfnGetRenderbufferParameteriv: + glGetRenderbufferParameteriv((GLenum)args->a0, (GLenum)args->a1, (GLint*)&ret); + break; + case glfnGetShaderiv: + glGetShaderiv((GLint)args->a0, (GLenum)args->a1, (GLint*)&ret); + break; + case glfnGetShaderInfoLog: + glGetShaderInfoLog((GLuint)args->a0, (GLsizei)args->a1, (GLsizei*)args->a2, (GLchar*)args->a3); + break; + case glfnGetShaderPrecisionFormat: + glGetShaderPrecisionFormat((GLenum)args->a0, (GLenum)args->a1, (GLint*)args->a2, (GLint*)args->a3); + break; + case glfnGetShaderSource: + glGetShaderSource((GLuint)args->a0, (GLsizei)args->a1, (GLsizei*)args->a2, (GLchar*)args->a3); + break; + case glfnGetString: + ret = (uintptr_t)glGetString((GLenum)args->a0); + break; + case glfnGetTexParameterfv: + glGetTexParameterfv((GLenum)args->a0, (GLenum)args->a1, (GLfloat*)args->a2); + break; + case glfnGetTexParameteriv: + glGetTexParameteriv((GLenum)args->a0, (GLenum)args->a1, (GLint*)args->a2); + break; + case glfnGetUniformfv: + glGetUniformfv((GLuint)args->a0, (GLint)args->a1, (GLfloat*)args->a2); + break; + case glfnGetUniformiv: + glGetUniformiv((GLuint)args->a0, (GLint)args->a1, (GLint*)args->a2); + break; + case glfnGetUniformLocation: + ret = glGetUniformLocation((GLint)args->a0, (GLchar*)args->a1); + free((void*)args->a1); + break; + case glfnGetVertexAttribfv: + glGetVertexAttribfv((GLuint)args->a0, (GLenum)args->a1, (GLfloat*)args->a2); + break; + case glfnGetVertexAttribiv: + glGetVertexAttribiv((GLuint)args->a0, (GLenum)args->a1, (GLint*)args->a2); + break; + case glfnHint: + glHint((GLenum)args->a0, (GLenum)args->a1); + break; + case glfnIsBuffer: + ret = glIsBuffer((GLint)args->a0); + break; + case glfnIsEnabled: + ret = glIsEnabled((GLenum)args->a0); + break; + case glfnIsFramebuffer: + ret = glIsFramebuffer((GLint)args->a0); + break; + case glfnIsProgram: + ret = glIsProgram((GLint)args->a0); + break; + case glfnIsRenderbuffer: + ret = glIsRenderbuffer((GLint)args->a0); + break; + case glfnIsShader: + ret = glIsShader((GLint)args->a0); + break; + case glfnIsTexture: + ret = glIsTexture((GLint)args->a0); + break; + case glfnLineWidth: + glLineWidth(*(GLfloat*)&args->a0); + break; + case glfnLinkProgram: + glLinkProgram((GLint)args->a0); + break; + case glfnPixelStorei: + glPixelStorei((GLenum)args->a0, (GLint)args->a1); + break; + case glfnPolygonOffset: + glPolygonOffset(*(GLfloat*)&args->a0, *(GLfloat*)&args->a1); + break; + case glfnReadPixels: + glReadPixels((GLint)args->a0, (GLint)args->a1, (GLsizei)args->a2, (GLsizei)args->a3, (GLenum)args->a4, (GLenum)args->a5, (void*)args->a6); + break; + case glfnReleaseShaderCompiler: + glReleaseShaderCompiler(); + break; + case glfnRenderbufferStorage: + glRenderbufferStorage((GLenum)args->a0, (GLenum)args->a1, (GLint)args->a2, (GLint)args->a3); + break; + case glfnSampleCoverage: + glSampleCoverage(*(GLfloat*)&args->a0, (GLboolean)args->a1); + break; + case glfnScissor: + glScissor((GLint)args->a0, (GLint)args->a1, (GLint)args->a2, (GLint)args->a3); + break; + case glfnShaderSource: +#if defined(os_ios) || defined(os_osx) + glShaderSource((GLuint)args->a0, (GLsizei)args->a1, (const GLchar *const *)args->a2, NULL); +#else + glShaderSource((GLuint)args->a0, (GLsizei)args->a1, (const GLchar **)args->a2, NULL); +#endif + free(*(void**)args->a2); + free((void*)args->a2); + break; + case glfnStencilFunc: + glStencilFunc((GLenum)args->a0, (GLint)args->a1, (GLuint)args->a2); + break; + case glfnStencilFuncSeparate: + glStencilFuncSeparate((GLenum)args->a0, (GLenum)args->a1, (GLint)args->a2, (GLuint)args->a3); + break; + case glfnStencilMask: + glStencilMask((GLuint)args->a0); + break; + case glfnStencilMaskSeparate: + glStencilMaskSeparate((GLenum)args->a0, (GLuint)args->a1); + break; + case glfnStencilOp: + glStencilOp((GLenum)args->a0, (GLenum)args->a1, (GLenum)args->a2); + break; + case glfnStencilOpSeparate: + glStencilOpSeparate((GLenum)args->a0, (GLenum)args->a1, (GLenum)args->a2, (GLenum)args->a3); + break; + case glfnTexImage2D: + glTexImage2D( + (GLenum)args->a0, + (GLint)args->a1, + (GLint)args->a2, + (GLsizei)args->a3, + (GLsizei)args->a4, + 0, // border + (GLenum)args->a5, + (GLenum)args->a6, + (const GLvoid*)args->a7); + break; + case glfnTexSubImage2D: + glTexSubImage2D( + (GLenum)args->a0, + (GLint)args->a1, + (GLint)args->a2, + (GLint)args->a3, + (GLsizei)args->a4, + (GLsizei)args->a5, + (GLenum)args->a6, + (GLenum)args->a7, + (const GLvoid*)args->a8); + break; + case glfnTexParameterf: + glTexParameterf((GLenum)args->a0, (GLenum)args->a1, *(GLfloat*)&args->a2); + break; + case glfnTexParameterfv: + glTexParameterfv((GLenum)args->a0, (GLenum)args->a1, (GLfloat*)args->a2); + break; + case glfnTexParameteri: + glTexParameteri((GLenum)args->a0, (GLenum)args->a1, (GLint)args->a2); + break; + case glfnTexParameteriv: + glTexParameteriv((GLenum)args->a0, (GLenum)args->a1, (GLint*)args->a2); + break; + case glfnUniform1f: + glUniform1f((GLint)args->a0, *(GLfloat*)&args->a1); + break; + case glfnUniform1fv: + glUniform1fv((GLint)args->a0, (GLsizeiptr)args->a1, (GLvoid*)args->a2); + break; + case glfnUniform1i: + glUniform1i((GLint)args->a0, (GLint)args->a1); + break; + case glfnUniform1iv: + glUniform1iv((GLint)args->a0, (GLsizeiptr)args->a1, (GLvoid*)args->a2); + break; + case glfnUniform2f: + glUniform2f((GLint)args->a0, *(GLfloat*)&args->a1, *(GLfloat*)&args->a2); + break; + case glfnUniform2fv: + glUniform2fv((GLint)args->a0, (GLsizeiptr)args->a1, (GLvoid*)args->a2); + break; + case glfnUniform2i: + glUniform2i((GLint)args->a0, (GLint)args->a1, (GLint)args->a2); + break; + case glfnUniform2iv: + glUniform2iv((GLint)args->a0, (GLsizeiptr)args->a1, (GLvoid*)args->a2); + break; + case glfnUniform3f: + glUniform3f((GLint)args->a0, *(GLfloat*)&args->a1, *(GLfloat*)&args->a2, *(GLfloat*)&args->a3); + break; + case glfnUniform3fv: + glUniform3fv((GLint)args->a0, (GLsizeiptr)args->a1, (GLvoid*)args->a2); + break; + case glfnUniform3i: + glUniform3i((GLint)args->a0, (GLint)args->a1, (GLint)args->a2, (GLint)args->a3); + break; + case glfnUniform3iv: + glUniform3iv((GLint)args->a0, (GLsizeiptr)args->a1, (GLvoid*)args->a2); + break; + case glfnUniform4f: + glUniform4f((GLint)args->a0, *(GLfloat*)&args->a1, *(GLfloat*)&args->a2, *(GLfloat*)&args->a3, *(GLfloat*)&args->a4); + break; + case glfnUniform4fv: + glUniform4fv((GLint)args->a0, (GLsizeiptr)args->a1, (GLvoid*)args->a2); + break; + case glfnUniform4i: + glUniform4i((GLint)args->a0, (GLint)args->a1, (GLint)args->a2, (GLint)args->a3, (GLint)args->a4); + break; + case glfnUniform4iv: + glUniform4iv((GLint)args->a0, (GLsizeiptr)args->a1, (GLvoid*)args->a2); + break; + case glfnUniformMatrix2fv: + glUniformMatrix2fv((GLint)args->a0, (GLsizeiptr)args->a1, 0, (GLvoid*)args->a2); + break; + case glfnUniformMatrix3fv: + glUniformMatrix3fv((GLint)args->a0, (GLsizeiptr)args->a1, 0, (GLvoid*)args->a2); + break; + case glfnUniformMatrix4fv: + glUniformMatrix4fv((GLint)args->a0, (GLsizeiptr)args->a1, 0, (GLvoid*)args->a2); + break; + case glfnUseProgram: + glUseProgram((GLint)args->a0); + break; + case glfnValidateProgram: + glValidateProgram((GLint)args->a0); + break; + case glfnVertexAttrib1f: + glVertexAttrib1f((GLint)args->a0, *(GLfloat*)&args->a1); + break; + case glfnVertexAttrib1fv: + glVertexAttrib1fv((GLint)args->a0, (GLfloat*)args->a1); + break; + case glfnVertexAttrib2f: + glVertexAttrib2f((GLint)args->a0, *(GLfloat*)&args->a1, *(GLfloat*)&args->a2); + break; + case glfnVertexAttrib2fv: + glVertexAttrib2fv((GLint)args->a0, (GLfloat*)args->a1); + break; + case glfnVertexAttrib3f: + glVertexAttrib3f((GLint)args->a0, *(GLfloat*)&args->a1, *(GLfloat*)&args->a2, *(GLfloat*)&args->a3); + break; + case glfnVertexAttrib3fv: + glVertexAttrib3fv((GLint)args->a0, (GLfloat*)args->a1); + break; + case glfnVertexAttrib4f: + glVertexAttrib4f((GLint)args->a0, *(GLfloat*)&args->a1, *(GLfloat*)&args->a2, *(GLfloat*)&args->a3, *(GLfloat*)&args->a4); + break; + case glfnVertexAttrib4fv: + glVertexAttrib4fv((GLint)args->a0, (GLfloat*)args->a1); + break; + case glfnVertexAttribPointer: + glVertexAttribPointer((GLuint)args->a0, (GLint)args->a1, (GLenum)args->a2, (GLboolean)args->a3, (GLsizei)args->a4, (const GLvoid*)args->a5); + break; + case glfnViewport: + glViewport((GLint)args->a0, (GLint)args->a1, (GLint)args->a2, (GLint)args->a3); + break; + } +} diff --git a/src/golang.org/x/mobile/gl/work.go b/src/golang.org/x/mobile/gl/work.go new file mode 100644 index 0000000000..7df049284c --- /dev/null +++ b/src/golang.org/x/mobile/gl/work.go @@ -0,0 +1,123 @@ +// Copyright 2015 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 darwin linux + +package gl + +/* +#cgo darwin,amd64 LDFLAGS: -framework OpenGL +#cgo darwin,arm LDFLAGS: -framework OpenGLES +#cgo darwin,arm64 LDFLAGS: -framework OpenGLES +#cgo linux LDFLAGS: -lGLESv2 + +#cgo darwin,amd64 CFLAGS: -Dos_osx +#cgo darwin,arm CFLAGS: -Dos_ios +#cgo darwin,arm64 CFLAGS: -Dos_ios +#cgo linux CFLAGS: -Dos_linux + +#include +#include "work.h" + +struct fnargs cargs[10]; +uintptr_t ret; + +void process(int count) { + int i; + for (i = 0; i < count; i++) { + processFn(&cargs[i]); + } +} +*/ +import "C" + +// work is a queue of calls to execute. +var work = make(chan call, 10) + +// retvalue is sent a return value when blocking calls complete. +// It is safe to use a global unbuffered channel here as calls +// cannot currently be made concurrently. +// +// TODO: the comment above about concurrent calls isn't actually true: package +// app calls package gl, but it has to do so in a separate goroutine, which +// means that its gl calls (which may be blocking) can race with other gl calls +// in the main program. We should make it safe to issue blocking gl calls +// concurrently, or get the gl calls out of package app, or both. +var retvalue = make(chan C.uintptr_t) + +type call struct { + args C.struct_fnargs + blocking bool +} + +func enqueue(c call) C.uintptr_t { + work <- c + + select { + case workAvailable <- struct{}{}: + default: + } + + if c.blocking { + return <-retvalue + } + return 0 +} + +var ( + workAvailable = make(chan struct{}, 1) + // WorkAvailable communicates when DoWork should be called. + // + // This is an internal implementation detail and should only be used by the + // golang.org/x/mobile/app package. + WorkAvailable <-chan struct{} = workAvailable +) + +// DoWork performs any pending OpenGL calls. +// +// This is an internal implementation detail and should only be used by the +// golang.org/x/mobile/app package. +func DoWork() { + queue := make([]call, 0, len(work)) + for { + // Wait until at least one piece of work is ready. + // Accumulate work until a piece is marked as blocking. + select { + case w := <-work: + queue = append(queue, w) + default: + return + } + blocking := queue[len(queue)-1].blocking + enqueue: + for len(queue) < cap(queue) && !blocking { + select { + case w := <-work: + queue = append(queue, w) + blocking = queue[len(queue)-1].blocking + default: + break enqueue + } + } + + // Process the queued GL functions. + for i, q := range queue { + C.cargs[i] = q.args + } + C.process(C.int(len(queue))) + + // Cleanup and signal. + queue = queue[:0] + if blocking { + retvalue <- C.ret + } + } +} + +func glBoolean(b bool) C.uintptr_t { + if b { + return TRUE + } + return FALSE +} diff --git a/src/golang.org/x/mobile/gl/work.h b/src/golang.org/x/mobile/gl/work.h new file mode 100644 index 0000000000..dc7d0f09b4 --- /dev/null +++ b/src/golang.org/x/mobile/gl/work.h @@ -0,0 +1,179 @@ +// Copyright 2015 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. + +#ifdef os_linux +#include +#endif +#ifdef os_ios +#include +#endif +#ifdef os_osx +#include +#endif + +#include +#include + +typedef enum { + glfnUNDEFINED, + + glfnActiveTexture, + glfnAttachShader, + glfnBindAttribLocation, + glfnBindBuffer, + glfnBindFramebuffer, + glfnBindRenderbuffer, + glfnBindTexture, + glfnBlendColor, + glfnBlendEquation, + glfnBlendEquationSeparate, + glfnBlendFunc, + glfnBlendFuncSeparate, + glfnBufferData, + glfnBufferSubData, + glfnCheckFramebufferStatus, + glfnClear, + glfnClearColor, + glfnClearDepthf, + glfnClearStencil, + glfnColorMask, + glfnCompileShader, + glfnCompressedTexImage2D, + glfnCompressedTexSubImage2D, + glfnCopyTexImage2D, + glfnCopyTexSubImage2D, + glfnCreateProgram, + glfnCreateShader, + glfnCullFace, + glfnDeleteBuffer, + glfnDeleteFramebuffer, + glfnDeleteProgram, + glfnDeleteRenderbuffer, + glfnDeleteShader, + glfnDeleteTexture, + glfnDepthFunc, + glfnDepthRangef, + glfnDepthMask, + glfnDetachShader, + glfnDisable, + glfnDisableVertexAttribArray, + glfnDrawArrays, + glfnDrawElements, + glfnEnable, + glfnEnableVertexAttribArray, + glfnFinish, + glfnFlush, + glfnFramebufferRenderbuffer, + glfnFramebufferTexture2D, + glfnFrontFace, + glfnGenBuffer, + glfnGenFramebuffer, + glfnGenRenderbuffer, + glfnGenTexture, + glfnGenerateMipmap, + glfnGetActiveAttrib, + glfnGetActiveUniform, + glfnGetAttachedShaders, + glfnGetAttribLocation, + glfnGetBooleanv, + glfnGetBufferParameteri, + glfnGetError, + glfnGetFloatv, + glfnGetFramebufferAttachmentParameteriv, + glfnGetIntegerv, + glfnGetProgramInfoLog, + glfnGetProgramiv, + glfnGetRenderbufferParameteriv, + glfnGetShaderInfoLog, + glfnGetShaderPrecisionFormat, + glfnGetShaderSource, + glfnGetShaderiv, + glfnGetString, + glfnGetTexParameterfv, + glfnGetTexParameteriv, + glfnGetUniformLocation, + glfnGetUniformfv, + glfnGetUniformiv, + glfnGetVertexAttribfv, + glfnGetVertexAttribiv, + glfnHint, + glfnIsBuffer, + glfnIsEnabled, + glfnIsFramebuffer, + glfnIsProgram, + glfnIsRenderbuffer, + glfnIsShader, + glfnIsTexture, + glfnLineWidth, + glfnLinkProgram, + glfnPixelStorei, + glfnPolygonOffset, + glfnReadPixels, + glfnReleaseShaderCompiler, + glfnRenderbufferStorage, + glfnSampleCoverage, + glfnScissor, + glfnShaderSource, + glfnStencilFunc, + glfnStencilFuncSeparate, + glfnStencilMask, + glfnStencilMaskSeparate, + glfnStencilOp, + glfnStencilOpSeparate, + glfnTexImage2D, + glfnTexParameterf, + glfnTexParameterfv, + glfnTexParameteri, + glfnTexParameteriv, + glfnTexSubImage2D, + glfnUniform1f, + glfnUniform1fv, + glfnUniform1i, + glfnUniform1iv, + glfnUniform2f, + glfnUniform2fv, + glfnUniform2i, + glfnUniform2iv, + glfnUniform3f, + glfnUniform3fv, + glfnUniform3i, + glfnUniform3iv, + glfnUniform4f, + glfnUniform4fv, + glfnUniform4i, + glfnUniform4iv, + glfnUniformMatrix2fv, + glfnUniformMatrix3fv, + glfnUniformMatrix4fv, + glfnUseProgram, + glfnValidateProgram, + glfnVertexAttrib1f, + glfnVertexAttrib1fv, + glfnVertexAttrib2f, + glfnVertexAttrib2fv, + glfnVertexAttrib3f, + glfnVertexAttrib3fv, + glfnVertexAttrib4f, + glfnVertexAttrib4fv, + glfnVertexAttribPointer, + glfnViewport, +} glfn; + +struct fnargs { + glfn fn; + + uintptr_t a0; + uintptr_t a1; + uintptr_t a2; + uintptr_t a3; + uintptr_t a4; + uintptr_t a5; + uintptr_t a6; + uintptr_t a7; + uintptr_t a8; +}; + +extern uintptr_t ret; + +extern void processFn(struct fnargs* args); diff --git a/src/golang.org/x/mobile/internal/loader/README b/src/golang.org/x/mobile/internal/loader/README new file mode 100644 index 0000000000..909783de7d --- /dev/null +++ b/src/golang.org/x/mobile/internal/loader/README @@ -0,0 +1,8 @@ +Vendored copy of golang.org/x/tools/go/loader. +See vendor.bash. + +Modifications: + +- removed dependency on x/tools/astutil +- removed dependency on x/tools/buildutil +- uses Go 1.5's go/types package diff --git a/src/golang.org/x/mobile/internal/loader/cgo.go b/src/golang.org/x/mobile/internal/loader/cgo.go new file mode 100644 index 0000000000..299e72579f --- /dev/null +++ b/src/golang.org/x/mobile/internal/loader/cgo.go @@ -0,0 +1,199 @@ +package loader + +// This file handles cgo preprocessing of files containing `import "C"`. +// +// DESIGN +// +// The approach taken is to run the cgo processor on the package's +// CgoFiles and parse the output, faking the filenames of the +// resulting ASTs so that the synthetic file containing the C types is +// called "C" (e.g. "~/go/src/net/C") and the preprocessed files +// have their original names (e.g. "~/go/src/net/cgo_unix.go"), +// not the names of the actual temporary files. +// +// The advantage of this approach is its fidelity to 'go build'. The +// downside is that the token.Position.Offset for each AST node is +// incorrect, being an offset within the temporary file. Line numbers +// should still be correct because of the //line comments. +// +// The logic of this file is mostly plundered from the 'go build' +// tool, which also invokes the cgo preprocessor. +// +// +// REJECTED ALTERNATIVE +// +// An alternative approach that we explored is to extend go/types' +// Importer mechanism to provide the identity of the importing package +// so that each time `import "C"` appears it resolves to a different +// synthetic package containing just the objects needed in that case. +// The loader would invoke cgo but parse only the cgo_types.go file +// defining the package-level objects, discarding the other files +// resulting from preprocessing. +// +// The benefit of this approach would have been that source-level +// syntax information would correspond exactly to the original cgo +// file, with no preprocessing involved, making source tools like +// godoc, oracle, and eg happy. However, the approach was rejected +// due to the additional complexity it would impose on go/types. (It +// made for a beautiful demo, though.) +// +// cgo files, despite their *.go extension, are not legal Go source +// files per the specification since they may refer to unexported +// members of package "C" such as C.int. Also, a function such as +// C.getpwent has in effect two types, one matching its C type and one +// which additionally returns (errno C.int). The cgo preprocessor +// uses name mangling to distinguish these two functions in the +// processed code, but go/types would need to duplicate this logic in +// its handling of function calls, analogous to the treatment of map +// lookups in which y=m[k] and y,ok=m[k] are both legal. + +import ( + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/token" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" +) + +// processCgoFiles invokes the cgo preprocessor on bp.CgoFiles, parses +// the output and returns the resulting ASTs. +// +func processCgoFiles(bp *build.Package, fset *token.FileSet, DisplayPath func(path string) string, mode parser.Mode) ([]*ast.File, error) { + tmpdir, err := ioutil.TempDir("", strings.Replace(bp.ImportPath, "/", "_", -1)+"_C") + if err != nil { + return nil, err + } + defer os.RemoveAll(tmpdir) + + pkgdir := bp.Dir + if DisplayPath != nil { + pkgdir = DisplayPath(pkgdir) + } + + cgoFiles, cgoDisplayFiles, err := runCgo(bp, pkgdir, tmpdir) + if err != nil { + return nil, err + } + var files []*ast.File + for i := range cgoFiles { + rd, err := os.Open(cgoFiles[i]) + if err != nil { + return nil, err + } + defer rd.Close() + display := filepath.Join(bp.Dir, cgoDisplayFiles[i]) + f, err := parser.ParseFile(fset, display, rd, mode) + if err != nil { + return nil, err + } + files = append(files, f) + } + return files, nil +} + +var cgoRe = regexp.MustCompile(`[/\\:]`) + +// runCgo invokes the cgo preprocessor on bp.CgoFiles and returns two +// lists of files: the resulting processed files (in temporary +// directory tmpdir) and the corresponding names of the unprocessed files. +// +// runCgo is adapted from (*builder).cgo in +// $GOROOT/src/cmd/go/build.go, but these features are unsupported: +// pkg-config, Objective C, CGOPKGPATH, CGO_FLAGS. +// +func runCgo(bp *build.Package, pkgdir, tmpdir string) (files, displayFiles []string, err error) { + cgoCPPFLAGS, _, _, _ := cflags(bp, true) + _, cgoexeCFLAGS, _, _ := cflags(bp, false) + + if len(bp.CgoPkgConfig) > 0 { + return nil, nil, fmt.Errorf("cgo pkg-config not supported") + } + + // Allows including _cgo_export.h from .[ch] files in the package. + cgoCPPFLAGS = append(cgoCPPFLAGS, "-I", tmpdir) + + // _cgo_gotypes.go (displayed "C") contains the type definitions. + files = append(files, filepath.Join(tmpdir, "_cgo_gotypes.go")) + displayFiles = append(displayFiles, "C") + for _, fn := range bp.CgoFiles { + // "foo.cgo1.go" (displayed "foo.go") is the processed Go source. + f := cgoRe.ReplaceAllString(fn[:len(fn)-len("go")], "_") + files = append(files, filepath.Join(tmpdir, f+"cgo1.go")) + displayFiles = append(displayFiles, fn) + } + + var cgoflags []string + if bp.Goroot && bp.ImportPath == "runtime/cgo" { + cgoflags = append(cgoflags, "-import_runtime_cgo=false") + } + if bp.Goroot && bp.ImportPath == "runtime/race" || bp.ImportPath == "runtime/cgo" { + cgoflags = append(cgoflags, "-import_syscall=false") + } + + args := stringList( + "go", "tool", "cgo", "-objdir", tmpdir, cgoflags, "--", + cgoCPPFLAGS, cgoexeCFLAGS, bp.CgoFiles, + ) + if false { + log.Printf("Running cgo for package %q: %s (dir=%s)", bp.ImportPath, args, pkgdir) + } + cmd := exec.Command(args[0], args[1:]...) + cmd.Dir = pkgdir + cmd.Stdout = os.Stderr + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return nil, nil, fmt.Errorf("cgo failed: %s: %s", args, err) + } + + return files, displayFiles, nil +} + +// -- unmodified from 'go build' --------------------------------------- + +// Return the flags to use when invoking the C or C++ compilers, or cgo. +func cflags(p *build.Package, def bool) (cppflags, cflags, cxxflags, ldflags []string) { + var defaults string + if def { + defaults = "-g -O2" + } + + cppflags = stringList(envList("CGO_CPPFLAGS", ""), p.CgoCPPFLAGS) + cflags = stringList(envList("CGO_CFLAGS", defaults), p.CgoCFLAGS) + cxxflags = stringList(envList("CGO_CXXFLAGS", defaults), p.CgoCXXFLAGS) + ldflags = stringList(envList("CGO_LDFLAGS", defaults), p.CgoLDFLAGS) + return +} + +// envList returns the value of the given environment variable broken +// into fields, using the default value when the variable is empty. +func envList(key, def string) []string { + v := os.Getenv(key) + if v == "" { + v = def + } + return strings.Fields(v) +} + +// stringList's arguments should be a sequence of string or []string values. +// stringList flattens them into a single []string. +func stringList(args ...interface{}) []string { + var x []string + for _, arg := range args { + switch arg := arg.(type) { + case []string: + x = append(x, arg...) + case string: + x = append(x, arg) + default: + panic("stringList: invalid argument") + } + } + return x +} diff --git a/src/golang.org/x/mobile/internal/loader/loader.go b/src/golang.org/x/mobile/internal/loader/loader.go new file mode 100644 index 0000000000..11d06af2ff --- /dev/null +++ b/src/golang.org/x/mobile/internal/loader/loader.go @@ -0,0 +1,856 @@ +// 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. + +package loader + +// See doc.go for package documentation and implementation notes. + +import ( + "errors" + "fmt" + "go/ast" + "go/build" + goimporter "go/importer" + "go/parser" + "go/token" + "go/types" + "os" + "sort" + "strings" + "sync" + "time" +) + +const trace = false // show timing info for type-checking + +// Config specifies the configuration for loading a whole program from +// Go source code. +// The zero value for Config is a ready-to-use default configuration. +type Config struct { + // Fset is the file set for the parser to use when loading the + // program. If nil, it may be lazily initialized by any + // method of Config. + Fset *token.FileSet + + // ParserMode specifies the mode to be used by the parser when + // loading source packages. + ParserMode parser.Mode + + // TypeChecker contains options relating to the type checker. + // + // The supplied IgnoreFuncBodies is not used; the effective + // value comes from the TypeCheckFuncBodies func below. + // The supplied Import function is not used either. + TypeChecker types.Config + + // TypeCheckFuncBodies is a predicate over package import + // paths. A package for which the predicate is false will + // have its package-level declarations type checked, but not + // its function bodies; this can be used to quickly load + // dependencies from source. If nil, all func bodies are type + // checked. + TypeCheckFuncBodies func(string) bool + + // If Build is non-nil, it is used to locate source packages. + // Otherwise &build.Default is used. + // + // By default, cgo is invoked to preprocess Go files that + // import the fake package "C". This behaviour can be + // disabled by setting CGO_ENABLED=0 in the environment prior + // to startup, or by setting Build.CgoEnabled=false. + Build *build.Context + + // The current directory, used for resolving relative package + // references such as "./go/loader". If empty, os.Getwd will be + // used instead. + Cwd string + + // If DisplayPath is non-nil, it is used to transform each + // file name obtained from Build.Import(). This can be used + // to prevent a virtualized build.Config's file names from + // leaking into the user interface. + DisplayPath func(path string) string + + // If AllowErrors is true, Load will return a Program even + // if some of the its packages contained I/O, parser or type + // errors; such errors are accessible via PackageInfo.Errors. If + // false, Load will fail if any package had an error. + AllowErrors bool + + // CreatePkgs specifies a list of non-importable initial + // packages to create. The resulting packages will appear in + // the corresponding elements of the Program.Created slice. + CreatePkgs []PkgSpec + + // ImportPkgs specifies a set of initial packages to load from + // source. The map keys are package import paths, used to + // locate the package relative to $GOROOT. + // + // The map value indicates whether to load tests. If true, Load + // will add and type-check two lists of files to the package: + // non-test files followed by in-package *_test.go files. In + // addition, it will append the external test package (if any) + // to Program.Created. + ImportPkgs map[string]bool + + // FindPackage is called during Load to create the build.Package + // for a given import path. If nil, a default implementation + // based on ctxt.Import is used. A client may use this hook to + // adapt to a proprietary build system that does not follow the + // "go build" layout conventions, for example. + // + // It must be safe to call concurrently from multiple goroutines. + FindPackage func(ctxt *build.Context, importPath string) (*build.Package, error) +} + +// A PkgSpec specifies a non-importable package to be created by Load. +// Files are processed first, but typically only one of Files and +// Filenames is provided. The path needn't be globally unique. +// +type PkgSpec struct { + Path string // import path ("" => use package declaration) + Files []*ast.File // ASTs of already-parsed files + Filenames []string // names of files to be parsed +} + +// A Program is a Go program loaded from source as specified by a Config. +type Program struct { + Fset *token.FileSet // the file set for this program + + // Created[i] contains the initial package whose ASTs or + // filenames were supplied by Config.CreatePkgs[i], followed by + // the external test package, if any, of each package in + // Config.ImportPkgs ordered by ImportPath. + Created []*PackageInfo + + // Imported contains the initially imported packages, + // as specified by Config.ImportPkgs. + Imported map[string]*PackageInfo + + // AllPackages contains the PackageInfo of every package + // encountered by Load: all initial packages and all + // dependencies, including incomplete ones. + AllPackages map[*types.Package]*PackageInfo + + // importMap is the canonical mapping of import paths to + // packages. It contains all Imported initial packages, but not + // Created ones, and all imported dependencies. + importMap map[string]*types.Package +} + +// PackageInfo holds the ASTs and facts derived by the type-checker +// for a single package. +// +// Not mutated once exposed via the API. +// +type PackageInfo struct { + Pkg *types.Package + Importable bool // true if 'import "Pkg.Path()"' would resolve to this + TransitivelyErrorFree bool // true if Pkg and all its dependencies are free of errors + Files []*ast.File // syntax trees for the package's files + Errors []error // non-nil if the package had errors + types.Info // type-checker deductions. + + checker *types.Checker // transient type-checker state + errorFunc func(error) +} + +func (info *PackageInfo) String() string { return info.Pkg.Path() } + +func (info *PackageInfo) appendError(err error) { + if info.errorFunc != nil { + info.errorFunc(err) + } else { + fmt.Fprintln(os.Stderr, err) + } + info.Errors = append(info.Errors, err) +} + +func (conf *Config) fset() *token.FileSet { + if conf.Fset == nil { + conf.Fset = token.NewFileSet() + } + return conf.Fset +} + +// ParseFile is a convenience function (intended for testing) that invokes +// the parser using the Config's FileSet, which is initialized if nil. +// +// src specifies the parser input as a string, []byte, or io.Reader, and +// filename is its apparent name. If src is nil, the contents of +// filename are read from the file system. +// +func (conf *Config) ParseFile(filename string, src interface{}) (*ast.File, error) { + // TODO(adonovan): use conf.build() etc like parseFiles does. + return parser.ParseFile(conf.fset(), filename, src, conf.ParserMode) +} + +// FromArgsUsage is a partial usage message that applications calling +// FromArgs may wish to include in their -help output. +const FromArgsUsage = ` + is a list of arguments denoting a set of initial packages. +It may take one of two forms: + +1. A list of *.go source files. + + All of the specified files are loaded, parsed and type-checked + as a single package. All the files must belong to the same directory. + +2. A list of import paths, each denoting a package. + + The package's directory is found relative to the $GOROOT and + $GOPATH using similar logic to 'go build', and the *.go files in + that directory are loaded, parsed and type-checked as a single + package. + + In addition, all *_test.go files in the directory are then loaded + and parsed. Those files whose package declaration equals that of + the non-*_test.go files are included in the primary package. Test + files whose package declaration ends with "_test" are type-checked + as another package, the 'external' test package, so that a single + import path may denote two packages. (Whether this behaviour is + enabled is tool-specific, and may depend on additional flags.) + +A '--' argument terminates the list of packages. +` + +// FromArgs interprets args as a set of initial packages to load from +// source and updates the configuration. It returns the list of +// unconsumed arguments. +// +// It is intended for use in command-line interfaces that require a +// set of initial packages to be specified; see FromArgsUsage message +// for details. +// +// Only superficial errors are reported at this stage; errors dependent +// on I/O are detected during Load. +// +func (conf *Config) FromArgs(args []string, xtest bool) ([]string, error) { + var rest []string + for i, arg := range args { + if arg == "--" { + rest = args[i+1:] + args = args[:i] + break // consume "--" and return the remaining args + } + } + + if len(args) > 0 && strings.HasSuffix(args[0], ".go") { + // Assume args is a list of a *.go files + // denoting a single ad hoc package. + for _, arg := range args { + if !strings.HasSuffix(arg, ".go") { + return nil, fmt.Errorf("named files must be .go files: %s", arg) + } + } + conf.CreateFromFilenames("", args...) + } else { + // Assume args are directories each denoting a + // package and (perhaps) an external test, iff xtest. + for _, arg := range args { + if xtest { + conf.ImportWithTests(arg) + } else { + conf.Import(arg) + } + } + } + + return rest, nil +} + +// CreateFromFilenames is a convenience function that adds +// a conf.CreatePkgs entry to create a package of the specified *.go +// files. +// +func (conf *Config) CreateFromFilenames(path string, filenames ...string) { + conf.CreatePkgs = append(conf.CreatePkgs, PkgSpec{Path: path, Filenames: filenames}) +} + +// CreateFromFiles is a convenience function that adds a conf.CreatePkgs +// entry to create package of the specified path and parsed files. +// +func (conf *Config) CreateFromFiles(path string, files ...*ast.File) { + conf.CreatePkgs = append(conf.CreatePkgs, PkgSpec{Path: path, Files: files}) +} + +// ImportWithTests is a convenience function that adds path to +// ImportPkgs, the set of initial source packages located relative to +// $GOPATH. The package will be augmented by any *_test.go files in +// its directory that contain a "package x" (not "package x_test") +// declaration. +// +// In addition, if any *_test.go files contain a "package x_test" +// declaration, an additional package comprising just those files will +// be added to CreatePkgs. +// +func (conf *Config) ImportWithTests(path string) { conf.addImport(path, true) } + +// Import is a convenience function that adds path to ImportPkgs, the +// set of initial packages that will be imported from source. +// +func (conf *Config) Import(path string) { conf.addImport(path, false) } + +func (conf *Config) addImport(path string, tests bool) { + if path == "C" || path == "unsafe" { + return // ignore; not a real package + } + if conf.ImportPkgs == nil { + conf.ImportPkgs = make(map[string]bool) + } + conf.ImportPkgs[path] = conf.ImportPkgs[path] || tests +} + +// InitialPackages returns a new slice containing the set of initial +// packages (Created + Imported) in unspecified order. +// +func (prog *Program) InitialPackages() []*PackageInfo { + infos := make([]*PackageInfo, 0, len(prog.Created)+len(prog.Imported)) + infos = append(infos, prog.Created...) + for _, info := range prog.Imported { + infos = append(infos, info) + } + return infos +} + +// Package returns the ASTs and results of type checking for the +// specified package. +func (prog *Program) Package(path string) *PackageInfo { + if info, ok := prog.AllPackages[prog.importMap[path]]; ok { + return info + } + for _, info := range prog.Created { + if path == info.Pkg.Path() { + return info + } + } + return nil +} + +// ---------- Implementation ---------- + +// importer holds the working state of the algorithm. +type importer struct { + conf *Config // the client configuration + start time.Time // for logging + + progMu sync.Mutex // guards prog + prog *Program // the resulting program + + importedMu sync.Mutex // guards imported + imported map[string]*importInfo // all imported packages (incl. failures) by import path + + // import dependency graph: graph[x][y] => x imports y + // + // Since non-importable packages cannot be cyclic, we ignore + // their imports, thus we only need the subgraph over importable + // packages. Nodes are identified by their import paths. + graphMu sync.Mutex + graph map[string]map[string]bool +} + +// importInfo tracks the success or failure of a single import. +// +// Upon completion, exactly one of info and err is non-nil: +// info on successful creation of a package, err otherwise. +// A successful package may still contain type errors. +// +type importInfo struct { + path string // import path + mu sync.Mutex // guards the following fields prior to completion + info *PackageInfo // results of typechecking (including errors) + err error // reason for failure to create a package + complete sync.Cond // complete condition is that one of info, err is non-nil. +} + +// awaitCompletion blocks until ii is complete, +// i.e. the info and err fields are safe to inspect without a lock. +// It is concurrency-safe and idempotent. +func (ii *importInfo) awaitCompletion() { + ii.mu.Lock() + for ii.info == nil && ii.err == nil { + ii.complete.Wait() + } + ii.mu.Unlock() +} + +// Complete marks ii as complete. +// Its info and err fields will not be subsequently updated. +func (ii *importInfo) Complete(info *PackageInfo, err error) { + ii.mu.Lock() + ii.info = info + ii.err = err + ii.complete.Broadcast() + ii.mu.Unlock() +} + +// Load creates the initial packages specified by conf.{Create,Import}Pkgs, +// loading their dependencies packages as needed. +// +// On success, Load returns a Program containing a PackageInfo for +// each package. On failure, it returns an error. +// +// If AllowErrors is true, Load will return a Program even if some +// packages contained I/O, parser or type errors, or if dependencies +// were missing. (Such errors are accessible via PackageInfo.Errors. If +// false, Load will fail if any package had an error. +// +// It is an error if no packages were loaded. +// +func (conf *Config) Load() (*Program, error) { + // Create a simple default error handler for parse/type errors. + if conf.TypeChecker.Error == nil { + conf.TypeChecker.Error = func(e error) { fmt.Fprintln(os.Stderr, e) } + } + + // Set default working directory for relative package references. + if conf.Cwd == "" { + var err error + conf.Cwd, err = os.Getwd() + if err != nil { + return nil, err + } + } + + // Install default FindPackage hook using go/build logic. + if conf.FindPackage == nil { + conf.FindPackage = func(ctxt *build.Context, path string) (*build.Package, error) { + // TODO(adonovan): cache calls to build.Import + // so we don't do it three times per test package. + bp, err := ctxt.Import(path, conf.Cwd, 0) + if _, ok := err.(*build.NoGoError); ok { + return bp, nil // empty directory is not an error + } + return bp, err + } + } + + prog := &Program{ + Fset: conf.fset(), + Imported: make(map[string]*PackageInfo), + importMap: make(map[string]*types.Package), + AllPackages: make(map[*types.Package]*PackageInfo), + } + + imp := importer{ + conf: conf, + prog: prog, + imported: make(map[string]*importInfo), + start: time.Now(), + graph: make(map[string]map[string]bool), + } + + // -- loading proper (concurrent phase) -------------------------------- + + var errpkgs []string // packages that contained errors + + // Load the initially imported packages and their dependencies, + // in parallel. + for _, ii := range imp.loadAll("", conf.ImportPkgs) { + if ii.err != nil { + conf.TypeChecker.Error(ii.err) // failed to create package + errpkgs = append(errpkgs, ii.path) + continue + } + prog.Imported[ii.info.Pkg.Path()] = ii.info + } + + // Augment the designated initial packages by their tests. + // Dependencies are loaded in parallel. + var xtestPkgs []*build.Package + for path, augment := range conf.ImportPkgs { + if !augment { + continue + } + + bp, err := conf.FindPackage(conf.build(), path) + if err != nil { + // Package not found, or can't even parse package declaration. + // Already reported by previous loop; ignore it. + continue + } + + // Needs external test package? + if len(bp.XTestGoFiles) > 0 { + xtestPkgs = append(xtestPkgs, bp) + } + + imp.importedMu.Lock() // (unnecessary, we're sequential here) + info := imp.imported[path].info // must be non-nil, see above + imp.importedMu.Unlock() + + // Parse the in-package test files. + files, errs := imp.conf.parsePackageFiles(bp, 't') + for _, err := range errs { + info.appendError(err) + } + + // The test files augmenting package P cannot be imported, + // but may import packages that import P, + // so we must disable the cycle check. + imp.addFiles(info, files, false) + } + + createPkg := func(path string, files []*ast.File, errs []error) { + info := imp.newPackageInfo(path) + for _, err := range errs { + info.appendError(err) + } + + // Ad hoc packages are non-importable, + // so no cycle check is needed. + // addFiles loads dependencies in parallel. + imp.addFiles(info, files, false) + prog.Created = append(prog.Created, info) + } + + // Create packages specified by conf.CreatePkgs. + for _, cp := range conf.CreatePkgs { + files, errs := parseFiles(conf.fset(), conf.build(), nil, ".", cp.Filenames, conf.ParserMode) + files = append(files, cp.Files...) + + path := cp.Path + if path == "" { + if len(files) > 0 { + path = files[0].Name.Name + } else { + path = "(unnamed)" + } + } + createPkg(path, files, errs) + } + + // Create external test packages. + sort.Sort(byImportPath(xtestPkgs)) + for _, bp := range xtestPkgs { + files, errs := imp.conf.parsePackageFiles(bp, 'x') + createPkg(bp.ImportPath+"_test", files, errs) + } + + // -- finishing up (sequential) ---------------------------------------- + + if len(prog.Imported)+len(prog.Created) == 0 { + return nil, errors.New("no initial packages were loaded") + } + + // Create infos for indirectly imported packages. + // e.g. incomplete packages without syntax, loaded from export data. + for _, obj := range prog.importMap { + info := prog.AllPackages[obj] + if info == nil { + prog.AllPackages[obj] = &PackageInfo{Pkg: obj, Importable: true} + } else { + // finished + info.checker = nil + info.errorFunc = nil + } + } + + if !conf.AllowErrors { + // Report errors in indirectly imported packages. + for _, info := range prog.AllPackages { + if len(info.Errors) > 0 { + errpkgs = append(errpkgs, info.Pkg.Path()) + } + } + if errpkgs != nil { + var more string + if len(errpkgs) > 3 { + more = fmt.Sprintf(" and %d more", len(errpkgs)-3) + errpkgs = errpkgs[:3] + } + return nil, fmt.Errorf("couldn't load packages due to errors: %s%s", + strings.Join(errpkgs, ", "), more) + } + } + + markErrorFreePackages(prog.AllPackages) + + return prog, nil +} + +type byImportPath []*build.Package + +func (b byImportPath) Len() int { return len(b) } +func (b byImportPath) Less(i, j int) bool { return b[i].ImportPath < b[j].ImportPath } +func (b byImportPath) Swap(i, j int) { b[i], b[j] = b[j], b[i] } + +// markErrorFreePackages sets the TransitivelyErrorFree flag on all +// applicable packages. +func markErrorFreePackages(allPackages map[*types.Package]*PackageInfo) { + // Build the transpose of the import graph. + importedBy := make(map[*types.Package]map[*types.Package]bool) + for P := range allPackages { + for _, Q := range P.Imports() { + clients, ok := importedBy[Q] + if !ok { + clients = make(map[*types.Package]bool) + importedBy[Q] = clients + } + clients[P] = true + } + } + + // Find all packages reachable from some error package. + reachable := make(map[*types.Package]bool) + var visit func(*types.Package) + visit = func(p *types.Package) { + if !reachable[p] { + reachable[p] = true + for q := range importedBy[p] { + visit(q) + } + } + } + for _, info := range allPackages { + if len(info.Errors) > 0 { + visit(info.Pkg) + } + } + + // Mark the others as "transitively error-free". + for _, info := range allPackages { + if !reachable[info.Pkg] { + info.TransitivelyErrorFree = true + } + } +} + +// build returns the effective build context. +func (conf *Config) build() *build.Context { + if conf.Build != nil { + return conf.Build + } + return &build.Default +} + +// parsePackageFiles enumerates the files belonging to package path, +// then loads, parses and returns them, plus a list of I/O or parse +// errors that were encountered. +// +// 'which' indicates which files to include: +// 'g': include non-test *.go source files (GoFiles + processed CgoFiles) +// 't': include in-package *_test.go source files (TestGoFiles) +// 'x': include external *_test.go source files. (XTestGoFiles) +// +func (conf *Config) parsePackageFiles(bp *build.Package, which rune) ([]*ast.File, []error) { + var filenames []string + switch which { + case 'g': + filenames = bp.GoFiles + case 't': + filenames = bp.TestGoFiles + case 'x': + filenames = bp.XTestGoFiles + default: + panic(which) + } + + files, errs := parseFiles(conf.fset(), conf.build(), conf.DisplayPath, bp.Dir, filenames, conf.ParserMode) + + // Preprocess CgoFiles and parse the outputs (sequentially). + if which == 'g' && bp.CgoFiles != nil { + cgofiles, err := processCgoFiles(bp, conf.fset(), conf.DisplayPath, conf.ParserMode) + if err != nil { + errs = append(errs, err) + } else { + files = append(files, cgofiles...) + } + } + + return files, errs +} + +// loadAll loads, parses, and type-checks the specified packages in +// parallel and returns their completed importInfos in unspecified order. +// +// fromPath is the import path of the importing package, if it is +// importable, "" otherwise. It is used for cycle detection. +// +func (imp *importer) loadAll(fromPath string, paths map[string]bool) []*importInfo { + result := make([]*importInfo, 0, len(paths)) + for path := range paths { + result = append(result, imp.startLoad(path)) + } + + if fromPath != "" { + // We're loading a set of imports. + // + // We must record graph edges from the importing package + // to its dependencies, and check for cycles. + imp.graphMu.Lock() + deps, ok := imp.graph[fromPath] + if !ok { + deps = make(map[string]bool) + imp.graph[fromPath] = deps + } + for path := range paths { + deps[path] = true + } + imp.graphMu.Unlock() + } + + for _, ii := range result { + if fromPath != "" { + if cycle := imp.findPath(ii.path, fromPath); cycle != nil { + // Cycle-forming import: we must not await its + // completion since it would deadlock. + // + // We don't record the error in ii since + // the error is really associated with the + // cycle-forming edge, not the package itself. + // (Also it would complicate the + // invariants of importPath completion.) + if trace { + fmt.Fprintln(os.Stderr, "import cycle: %q", cycle) + } + continue + } + } + ii.awaitCompletion() + + } + return result +} + +// findPath returns an arbitrary path from 'from' to 'to' in the import +// graph, or nil if there was none. +func (imp *importer) findPath(from, to string) []string { + imp.graphMu.Lock() + defer imp.graphMu.Unlock() + + seen := make(map[string]bool) + var search func(stack []string, importPath string) []string + search = func(stack []string, importPath string) []string { + if !seen[importPath] { + seen[importPath] = true + stack = append(stack, importPath) + if importPath == to { + return stack + } + for x := range imp.graph[importPath] { + if p := search(stack, x); p != nil { + return p + } + } + } + return nil + } + return search(make([]string, 0, 20), from) +} + +// startLoad initiates the loading, parsing and type-checking of the +// specified package and its dependencies, if it has not already begun. +// +// It returns an importInfo, not necessarily in a completed state. The +// caller must call awaitCompletion() before accessing its info and err +// fields. +// +// startLoad is concurrency-safe and idempotent. +// +// Precondition: path != "unsafe". +// +func (imp *importer) startLoad(path string) *importInfo { + imp.importedMu.Lock() + ii, ok := imp.imported[path] + if !ok { + ii = &importInfo{path: path} + ii.complete.L = &ii.mu + imp.imported[path] = ii + go func() { + ii.Complete(imp.load(path)) + }() + } + imp.importedMu.Unlock() + + return ii +} + +// load implements package loading by parsing Go source files +// located by go/build. +// +func (imp *importer) load(path string) (*PackageInfo, error) { + bp, err := imp.conf.FindPackage(imp.conf.build(), path) + if err != nil { + return nil, err // package not found + } + info := imp.newPackageInfo(bp.ImportPath) + info.Importable = true + files, errs := imp.conf.parsePackageFiles(bp, 'g') + for _, err := range errs { + info.appendError(err) + } + + imp.addFiles(info, files, true) + + imp.progMu.Lock() + imp.prog.importMap[path] = info.Pkg + imp.progMu.Unlock() + + return info, nil +} + +// addFiles adds and type-checks the specified files to info, loading +// their dependencies if needed. The order of files determines the +// package initialization order. It may be called multiple times on the +// same package. Errors are appended to the info.Errors field. +// +// cycleCheck determines whether the imports within files create +// dependency edges that should be checked for potential cycles. +// +func (imp *importer) addFiles(info *PackageInfo, files []*ast.File, cycleCheck bool) { + info.Files = append(info.Files, files...) + + // Ensure the dependencies are loaded, in parallel. + var fromPath string + if cycleCheck { + fromPath = info.Pkg.Path() + } + imp.loadAll(fromPath, scanImports(files)) + + if trace { + fmt.Fprintf(os.Stderr, "%s: start %q (%d)\n", + time.Since(imp.start), info.Pkg.Path(), len(files)) + } + + // Ignore the returned (first) error since we + // already collect them all in the PackageInfo. + info.checker.Files(files) + + if trace { + fmt.Fprintf(os.Stderr, "%s: stop %q\n", + time.Since(imp.start), info.Pkg.Path()) + } +} + +func (imp *importer) newPackageInfo(path string) *PackageInfo { + pkg := types.NewPackage(path, "") + info := &PackageInfo{ + Pkg: pkg, + Info: types.Info{ + Types: make(map[ast.Expr]types.TypeAndValue), + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), + Implicits: make(map[ast.Node]types.Object), + Scopes: make(map[ast.Node]*types.Scope), + Selections: make(map[*ast.SelectorExpr]*types.Selection), + }, + errorFunc: imp.conf.TypeChecker.Error, + } + + // Copy the types.Config so we can vary it across PackageInfos. + tc := imp.conf.TypeChecker + tc.IgnoreFuncBodies = false + if f := imp.conf.TypeCheckFuncBodies; f != nil { + tc.IgnoreFuncBodies = !f(path) + } + tc.Importer = goimporter.Default() + tc.Error = info.appendError // appendError wraps the user's Error function + + info.checker = types.NewChecker(&tc, imp.conf.fset(), pkg, &info.Info) + imp.progMu.Lock() + imp.prog.AllPackages[pkg] = info + imp.progMu.Unlock() + return info +} diff --git a/src/golang.org/x/mobile/internal/loader/util.go b/src/golang.org/x/mobile/internal/loader/util.go new file mode 100644 index 0000000000..b651fc68c5 --- /dev/null +++ b/src/golang.org/x/mobile/internal/loader/util.go @@ -0,0 +1,132 @@ +// 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. + +package loader + +import ( + "go/ast" + "go/build" + "go/parser" + "go/token" + "io" + "os" + "path/filepath" + "strconv" + "sync" +) + +// We use a counting semaphore to limit +// the number of parallel I/O calls per process. +var sema = make(chan bool, 10) + +// parseFiles parses the Go source files within directory dir and +// returns the ASTs of the ones that could be at least partially parsed, +// along with a list of I/O and parse errors encountered. +// +// I/O is done via ctxt, which may specify a virtual file system. +// displayPath is used to transform the filenames attached to the ASTs. +// +func parseFiles(fset *token.FileSet, ctxt *build.Context, displayPath func(string) string, dir string, files []string, mode parser.Mode) ([]*ast.File, []error) { + isAbsPath := ctxt.IsAbsPath + if isAbsPath == nil { + isAbsPath = filepath.IsAbs + } + joinPath := ctxt.JoinPath + if joinPath == nil { + joinPath = filepath.Join + } + + if displayPath == nil { + displayPath = func(path string) string { return path } + } + var wg sync.WaitGroup + n := len(files) + parsed := make([]*ast.File, n) + errors := make([]error, n) + for i, file := range files { + if !isAbsPath(file) { + file = joinPath(dir, file) + } + wg.Add(1) + go func(i int, file string) { + sema <- true // wait + defer func() { + wg.Done() + <-sema // signal + }() + var rd io.ReadCloser + var err error + if ctxt.OpenFile != nil { + rd, err = ctxt.OpenFile(file) + } else { + rd, err = os.Open(file) + } + if err != nil { + errors[i] = err // open failed + return + } + + // ParseFile may return both an AST and an error. + parsed[i], errors[i] = parser.ParseFile(fset, displayPath(file), rd, mode) + rd.Close() + }(i, file) + } + wg.Wait() + + // Eliminate nils, preserving order. + var o int + for _, f := range parsed { + if f != nil { + parsed[o] = f + o++ + } + } + parsed = parsed[:o] + + o = 0 + for _, err := range errors { + if err != nil { + errors[o] = err + o++ + } + } + errors = errors[:o] + + return parsed, errors +} + +// scanImports returns the set of all package import paths from all +// import specs in the specified files. +func scanImports(files []*ast.File) map[string]bool { + imports := make(map[string]bool) + for _, f := range files { + for _, decl := range f.Decls { + if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT { + for _, spec := range decl.Specs { + spec := spec.(*ast.ImportSpec) + + // NB: do not assume the program is well-formed! + path, err := strconv.Unquote(spec.Path.Value) + if err != nil { + continue // quietly ignore the error + } + if path == "C" || path == "unsafe" { + continue // skip pseudo packages + } + imports[path] = true + } + } + } + } + return imports +} + +// ---------- Internal helpers ---------- + +// TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos) +func tokenFileContainsPos(f *token.File, pos token.Pos) bool { + p := int(pos) + base := f.Base() + return base <= p && p < base+f.Size() +} diff --git a/src/golang.org/x/mobile/internal/loader/vendor.bash b/src/golang.org/x/mobile/internal/loader/vendor.bash new file mode 100755 index 0000000000..a5bdffd8f1 --- /dev/null +++ b/src/golang.org/x/mobile/internal/loader/vendor.bash @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +# Copyright 2015 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. + +# Run this script to obtain an up-to-date vendored version of go/loader. + +set -e + +if [ ! -f vendor.bash ]; then + echo 'vendor.bash must be run from the loader directory' 1>&2 + exit 1 +fi + +rm -f *.go +for f in cgo.go loader.go util.go; do + cp $GOPATH/src/golang.org/x/tools/go/loader/$f . +done + +patch -p1 < vendor.patch diff --git a/src/golang.org/x/mobile/internal/loader/vendor.patch b/src/golang.org/x/mobile/internal/loader/vendor.patch new file mode 100644 index 0000000000..b4f0c5fa57 --- /dev/null +++ b/src/golang.org/x/mobile/internal/loader/vendor.patch @@ -0,0 +1,167 @@ +diff --git a/loader.go b/loader.go +index 5555faa..11d06af 100644 +--- a/loader.go ++++ b/loader.go +@@ -11,16 +11,15 @@ import ( + "fmt" + "go/ast" + "go/build" ++ goimporter "go/importer" + "go/parser" + "go/token" ++ "go/types" + "os" + "sort" + "strings" + "sync" + "time" +- +- "golang.org/x/tools/go/ast/astutil" +- "golang.org/x/tools/go/types" + ) + + const trace = false // show timing info for type-checking +@@ -303,33 +302,6 @@ func (conf *Config) addImport(path string, tests bool) { + conf.ImportPkgs[path] = conf.ImportPkgs[path] || tests + } + +-// PathEnclosingInterval returns the PackageInfo and ast.Node that +-// contain source interval [start, end), and all the node's ancestors +-// up to the AST root. It searches all ast.Files of all packages in prog. +-// exact is defined as for astutil.PathEnclosingInterval. +-// +-// The zero value is returned if not found. +-// +-func (prog *Program) PathEnclosingInterval(start, end token.Pos) (pkg *PackageInfo, path []ast.Node, exact bool) { +- for _, info := range prog.AllPackages { +- for _, f := range info.Files { +- if f.Pos() == token.NoPos { +- // This can happen if the parser saw +- // too many errors and bailed out. +- // (Use parser.AllErrors to prevent that.) +- continue +- } +- if !tokenFileContainsPos(prog.Fset.File(f.Pos()), start) { +- continue +- } +- if path, exact := astutil.PathEnclosingInterval(f, start, end); path != nil { +- return info, path, exact +- } +- } +- } +- return nil, nil, false +-} +- + // InitialPackages returns a new slice containing the set of initial + // packages (Created + Imported) in unspecified order. + // +@@ -690,55 +662,6 @@ func (conf *Config) parsePackageFiles(bp *build.Package, which rune) ([]*ast.Fil + return files, errs + } + +-// doImport imports the package denoted by path. +-// It implements the types.Importer signature. +-// +-// imports is the type-checker's package canonicalization map. +-// +-// It returns an error if a package could not be created +-// (e.g. go/build or parse error), but type errors are reported via +-// the types.Config.Error callback (the first of which is also saved +-// in the package's PackageInfo). +-// +-// Idempotent. +-// +-func (imp *importer) doImport(from *PackageInfo, to string) (*types.Package, error) { +- // Package unsafe is handled specially, and has no PackageInfo. +- // TODO(adonovan): move this check into go/types? +- if to == "unsafe" { +- return types.Unsafe, nil +- } +- if to == "C" { +- // This should be unreachable, but ad hoc packages are +- // not currently subject to cgo preprocessing. +- // See https://github.com/golang/go/issues/11627. +- return nil, fmt.Errorf(`the loader doesn't cgo-process ad hoc packages like %q; see Go issue 11627`, +- from.Pkg.Path()) +- } +- +- imp.importedMu.Lock() +- ii := imp.imported[to] +- imp.importedMu.Unlock() +- if ii == nil { +- panic("internal error: unexpected import: " + to) +- } +- if ii.err != nil { +- return nil, ii.err +- } +- if ii.info != nil { +- return ii.info.Pkg, nil +- } +- +- // Import of incomplete package: this indicates a cycle. +- fromPath := from.Pkg.Path() +- if cycle := imp.findPath(to, fromPath); cycle != nil { +- cycle = append([]string{fromPath}, cycle...) +- return nil, fmt.Errorf("import cycle: %s", strings.Join(cycle, " -> ")) +- } +- +- panic("internal error: import of incomplete (yet acyclic) package: " + fromPath) +-} +- + // loadAll loads, parses, and type-checks the specified packages in + // parallel and returns their completed importInfos in unspecified order. + // +@@ -922,9 +845,7 @@ func (imp *importer) newPackageInfo(path string) *PackageInfo { + if f := imp.conf.TypeCheckFuncBodies; f != nil { + tc.IgnoreFuncBodies = !f(path) + } +- tc.Import = func(_ map[string]*types.Package, to string) (*types.Package, error) { +- return imp.doImport(info, to) +- } ++ tc.Importer = goimporter.Default() + tc.Error = info.appendError // appendError wraps the user's Error function + + info.checker = types.NewChecker(&tc, imp.conf.fset(), pkg, &info.Info) +diff --git a/util.go b/util.go +index 0404e99..b651fc6 100644 +--- a/util.go ++++ b/util.go +@@ -11,10 +11,9 @@ import ( + "go/token" + "io" + "os" ++ "path/filepath" + "strconv" + "sync" +- +- "golang.org/x/tools/go/buildutil" + ) + + // We use a counting semaphore to limit +@@ -29,6 +28,15 @@ var sema = make(chan bool, 10) + // displayPath is used to transform the filenames attached to the ASTs. + // + func parseFiles(fset *token.FileSet, ctxt *build.Context, displayPath func(string) string, dir string, files []string, mode parser.Mode) ([]*ast.File, []error) { ++ isAbsPath := ctxt.IsAbsPath ++ if isAbsPath == nil { ++ isAbsPath = filepath.IsAbs ++ } ++ joinPath := ctxt.JoinPath ++ if joinPath == nil { ++ joinPath = filepath.Join ++ } ++ + if displayPath == nil { + displayPath = func(path string) string { return path } + } +@@ -37,8 +45,8 @@ func parseFiles(fset *token.FileSet, ctxt *build.Context, displayPath func(strin + parsed := make([]*ast.File, n) + errors := make([]error, n) + for i, file := range files { +- if !buildutil.IsAbsPath(ctxt, file) { +- file = buildutil.JoinPath(ctxt, dir, file) ++ if !isAbsPath(file) { ++ file = joinPath(dir, file) + } + wg.Add(1) + go func(i int, file string) { diff --git a/src/golang.org/x/mobile/internal/mobileinit/ctx_android.go b/src/golang.org/x/mobile/internal/mobileinit/ctx_android.go new file mode 100644 index 0000000000..753a2c8fa9 --- /dev/null +++ b/src/golang.org/x/mobile/internal/mobileinit/ctx_android.go @@ -0,0 +1,61 @@ +// Copyright 2015 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. + +package mobileinit + +/* +#include +#include + +// current_vm is stored to initialize other cgo packages. +// +// As all the Go packages in a program form a single shared library, +// there can only be one JNI_OnLoad function for initialization. In +// OpenJDK there is JNI_GetCreatedJavaVMs, but this is not available +// on android. +JavaVM* current_vm; + +// current_ctx is Android's android.context.Context. May be NULL. +jobject current_ctx; + +// Set current_vm and current_ctx. The ctx passed in must be a global +// reference instance. +void set_vm_ctx(JavaVM* vm, jobject ctx) { + current_vm = vm; + current_ctx = ctx; + // TODO: check leak +} +*/ +import "C" + +import "unsafe" + +// SetCurrentContext populates the global Context object with the specified +// current JavaVM instance (vm) and android.context.Context object (ctx). +// The android.context.Context object must be a global reference. +func SetCurrentContext(vm, ctx unsafe.Pointer) { + C.set_vm_ctx((*C.JavaVM)(vm), (C.jobject)(ctx)) +} + +// TODO(hyangah): should the app package have Context? It may be useful for +// external packages that need to access android context and vm. + +// Context holds global OS-specific context. +// +// Its extra methods are deliberately difficult to access because they must be +// used with care. Their use implies the use of cgo, which probably requires +// you understand the initialization process in the app package. Also care must +// be taken to write both Android, iOS, and desktop-testing versions to +// maintain portability. +type Context struct{} + +// AndroidContext returns a jobject for the app android.context.Context. +func (Context) AndroidContext() unsafe.Pointer { + return unsafe.Pointer(C.current_ctx) +} + +// JavaVM returns a JNI *JavaVM. +func (Context) JavaVM() unsafe.Pointer { + return unsafe.Pointer(C.current_vm) +} diff --git a/src/golang.org/x/mobile/internal/mobileinit/mobileinit.go b/src/golang.org/x/mobile/internal/mobileinit/mobileinit.go new file mode 100644 index 0000000000..767943b7e0 --- /dev/null +++ b/src/golang.org/x/mobile/internal/mobileinit/mobileinit.go @@ -0,0 +1,9 @@ +// Copyright 2015 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. + +// Package mobileinit contains common initialization logic for mobile platforms +// that is relevant to both all-Go apps and gobind-based apps. +// +// Long-term, some code in this package should consider moving into Go stdlib. +package mobileinit diff --git a/src/golang.org/x/mobile/internal/mobileinit/mobileinit_android.go b/src/golang.org/x/mobile/internal/mobileinit/mobileinit_android.go new file mode 100644 index 0000000000..c3186ad1ca --- /dev/null +++ b/src/golang.org/x/mobile/internal/mobileinit/mobileinit_android.go @@ -0,0 +1,82 @@ +// Copyright 2014 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. + +package mobileinit + +/* +To view the log output run: +adb logcat GoLog:I *:S +*/ + +// Android redirects stdout and stderr to /dev/null. +// As these are common debugging utilities in Go, +// we redirect them to logcat. +// +// Unfortunately, logcat is line oriented, so we must buffer. + +/* +#cgo LDFLAGS: -landroid -llog + +#include +#include +*/ +import "C" + +import ( + "bufio" + "log" + "os" + "unsafe" +) + +var ( + ctag = C.CString("GoLog") +) + +type infoWriter struct{} + +func (infoWriter) Write(p []byte) (n int, err error) { + cstr := C.CString(string(p)) + C.__android_log_write(C.ANDROID_LOG_INFO, ctag, cstr) + C.free(unsafe.Pointer(cstr)) + return len(p), nil +} + +func lineLog(f *os.File, priority C.int) { + const logSize = 1024 // matches android/log.h. + r := bufio.NewReaderSize(f, logSize) + for { + line, _, err := r.ReadLine() + str := string(line) + if err != nil { + str += " " + err.Error() + } + cstr := C.CString(str) + C.__android_log_write(priority, ctag, cstr) + C.free(unsafe.Pointer(cstr)) + if err != nil { + break + } + } +} + +func init() { + log.SetOutput(infoWriter{}) + // android logcat includes all of log.LstdFlags + log.SetFlags(log.Flags() &^ log.LstdFlags) + + r, w, err := os.Pipe() + if err != nil { + panic(err) + } + os.Stderr = w + go lineLog(r, C.ANDROID_LOG_ERROR) + + r, w, err = os.Pipe() + if err != nil { + panic(err) + } + os.Stdout = w + go lineLog(r, C.ANDROID_LOG_INFO) +} diff --git a/src/golang.org/x/mobile/misc/androidstudio/.gitignore b/src/golang.org/x/mobile/misc/androidstudio/.gitignore new file mode 100644 index 0000000000..0b6269368b --- /dev/null +++ b/src/golang.org/x/mobile/misc/androidstudio/.gitignore @@ -0,0 +1,2 @@ +.gradle +/build diff --git a/src/golang.org/x/mobile/misc/androidstudio/README.md b/src/golang.org/x/mobile/misc/androidstudio/README.md new file mode 100644 index 0000000000..5a01e45e39 --- /dev/null +++ b/src/golang.org/x/mobile/misc/androidstudio/README.md @@ -0,0 +1,31 @@ +gobindPlugin invokes gomobile bind command on the specified package. + +# Usage + +build.gradle: +
+plugins {
+  id "org.golang.mobile.bind" version "0.2.2"
+}
+
+gobind {
+  // package to bind
+  pkg "github.com/someone/somepackage"
+
+  // GOPATH
+  GOPATH "/home/gopher"
+
+  // Absolute path to the gomobile binary
+  GOMOBILE "/mypath/bin/gomobile"
+
+  // Absolute path to the go binary
+  GO "/usr/local/go/bin/go"
+}
+
+ +For details: +https://plugins.gradle.org/plugin/org.golang.mobile.bind + +# TODO + +* Find the stale aar file (how?) diff --git a/src/golang.org/x/mobile/misc/androidstudio/build.gradle b/src/golang.org/x/mobile/misc/androidstudio/build.gradle new file mode 100644 index 0000000000..68760dc556 --- /dev/null +++ b/src/golang.org/x/mobile/misc/androidstudio/build.gradle @@ -0,0 +1,43 @@ +apply plugin: 'groovy' + +// Many android users still use JDK7. +sourceCompatibility = '1.7' +targetCompatibility = '1.7' + +buildscript { + repositories { + maven { + url "https://plugins.gradle.org/m2/" + } + } + dependencies { + classpath "com.gradle.publish:plugin-publish-plugin:0.9.1" + } +} + +apply plugin: "com.gradle.plugin-publish" +repositories { + jcenter() +} + +dependencies { + compile gradleApi() + compile localGroovy() + + testCompile 'junit:junit:4.11' +} + +pluginBundle { + website = 'https://golang.org/x/mobile' + vcsUrl = 'https://golang.org/x/mobile' + description = 'Plugin for gomobile projects (beta)' + version = '0.2.2' + + plugins { + gobindPlugin { + id = 'org.golang.mobile.bind' + displayName = 'gomobile bind plugin' + tags = ['golang', 'gomobile', 'gobind'] + } + } +} diff --git a/src/golang.org/x/mobile/misc/androidstudio/settings.gradle b/src/golang.org/x/mobile/misc/androidstudio/settings.gradle new file mode 100644 index 0000000000..b1def88a56 --- /dev/null +++ b/src/golang.org/x/mobile/misc/androidstudio/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'gobindPlugin' diff --git a/src/golang.org/x/mobile/misc/androidstudio/src/main/groovy/org/golang/mobile/AARPublishArtifact.java b/src/golang.org/x/mobile/misc/androidstudio/src/main/groovy/org/golang/mobile/AARPublishArtifact.java new file mode 100644 index 0000000000..a9e60fc700 --- /dev/null +++ b/src/golang.org/x/mobile/misc/androidstudio/src/main/groovy/org/golang/mobile/AARPublishArtifact.java @@ -0,0 +1,89 @@ +/** + * Copyright 2015 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. + */ + +package org.golang.mobile; + +import org.golang.mobile.OutputFileTask; + +import org.gradle.api.Task; +import org.gradle.api.artifacts.PublishArtifact; +import org.gradle.api.tasks.TaskDependency; + +import java.io.File; +import java.util.Collections; +import java.util.Date; +import java.util.Set; + +/** + * custom implementation of PublishArtifact for published AAR + */ +public class AARPublishArtifact implements PublishArtifact { + + private final String name; + private final String classifier; + private final OutputFileTask task; + private final TaskDependency taskDependency; + + private static final class DefaultTaskDependency implements TaskDependency { + + private final Set tasks; + + DefaultTaskDependency(Task task) { + this.tasks = Collections.singleton(task); + } + + @Override + public Set getDependencies(Task task) { + return tasks; + } + } + + public AARPublishArtifact( + String name, + String classifier, + OutputFileTask task) { + this.name = name; + this.classifier = classifier; + this.task = task; + this.taskDependency = new DefaultTaskDependency((Task) task); + } + + + @Override + public String getName() { + return name; + } + + @Override + public String getExtension() { + return "aar"; + } + + @Override + public String getType() { + return "aar"; + } + + @Override + public String getClassifier() { + return classifier; + } + + @Override + public File getFile() { + return task.getOutputFile(); + } + + @Override + public Date getDate() { + return null; + } + + @Override + public TaskDependency getBuildDependencies() { + return taskDependency; + } +} diff --git a/src/golang.org/x/mobile/misc/androidstudio/src/main/groovy/org/golang/mobile/GobindPlugin.groovy b/src/golang.org/x/mobile/misc/androidstudio/src/main/groovy/org/golang/mobile/GobindPlugin.groovy new file mode 100644 index 0000000000..0721bf690d --- /dev/null +++ b/src/golang.org/x/mobile/misc/androidstudio/src/main/groovy/org/golang/mobile/GobindPlugin.groovy @@ -0,0 +1,143 @@ +/* + * Copyright 2015 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. + */ + +package org.golang.mobile + +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.Project +import org.gradle.api.Plugin +import org.gradle.api.Task +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction + +import org.golang.mobile.OutputFileTask +import org.golang.mobile.AARPublishArtifact + +/* + * GobindPlugin configures the default project that builds .AAR file + * from a go package, using gomobile bind command. + * For gomobile bind command, see https://golang.org/x/mobile/cmd/gomobile + */ +class GobindPlugin implements Plugin { + void apply(Project project) { + project.configurations.create("default") + project.extensions.create('gobind', GobindExtension) + + Task gobindTask = project.tasks.create("gobind", GobindTask) + gobindTask.outputFile = project.file(project.name+".aar") + project.artifacts.add("default", new AARPublishArtifact( + 'mylib', + null, + gobindTask)) + + Task cleanTask = project.tasks.create("clean", { + project.delete(project.name+".aar") + }) + } +} + +class GobindTask extends DefaultTask implements OutputFileTask { + @OutputFile + File outputFile + + @TaskAction + def gobind() { + def pkg = project.gobind.pkg + def gopath = (project.gobind.GOPATH ?: System.getenv("GOPATH"))?.trim() + if (!pkg || !gopath) { + throw new GradleException('gobind.pkg and gobind.GOPATH must be set') + } + + def paths = (gopath.split(File.pathSeparator).collect{ "$it/bin" } + + System.getenv("PATH").split(File.pathSeparator)).flatten() + // Default installation path of go distribution. + if (isWindows()) { + paths = paths + "c:\\Go\\bin" + } else { + paths = paths + "/usr/local/go/bin" + } + + def gomobile = (project.gobind.GOMOBILE ?: findExecutable("gomobile", paths))?.trim() + def gobin = (project.gobind.GO ?: findExecutable("go", paths))?.trim() + def gomobileFlags = project.gobind.GOMOBILEFLAGS?.trim() + + if (!gomobile || !gobin) { + throw new GradleException('failed to find gomobile/go tools. Set gobind.GOMOBILE and gobind.GO') + } + + paths = [findDir(gomobile), findDir(gobin), paths].flatten() + + Properties properties = new Properties() + properties.load(project.rootProject.file('local.properties').newDataInputStream()) + def androidHome = properties.getProperty('sdk.dir') + if (!androidHome?.trim()) { + // fallback to ANDROID_HOME + androidHome = System.getenv("ANDROID_HOME") + } + + project.exec { + executable(gomobile) + + def cmd = ["bind", "-target=android", "-i", "-o", project.name+".aar"] + if (gomobileFlags) { + cmd = (cmd+gomobileFlags.split(" ")).flatten() + } + cmd << pkg + + args(cmd) + if (!androidHome?.trim()) { + throw new GradleException('Neither sdk.dir or ANDROID_HOME is set') + } + environment("GOPATH", gopath) + environment("PATH", paths.join(File.pathSeparator)) + environment("ANDROID_HOME", androidHome) + } + } + + def isWindows() { + return System.getProperty("os.name").startsWith("Windows") + } + + def findExecutable(String name, ArrayList paths) { + if (isWindows() && !name.endsWith(".exe")) { + name = name + ".exe" + } + for (p in paths) { + def f = new File(p + File.separator + name) + if (f.exists()) { + return p + File.separator + name + } + } + throw new GradleException('binary ' + name + ' is not found in $PATH (' + paths + ')') + } + + def findDir(String binpath) { + if (!binpath) { + return "" + } + + def f = new File(binpath) + return f.getParentFile().getAbsolutePath(); + } +} + +class GobindExtension { + // Package to bind. (required) + def String pkg = "" + + // GOPATH: necessary for gomobile tool. (required) + def String GOPATH = System.getenv("GOPATH") + + // GO: path to go tool. (can omit if 'go' is in the paths visible by Android Studio) + def String GO = "" + + // GOMOBILE: path to gomobile binary. (can omit if 'gomobile' is under GOPATH) + def String GOMOBILE = "" + + // GOMOBILEFLAGS: extra flags to be passed to gomobile command. (optional) + def String GOMOBILEFLAGS = "" +} diff --git a/src/golang.org/x/mobile/misc/androidstudio/src/main/groovy/org/golang/mobile/OutputFileTask.java b/src/golang.org/x/mobile/misc/androidstudio/src/main/groovy/org/golang/mobile/OutputFileTask.java new file mode 100644 index 0000000000..6c41f6c6eb --- /dev/null +++ b/src/golang.org/x/mobile/misc/androidstudio/src/main/groovy/org/golang/mobile/OutputFileTask.java @@ -0,0 +1,16 @@ +/** + * Copyright 2015 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. + */ + +package org.golang.mobile; + +import java.io.File; + +/** + * A task that outputs a file. + */ +public interface OutputFileTask { + File getOutputFile(); +} diff --git a/src/golang.org/x/mobile/misc/androidstudio/src/main/resources/META-INF/gradle-plugins/org.golang.mobile.bind.properties b/src/golang.org/x/mobile/misc/androidstudio/src/main/resources/META-INF/gradle-plugins/org.golang.mobile.bind.properties new file mode 100644 index 0000000000..edc8043a53 --- /dev/null +++ b/src/golang.org/x/mobile/misc/androidstudio/src/main/resources/META-INF/gradle-plugins/org.golang.mobile.bind.properties @@ -0,0 +1 @@ +implementation-class=org.golang.mobile.GobindPlugin diff --git a/src/golang.org/x/mobile/misc/androidstudio/src/test/groovy/org/golang/mobile/GobindPluginTest.groovy b/src/golang.org/x/mobile/misc/androidstudio/src/test/groovy/org/golang/mobile/GobindPluginTest.groovy new file mode 100644 index 0000000000..ddb512bb5f --- /dev/null +++ b/src/golang.org/x/mobile/misc/androidstudio/src/test/groovy/org/golang/mobile/GobindPluginTest.groovy @@ -0,0 +1,22 @@ +/** + * Copyright 2015 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. + */ + +package org.golang.mobile + +import org.junit.Test +import org.gradle.testfixtures.ProjectBuilder +import org.gradle.api.Project +import static org.junit.Assert.* + +class GobindPluginTest { + @Test + public void gobindPluginAddsGobindTaskToProject() { + Project project = ProjectBuilder.builder().build() + project.apply plugin: 'org.golang.mobile.bind' + + assertTrue(project.tasks.gobind instanceof GobindTask) + } +} diff --git a/src/golang.org/x/mobile/misc/androidstudio/src/test/groovy/org/golang/mobile/GobindTaskTest.groovy b/src/golang.org/x/mobile/misc/androidstudio/src/test/groovy/org/golang/mobile/GobindTaskTest.groovy new file mode 100644 index 0000000000..0492fd577b --- /dev/null +++ b/src/golang.org/x/mobile/misc/androidstudio/src/test/groovy/org/golang/mobile/GobindTaskTest.groovy @@ -0,0 +1,21 @@ +/** + * Copyright 2015 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. + */ + +package org.golang.mobile + +import org.junit.Test +import org.gradle.testfixtures.ProjectBuilder +import org.gradle.api.Project +import static org.junit.Assert.* + +class GobindTaskTest { + @Test + public void canAddTaskToProject() { + Project project = ProjectBuilder.builder().build() + def task = project.task('gobind', type: GobindTask) + assertTrue(task instanceof GobindTask) + } +} diff --git a/src/golang.org/x/mobile/testdata/gophercolor.png b/src/golang.org/x/mobile/testdata/gophercolor.png new file mode 100644 index 0000000000..a1a21613ad Binary files /dev/null and b/src/golang.org/x/mobile/testdata/gophercolor.png differ diff --git a/src/golang.org/x/mobile/testdata/gopherswim.png b/src/golang.org/x/mobile/testdata/gopherswim.png new file mode 100644 index 0000000000..5ed0eafb40 Binary files /dev/null and b/src/golang.org/x/mobile/testdata/gopherswim.png differ diff --git a/src/golang.org/x/mobile/testdata/testpattern-window.png b/src/golang.org/x/mobile/testdata/testpattern-window.png new file mode 100644 index 0000000000..3262253819 Binary files /dev/null and b/src/golang.org/x/mobile/testdata/testpattern-window.png differ diff --git a/src/golang.org/x/mobile/testdata/testpattern.png b/src/golang.org/x/mobile/testdata/testpattern.png new file mode 100644 index 0000000000..ec87bb56a2 Binary files /dev/null and b/src/golang.org/x/mobile/testdata/testpattern.png differ diff --git a/src/golang.org/x/net/.gitattributes b/src/golang.org/x/net/.gitattributes new file mode 100644 index 0000000000..d2f212e5da --- /dev/null +++ b/src/golang.org/x/net/.gitattributes @@ -0,0 +1,10 @@ +# Treat all files in this repo as binary, with no git magic updating +# line endings. Windows users contributing to Go will need to use a +# modern version of git and editors capable of LF line endings. +# +# We'll prevent accidental CRLF line endings from entering the repo +# via the git-review gofmt checks. +# +# See golang.org/issue/9281 + +* -text diff --git a/src/golang.org/x/net/.gitignore b/src/golang.org/x/net/.gitignore new file mode 100644 index 0000000000..8339fd61d3 --- /dev/null +++ b/src/golang.org/x/net/.gitignore @@ -0,0 +1,2 @@ +# Add no patterns to .hgignore except for files generated by the build. +last-change diff --git a/src/golang.org/x/net/AUTHORS b/src/golang.org/x/net/AUTHORS new file mode 100644 index 0000000000..15167cd746 --- /dev/null +++ b/src/golang.org/x/net/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at http://tip.golang.org/AUTHORS. diff --git a/src/golang.org/x/net/CONTRIBUTING.md b/src/golang.org/x/net/CONTRIBUTING.md new file mode 100644 index 0000000000..88dff59bc7 --- /dev/null +++ b/src/golang.org/x/net/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# Contributing to Go + +Go is an open source project. + +It is the work of hundreds of contributors. We appreciate your help! + + +## Filing issues + +When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions: + +1. What version of Go are you using (`go version`)? +2. What operating system and processor architecture are you using? +3. What did you do? +4. What did you expect to see? +5. What did you see instead? + +General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker. +The gophers there will answer or ask you to file an issue if you've tripped over a bug. + +## Contributing code + +Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html) +before sending patches. + +**We do not accept GitHub pull requests** +(we use [Gerrit](https://code.google.com/p/gerrit/) instead for code review). + +Unless otherwise noted, the Go source files are distributed under +the BSD-style license found in the LICENSE file. + diff --git a/src/golang.org/x/net/CONTRIBUTORS b/src/golang.org/x/net/CONTRIBUTORS new file mode 100644 index 0000000000..1c4577e968 --- /dev/null +++ b/src/golang.org/x/net/CONTRIBUTORS @@ -0,0 +1,3 @@ +# This source code was written by the Go contributors. +# The master list of contributors is in the main Go distribution, +# visible at http://tip.golang.org/CONTRIBUTORS. diff --git a/src/golang.org/x/net/LICENSE b/src/golang.org/x/net/LICENSE new file mode 100644 index 0000000000..6a66aea5ea --- /dev/null +++ b/src/golang.org/x/net/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/golang.org/x/net/PATENTS b/src/golang.org/x/net/PATENTS new file mode 100644 index 0000000000..733099041f --- /dev/null +++ b/src/golang.org/x/net/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google 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, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/src/golang.org/x/net/README b/src/golang.org/x/net/README new file mode 100644 index 0000000000..6b13d8e505 --- /dev/null +++ b/src/golang.org/x/net/README @@ -0,0 +1,3 @@ +This repository holds supplementary Go networking libraries. + +To submit changes to this repository, see http://golang.org/doc/contribute.html. diff --git a/src/golang.org/x/net/codereview.cfg b/src/golang.org/x/net/codereview.cfg new file mode 100644 index 0000000000..3f8b14b64e --- /dev/null +++ b/src/golang.org/x/net/codereview.cfg @@ -0,0 +1 @@ +issuerepo: golang/go diff --git a/src/golang.org/x/net/context/context.go b/src/golang.org/x/net/context/context.go new file mode 100644 index 0000000000..e7ee376c47 --- /dev/null +++ b/src/golang.org/x/net/context/context.go @@ -0,0 +1,447 @@ +// Copyright 2014 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. + +// Package context defines the Context type, which carries deadlines, +// cancelation signals, and other request-scoped values across API boundaries +// and between processes. +// +// Incoming requests to a server should create a Context, and outgoing calls to +// servers should accept a Context. The chain of function calls between must +// propagate the Context, optionally replacing it with a modified copy created +// using WithDeadline, WithTimeout, WithCancel, or WithValue. +// +// Programs that use Contexts should follow these rules to keep interfaces +// consistent across packages and enable static analysis tools to check context +// propagation: +// +// Do not store Contexts inside a struct type; instead, pass a Context +// explicitly to each function that needs it. The Context should be the first +// parameter, typically named ctx: +// +// func DoSomething(ctx context.Context, arg Arg) error { +// // ... use ctx ... +// } +// +// Do not pass a nil Context, even if a function permits it. Pass context.TODO +// if you are unsure about which Context to use. +// +// Use context Values only for request-scoped data that transits processes and +// APIs, not for passing optional parameters to functions. +// +// The same Context may be passed to functions running in different goroutines; +// Contexts are safe for simultaneous use by multiple goroutines. +// +// See http://blog.golang.org/context for example code for a server that uses +// Contexts. +package context // import "golang.org/x/net/context" + +import ( + "errors" + "fmt" + "sync" + "time" +) + +// A Context carries a deadline, a cancelation signal, and other values across +// API boundaries. +// +// Context's methods may be called by multiple goroutines simultaneously. +type Context interface { + // Deadline returns the time when work done on behalf of this context + // should be canceled. Deadline returns ok==false when no deadline is + // set. Successive calls to Deadline return the same results. + Deadline() (deadline time.Time, ok bool) + + // Done returns a channel that's closed when work done on behalf of this + // context should be canceled. Done may return nil if this context can + // never be canceled. Successive calls to Done return the same value. + // + // WithCancel arranges for Done to be closed when cancel is called; + // WithDeadline arranges for Done to be closed when the deadline + // expires; WithTimeout arranges for Done to be closed when the timeout + // elapses. + // + // Done is provided for use in select statements: + // + // // Stream generates values with DoSomething and sends them to out + // // until DoSomething returns an error or ctx.Done is closed. + // func Stream(ctx context.Context, out <-chan Value) error { + // for { + // v, err := DoSomething(ctx) + // if err != nil { + // return err + // } + // select { + // case <-ctx.Done(): + // return ctx.Err() + // case out <- v: + // } + // } + // } + // + // See http://blog.golang.org/pipelines for more examples of how to use + // a Done channel for cancelation. + Done() <-chan struct{} + + // Err returns a non-nil error value after Done is closed. Err returns + // Canceled if the context was canceled or DeadlineExceeded if the + // context's deadline passed. No other values for Err are defined. + // After Done is closed, successive calls to Err return the same value. + Err() error + + // Value returns the value associated with this context for key, or nil + // if no value is associated with key. Successive calls to Value with + // the same key returns the same result. + // + // Use context values only for request-scoped data that transits + // processes and API boundaries, not for passing optional parameters to + // functions. + // + // A key identifies a specific value in a Context. Functions that wish + // to store values in Context typically allocate a key in a global + // variable then use that key as the argument to context.WithValue and + // Context.Value. A key can be any type that supports equality; + // packages should define keys as an unexported type to avoid + // collisions. + // + // Packages that define a Context key should provide type-safe accessors + // for the values stores using that key: + // + // // Package user defines a User type that's stored in Contexts. + // package user + // + // import "golang.org/x/net/context" + // + // // User is the type of value stored in the Contexts. + // type User struct {...} + // + // // key is an unexported type for keys defined in this package. + // // This prevents collisions with keys defined in other packages. + // type key int + // + // // userKey is the key for user.User values in Contexts. It is + // // unexported; clients use user.NewContext and user.FromContext + // // instead of using this key directly. + // var userKey key = 0 + // + // // NewContext returns a new Context that carries value u. + // func NewContext(ctx context.Context, u *User) context.Context { + // return context.WithValue(ctx, userKey, u) + // } + // + // // FromContext returns the User value stored in ctx, if any. + // func FromContext(ctx context.Context) (*User, bool) { + // u, ok := ctx.Value(userKey).(*User) + // return u, ok + // } + Value(key interface{}) interface{} +} + +// Canceled is the error returned by Context.Err when the context is canceled. +var Canceled = errors.New("context canceled") + +// DeadlineExceeded is the error returned by Context.Err when the context's +// deadline passes. +var DeadlineExceeded = errors.New("context deadline exceeded") + +// An emptyCtx is never canceled, has no values, and has no deadline. It is not +// struct{}, since vars of this type must have distinct addresses. +type emptyCtx int + +func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { + return +} + +func (*emptyCtx) Done() <-chan struct{} { + return nil +} + +func (*emptyCtx) Err() error { + return nil +} + +func (*emptyCtx) Value(key interface{}) interface{} { + return nil +} + +func (e *emptyCtx) String() string { + switch e { + case background: + return "context.Background" + case todo: + return "context.TODO" + } + return "unknown empty Context" +} + +var ( + background = new(emptyCtx) + todo = new(emptyCtx) +) + +// Background returns a non-nil, empty Context. It is never canceled, has no +// values, and has no deadline. It is typically used by the main function, +// initialization, and tests, and as the top-level Context for incoming +// requests. +func Background() Context { + return background +} + +// TODO returns a non-nil, empty Context. Code should use context.TODO when +// it's unclear which Context to use or it's is not yet available (because the +// surrounding function has not yet been extended to accept a Context +// parameter). TODO is recognized by static analysis tools that determine +// whether Contexts are propagated correctly in a program. +func TODO() Context { + return todo +} + +// A CancelFunc tells an operation to abandon its work. +// A CancelFunc does not wait for the work to stop. +// After the first call, subsequent calls to a CancelFunc do nothing. +type CancelFunc func() + +// WithCancel returns a copy of parent with a new Done channel. The returned +// context's Done channel is closed when the returned cancel function is called +// or when the parent context's Done channel is closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { + c := newCancelCtx(parent) + propagateCancel(parent, &c) + return &c, func() { c.cancel(true, Canceled) } +} + +// newCancelCtx returns an initialized cancelCtx. +func newCancelCtx(parent Context) cancelCtx { + return cancelCtx{ + Context: parent, + done: make(chan struct{}), + } +} + +// propagateCancel arranges for child to be canceled when parent is. +func propagateCancel(parent Context, child canceler) { + if parent.Done() == nil { + return // parent is never canceled + } + if p, ok := parentCancelCtx(parent); ok { + p.mu.Lock() + if p.err != nil { + // parent has already been canceled + child.cancel(false, p.err) + } else { + if p.children == nil { + p.children = make(map[canceler]bool) + } + p.children[child] = true + } + p.mu.Unlock() + } else { + go func() { + select { + case <-parent.Done(): + child.cancel(false, parent.Err()) + case <-child.Done(): + } + }() + } +} + +// parentCancelCtx follows a chain of parent references until it finds a +// *cancelCtx. This function understands how each of the concrete types in this +// package represents its parent. +func parentCancelCtx(parent Context) (*cancelCtx, bool) { + for { + switch c := parent.(type) { + case *cancelCtx: + return c, true + case *timerCtx: + return &c.cancelCtx, true + case *valueCtx: + parent = c.Context + default: + return nil, false + } + } +} + +// removeChild removes a context from its parent. +func removeChild(parent Context, child canceler) { + p, ok := parentCancelCtx(parent) + if !ok { + return + } + p.mu.Lock() + if p.children != nil { + delete(p.children, child) + } + p.mu.Unlock() +} + +// A canceler is a context type that can be canceled directly. The +// implementations are *cancelCtx and *timerCtx. +type canceler interface { + cancel(removeFromParent bool, err error) + Done() <-chan struct{} +} + +// A cancelCtx can be canceled. When canceled, it also cancels any children +// that implement canceler. +type cancelCtx struct { + Context + + done chan struct{} // closed by the first cancel call. + + mu sync.Mutex + children map[canceler]bool // set to nil by the first cancel call + err error // set to non-nil by the first cancel call +} + +func (c *cancelCtx) Done() <-chan struct{} { + return c.done +} + +func (c *cancelCtx) Err() error { + c.mu.Lock() + defer c.mu.Unlock() + return c.err +} + +func (c *cancelCtx) String() string { + return fmt.Sprintf("%v.WithCancel", c.Context) +} + +// cancel closes c.done, cancels each of c's children, and, if +// removeFromParent is true, removes c from its parent's children. +func (c *cancelCtx) cancel(removeFromParent bool, err error) { + if err == nil { + panic("context: internal error: missing cancel error") + } + c.mu.Lock() + if c.err != nil { + c.mu.Unlock() + return // already canceled + } + c.err = err + close(c.done) + for child := range c.children { + // NOTE: acquiring the child's lock while holding parent's lock. + child.cancel(false, err) + } + c.children = nil + c.mu.Unlock() + + if removeFromParent { + removeChild(c.Context, c) + } +} + +// WithDeadline returns a copy of the parent context with the deadline adjusted +// to be no later than d. If the parent's deadline is already earlier than d, +// WithDeadline(parent, d) is semantically equivalent to parent. The returned +// context's Done channel is closed when the deadline expires, when the returned +// cancel function is called, or when the parent context's Done channel is +// closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { + if cur, ok := parent.Deadline(); ok && cur.Before(deadline) { + // The current deadline is already sooner than the new one. + return WithCancel(parent) + } + c := &timerCtx{ + cancelCtx: newCancelCtx(parent), + deadline: deadline, + } + propagateCancel(parent, c) + d := deadline.Sub(time.Now()) + if d <= 0 { + c.cancel(true, DeadlineExceeded) // deadline has already passed + return c, func() { c.cancel(true, Canceled) } + } + c.mu.Lock() + defer c.mu.Unlock() + if c.err == nil { + c.timer = time.AfterFunc(d, func() { + c.cancel(true, DeadlineExceeded) + }) + } + return c, func() { c.cancel(true, Canceled) } +} + +// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to +// implement Done and Err. It implements cancel by stopping its timer then +// delegating to cancelCtx.cancel. +type timerCtx struct { + cancelCtx + timer *time.Timer // Under cancelCtx.mu. + + deadline time.Time +} + +func (c *timerCtx) Deadline() (deadline time.Time, ok bool) { + return c.deadline, true +} + +func (c *timerCtx) String() string { + return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now())) +} + +func (c *timerCtx) cancel(removeFromParent bool, err error) { + c.cancelCtx.cancel(false, err) + if removeFromParent { + // Remove this timerCtx from its parent cancelCtx's children. + removeChild(c.cancelCtx.Context, c) + } + c.mu.Lock() + if c.timer != nil { + c.timer.Stop() + c.timer = nil + } + c.mu.Unlock() +} + +// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)). +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete: +// +// func slowOperationWithTimeout(ctx context.Context) (Result, error) { +// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) +// defer cancel() // releases resources if slowOperation completes before timeout elapses +// return slowOperation(ctx) +// } +func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { + return WithDeadline(parent, time.Now().Add(timeout)) +} + +// WithValue returns a copy of parent in which the value associated with key is +// val. +// +// Use context Values only for request-scoped data that transits processes and +// APIs, not for passing optional parameters to functions. +func WithValue(parent Context, key interface{}, val interface{}) Context { + return &valueCtx{parent, key, val} +} + +// A valueCtx carries a key-value pair. It implements Value for that key and +// delegates all other calls to the embedded Context. +type valueCtx struct { + Context + key, val interface{} +} + +func (c *valueCtx) String() string { + return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val) +} + +func (c *valueCtx) Value(key interface{}) interface{} { + if c.key == key { + return c.val + } + return c.Context.Value(key) +} diff --git a/src/golang.org/x/net/context/context_test.go b/src/golang.org/x/net/context/context_test.go new file mode 100644 index 0000000000..faf67722a0 --- /dev/null +++ b/src/golang.org/x/net/context/context_test.go @@ -0,0 +1,575 @@ +// Copyright 2014 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. + +package context + +import ( + "fmt" + "math/rand" + "runtime" + "strings" + "sync" + "testing" + "time" +) + +// otherContext is a Context that's not one of the types defined in context.go. +// This lets us test code paths that differ based on the underlying type of the +// Context. +type otherContext struct { + Context +} + +func TestBackground(t *testing.T) { + c := Background() + if c == nil { + t.Fatalf("Background returned nil") + } + select { + case x := <-c.Done(): + t.Errorf("<-c.Done() == %v want nothing (it should block)", x) + default: + } + if got, want := fmt.Sprint(c), "context.Background"; got != want { + t.Errorf("Background().String() = %q want %q", got, want) + } +} + +func TestTODO(t *testing.T) { + c := TODO() + if c == nil { + t.Fatalf("TODO returned nil") + } + select { + case x := <-c.Done(): + t.Errorf("<-c.Done() == %v want nothing (it should block)", x) + default: + } + if got, want := fmt.Sprint(c), "context.TODO"; got != want { + t.Errorf("TODO().String() = %q want %q", got, want) + } +} + +func TestWithCancel(t *testing.T) { + c1, cancel := WithCancel(Background()) + + if got, want := fmt.Sprint(c1), "context.Background.WithCancel"; got != want { + t.Errorf("c1.String() = %q want %q", got, want) + } + + o := otherContext{c1} + c2, _ := WithCancel(o) + contexts := []Context{c1, o, c2} + + for i, c := range contexts { + if d := c.Done(); d == nil { + t.Errorf("c[%d].Done() == %v want non-nil", i, d) + } + if e := c.Err(); e != nil { + t.Errorf("c[%d].Err() == %v want nil", i, e) + } + + select { + case x := <-c.Done(): + t.Errorf("<-c.Done() == %v want nothing (it should block)", x) + default: + } + } + + cancel() + time.Sleep(100 * time.Millisecond) // let cancelation propagate + + for i, c := range contexts { + select { + case <-c.Done(): + default: + t.Errorf("<-c[%d].Done() blocked, but shouldn't have", i) + } + if e := c.Err(); e != Canceled { + t.Errorf("c[%d].Err() == %v want %v", i, e, Canceled) + } + } +} + +func TestParentFinishesChild(t *testing.T) { + // Context tree: + // parent -> cancelChild + // parent -> valueChild -> timerChild + parent, cancel := WithCancel(Background()) + cancelChild, stop := WithCancel(parent) + defer stop() + valueChild := WithValue(parent, "key", "value") + timerChild, stop := WithTimeout(valueChild, 10000*time.Hour) + defer stop() + + select { + case x := <-parent.Done(): + t.Errorf("<-parent.Done() == %v want nothing (it should block)", x) + case x := <-cancelChild.Done(): + t.Errorf("<-cancelChild.Done() == %v want nothing (it should block)", x) + case x := <-timerChild.Done(): + t.Errorf("<-timerChild.Done() == %v want nothing (it should block)", x) + case x := <-valueChild.Done(): + t.Errorf("<-valueChild.Done() == %v want nothing (it should block)", x) + default: + } + + // The parent's children should contain the two cancelable children. + pc := parent.(*cancelCtx) + cc := cancelChild.(*cancelCtx) + tc := timerChild.(*timerCtx) + pc.mu.Lock() + if len(pc.children) != 2 || !pc.children[cc] || !pc.children[tc] { + t.Errorf("bad linkage: pc.children = %v, want %v and %v", + pc.children, cc, tc) + } + pc.mu.Unlock() + + if p, ok := parentCancelCtx(cc.Context); !ok || p != pc { + t.Errorf("bad linkage: parentCancelCtx(cancelChild.Context) = %v, %v want %v, true", p, ok, pc) + } + if p, ok := parentCancelCtx(tc.Context); !ok || p != pc { + t.Errorf("bad linkage: parentCancelCtx(timerChild.Context) = %v, %v want %v, true", p, ok, pc) + } + + cancel() + + pc.mu.Lock() + if len(pc.children) != 0 { + t.Errorf("pc.cancel didn't clear pc.children = %v", pc.children) + } + pc.mu.Unlock() + + // parent and children should all be finished. + check := func(ctx Context, name string) { + select { + case <-ctx.Done(): + default: + t.Errorf("<-%s.Done() blocked, but shouldn't have", name) + } + if e := ctx.Err(); e != Canceled { + t.Errorf("%s.Err() == %v want %v", name, e, Canceled) + } + } + check(parent, "parent") + check(cancelChild, "cancelChild") + check(valueChild, "valueChild") + check(timerChild, "timerChild") + + // WithCancel should return a canceled context on a canceled parent. + precanceledChild := WithValue(parent, "key", "value") + select { + case <-precanceledChild.Done(): + default: + t.Errorf("<-precanceledChild.Done() blocked, but shouldn't have") + } + if e := precanceledChild.Err(); e != Canceled { + t.Errorf("precanceledChild.Err() == %v want %v", e, Canceled) + } +} + +func TestChildFinishesFirst(t *testing.T) { + cancelable, stop := WithCancel(Background()) + defer stop() + for _, parent := range []Context{Background(), cancelable} { + child, cancel := WithCancel(parent) + + select { + case x := <-parent.Done(): + t.Errorf("<-parent.Done() == %v want nothing (it should block)", x) + case x := <-child.Done(): + t.Errorf("<-child.Done() == %v want nothing (it should block)", x) + default: + } + + cc := child.(*cancelCtx) + pc, pcok := parent.(*cancelCtx) // pcok == false when parent == Background() + if p, ok := parentCancelCtx(cc.Context); ok != pcok || (ok && pc != p) { + t.Errorf("bad linkage: parentCancelCtx(cc.Context) = %v, %v want %v, %v", p, ok, pc, pcok) + } + + if pcok { + pc.mu.Lock() + if len(pc.children) != 1 || !pc.children[cc] { + t.Errorf("bad linkage: pc.children = %v, cc = %v", pc.children, cc) + } + pc.mu.Unlock() + } + + cancel() + + if pcok { + pc.mu.Lock() + if len(pc.children) != 0 { + t.Errorf("child's cancel didn't remove self from pc.children = %v", pc.children) + } + pc.mu.Unlock() + } + + // child should be finished. + select { + case <-child.Done(): + default: + t.Errorf("<-child.Done() blocked, but shouldn't have") + } + if e := child.Err(); e != Canceled { + t.Errorf("child.Err() == %v want %v", e, Canceled) + } + + // parent should not be finished. + select { + case x := <-parent.Done(): + t.Errorf("<-parent.Done() == %v want nothing (it should block)", x) + default: + } + if e := parent.Err(); e != nil { + t.Errorf("parent.Err() == %v want nil", e) + } + } +} + +func testDeadline(c Context, wait time.Duration, t *testing.T) { + select { + case <-time.After(wait): + t.Fatalf("context should have timed out") + case <-c.Done(): + } + if e := c.Err(); e != DeadlineExceeded { + t.Errorf("c.Err() == %v want %v", e, DeadlineExceeded) + } +} + +func TestDeadline(t *testing.T) { + c, _ := WithDeadline(Background(), time.Now().Add(100*time.Millisecond)) + if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) { + t.Errorf("c.String() = %q want prefix %q", got, prefix) + } + testDeadline(c, 200*time.Millisecond, t) + + c, _ = WithDeadline(Background(), time.Now().Add(100*time.Millisecond)) + o := otherContext{c} + testDeadline(o, 200*time.Millisecond, t) + + c, _ = WithDeadline(Background(), time.Now().Add(100*time.Millisecond)) + o = otherContext{c} + c, _ = WithDeadline(o, time.Now().Add(300*time.Millisecond)) + testDeadline(c, 200*time.Millisecond, t) +} + +func TestTimeout(t *testing.T) { + c, _ := WithTimeout(Background(), 100*time.Millisecond) + if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) { + t.Errorf("c.String() = %q want prefix %q", got, prefix) + } + testDeadline(c, 200*time.Millisecond, t) + + c, _ = WithTimeout(Background(), 100*time.Millisecond) + o := otherContext{c} + testDeadline(o, 200*time.Millisecond, t) + + c, _ = WithTimeout(Background(), 100*time.Millisecond) + o = otherContext{c} + c, _ = WithTimeout(o, 300*time.Millisecond) + testDeadline(c, 200*time.Millisecond, t) +} + +func TestCanceledTimeout(t *testing.T) { + c, _ := WithTimeout(Background(), 200*time.Millisecond) + o := otherContext{c} + c, cancel := WithTimeout(o, 400*time.Millisecond) + cancel() + time.Sleep(100 * time.Millisecond) // let cancelation propagate + select { + case <-c.Done(): + default: + t.Errorf("<-c.Done() blocked, but shouldn't have") + } + if e := c.Err(); e != Canceled { + t.Errorf("c.Err() == %v want %v", e, Canceled) + } +} + +type key1 int +type key2 int + +var k1 = key1(1) +var k2 = key2(1) // same int as k1, different type +var k3 = key2(3) // same type as k2, different int + +func TestValues(t *testing.T) { + check := func(c Context, nm, v1, v2, v3 string) { + if v, ok := c.Value(k1).(string); ok == (len(v1) == 0) || v != v1 { + t.Errorf(`%s.Value(k1).(string) = %q, %t want %q, %t`, nm, v, ok, v1, len(v1) != 0) + } + if v, ok := c.Value(k2).(string); ok == (len(v2) == 0) || v != v2 { + t.Errorf(`%s.Value(k2).(string) = %q, %t want %q, %t`, nm, v, ok, v2, len(v2) != 0) + } + if v, ok := c.Value(k3).(string); ok == (len(v3) == 0) || v != v3 { + t.Errorf(`%s.Value(k3).(string) = %q, %t want %q, %t`, nm, v, ok, v3, len(v3) != 0) + } + } + + c0 := Background() + check(c0, "c0", "", "", "") + + c1 := WithValue(Background(), k1, "c1k1") + check(c1, "c1", "c1k1", "", "") + + if got, want := fmt.Sprint(c1), `context.Background.WithValue(1, "c1k1")`; got != want { + t.Errorf("c.String() = %q want %q", got, want) + } + + c2 := WithValue(c1, k2, "c2k2") + check(c2, "c2", "c1k1", "c2k2", "") + + c3 := WithValue(c2, k3, "c3k3") + check(c3, "c2", "c1k1", "c2k2", "c3k3") + + c4 := WithValue(c3, k1, nil) + check(c4, "c4", "", "c2k2", "c3k3") + + o0 := otherContext{Background()} + check(o0, "o0", "", "", "") + + o1 := otherContext{WithValue(Background(), k1, "c1k1")} + check(o1, "o1", "c1k1", "", "") + + o2 := WithValue(o1, k2, "o2k2") + check(o2, "o2", "c1k1", "o2k2", "") + + o3 := otherContext{c4} + check(o3, "o3", "", "c2k2", "c3k3") + + o4 := WithValue(o3, k3, nil) + check(o4, "o4", "", "c2k2", "") +} + +func TestAllocs(t *testing.T) { + bg := Background() + for _, test := range []struct { + desc string + f func() + limit float64 + gccgoLimit float64 + }{ + { + desc: "Background()", + f: func() { Background() }, + limit: 0, + gccgoLimit: 0, + }, + { + desc: fmt.Sprintf("WithValue(bg, %v, nil)", k1), + f: func() { + c := WithValue(bg, k1, nil) + c.Value(k1) + }, + limit: 3, + gccgoLimit: 3, + }, + { + desc: "WithTimeout(bg, 15*time.Millisecond)", + f: func() { + c, _ := WithTimeout(bg, 15*time.Millisecond) + <-c.Done() + }, + limit: 8, + gccgoLimit: 13, + }, + { + desc: "WithCancel(bg)", + f: func() { + c, cancel := WithCancel(bg) + cancel() + <-c.Done() + }, + limit: 5, + gccgoLimit: 8, + }, + { + desc: "WithTimeout(bg, 100*time.Millisecond)", + f: func() { + c, cancel := WithTimeout(bg, 100*time.Millisecond) + cancel() + <-c.Done() + }, + limit: 8, + gccgoLimit: 25, + }, + } { + limit := test.limit + if runtime.Compiler == "gccgo" { + // gccgo does not yet do escape analysis. + // TOOD(iant): Remove this when gccgo does do escape analysis. + limit = test.gccgoLimit + } + if n := testing.AllocsPerRun(100, test.f); n > limit { + t.Errorf("%s allocs = %f want %d", test.desc, n, int(limit)) + } + } +} + +func TestSimultaneousCancels(t *testing.T) { + root, cancel := WithCancel(Background()) + m := map[Context]CancelFunc{root: cancel} + q := []Context{root} + // Create a tree of contexts. + for len(q) != 0 && len(m) < 100 { + parent := q[0] + q = q[1:] + for i := 0; i < 4; i++ { + ctx, cancel := WithCancel(parent) + m[ctx] = cancel + q = append(q, ctx) + } + } + // Start all the cancels in a random order. + var wg sync.WaitGroup + wg.Add(len(m)) + for _, cancel := range m { + go func(cancel CancelFunc) { + cancel() + wg.Done() + }(cancel) + } + // Wait on all the contexts in a random order. + for ctx := range m { + select { + case <-ctx.Done(): + case <-time.After(1 * time.Second): + buf := make([]byte, 10<<10) + n := runtime.Stack(buf, true) + t.Fatalf("timed out waiting for <-ctx.Done(); stacks:\n%s", buf[:n]) + } + } + // Wait for all the cancel functions to return. + done := make(chan struct{}) + go func() { + wg.Wait() + close(done) + }() + select { + case <-done: + case <-time.After(1 * time.Second): + buf := make([]byte, 10<<10) + n := runtime.Stack(buf, true) + t.Fatalf("timed out waiting for cancel functions; stacks:\n%s", buf[:n]) + } +} + +func TestInterlockedCancels(t *testing.T) { + parent, cancelParent := WithCancel(Background()) + child, cancelChild := WithCancel(parent) + go func() { + parent.Done() + cancelChild() + }() + cancelParent() + select { + case <-child.Done(): + case <-time.After(1 * time.Second): + buf := make([]byte, 10<<10) + n := runtime.Stack(buf, true) + t.Fatalf("timed out waiting for child.Done(); stacks:\n%s", buf[:n]) + } +} + +func TestLayersCancel(t *testing.T) { + testLayers(t, time.Now().UnixNano(), false) +} + +func TestLayersTimeout(t *testing.T) { + testLayers(t, time.Now().UnixNano(), true) +} + +func testLayers(t *testing.T, seed int64, testTimeout bool) { + rand.Seed(seed) + errorf := func(format string, a ...interface{}) { + t.Errorf(fmt.Sprintf("seed=%d: %s", seed, format), a...) + } + const ( + timeout = 200 * time.Millisecond + minLayers = 30 + ) + type value int + var ( + vals []*value + cancels []CancelFunc + numTimers int + ctx = Background() + ) + for i := 0; i < minLayers || numTimers == 0 || len(cancels) == 0 || len(vals) == 0; i++ { + switch rand.Intn(3) { + case 0: + v := new(value) + ctx = WithValue(ctx, v, v) + vals = append(vals, v) + case 1: + var cancel CancelFunc + ctx, cancel = WithCancel(ctx) + cancels = append(cancels, cancel) + case 2: + var cancel CancelFunc + ctx, cancel = WithTimeout(ctx, timeout) + cancels = append(cancels, cancel) + numTimers++ + } + } + checkValues := func(when string) { + for _, key := range vals { + if val := ctx.Value(key).(*value); key != val { + errorf("%s: ctx.Value(%p) = %p want %p", when, key, val, key) + } + } + } + select { + case <-ctx.Done(): + errorf("ctx should not be canceled yet") + default: + } + if s, prefix := fmt.Sprint(ctx), "context.Background."; !strings.HasPrefix(s, prefix) { + t.Errorf("ctx.String() = %q want prefix %q", s, prefix) + } + t.Log(ctx) + checkValues("before cancel") + if testTimeout { + select { + case <-ctx.Done(): + case <-time.After(timeout + timeout/10): + errorf("ctx should have timed out") + } + checkValues("after timeout") + } else { + cancel := cancels[rand.Intn(len(cancels))] + cancel() + select { + case <-ctx.Done(): + default: + errorf("ctx should be canceled") + } + checkValues("after cancel") + } +} + +func TestCancelRemoves(t *testing.T) { + checkChildren := func(when string, ctx Context, want int) { + if got := len(ctx.(*cancelCtx).children); got != want { + t.Errorf("%s: context has %d children, want %d", when, got, want) + } + } + + ctx, _ := WithCancel(Background()) + checkChildren("after creation", ctx, 0) + _, cancel := WithCancel(ctx) + checkChildren("with WithCancel child ", ctx, 1) + cancel() + checkChildren("after cancelling WithCancel child", ctx, 0) + + ctx, _ = WithCancel(Background()) + checkChildren("after creation", ctx, 0) + _, cancel = WithTimeout(ctx, 60*time.Minute) + checkChildren("with WithTimeout child ", ctx, 1) + cancel() + checkChildren("after cancelling WithTimeout child", ctx, 0) +} diff --git a/src/golang.org/x/net/context/withtimeout_test.go b/src/golang.org/x/net/context/withtimeout_test.go new file mode 100644 index 0000000000..a6754dc368 --- /dev/null +++ b/src/golang.org/x/net/context/withtimeout_test.go @@ -0,0 +1,26 @@ +// Copyright 2014 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. + +package context_test + +import ( + "fmt" + "time" + + "golang.org/x/net/context" +) + +func ExampleWithTimeout() { + // Pass a context with a timeout to tell a blocking function that it + // should abandon its work after the timeout elapses. + ctx, _ := context.WithTimeout(context.Background(), 100*time.Millisecond) + select { + case <-time.After(200 * time.Millisecond): + fmt.Println("overslept") + case <-ctx.Done(): + fmt.Println(ctx.Err()) // prints "context deadline exceeded" + } + // Output: + // context deadline exceeded +} diff --git a/src/golang.org/x/net/dict/dict.go b/src/golang.org/x/net/dict/dict.go new file mode 100644 index 0000000000..58fef89e06 --- /dev/null +++ b/src/golang.org/x/net/dict/dict.go @@ -0,0 +1,210 @@ +// Copyright 2010 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. + +// Package dict implements the Dictionary Server Protocol +// as defined in RFC 2229. +package dict // import "golang.org/x/net/dict" + +import ( + "net/textproto" + "strconv" + "strings" +) + +// A Client represents a client connection to a dictionary server. +type Client struct { + text *textproto.Conn +} + +// Dial returns a new client connected to a dictionary server at +// addr on the given network. +func Dial(network, addr string) (*Client, error) { + text, err := textproto.Dial(network, addr) + if err != nil { + return nil, err + } + _, _, err = text.ReadCodeLine(220) + if err != nil { + text.Close() + return nil, err + } + return &Client{text: text}, nil +} + +// Close closes the connection to the dictionary server. +func (c *Client) Close() error { + return c.text.Close() +} + +// A Dict represents a dictionary available on the server. +type Dict struct { + Name string // short name of dictionary + Desc string // long description +} + +// Dicts returns a list of the dictionaries available on the server. +func (c *Client) Dicts() ([]Dict, error) { + id, err := c.text.Cmd("SHOW DB") + if err != nil { + return nil, err + } + + c.text.StartResponse(id) + defer c.text.EndResponse(id) + + _, _, err = c.text.ReadCodeLine(110) + if err != nil { + return nil, err + } + lines, err := c.text.ReadDotLines() + if err != nil { + return nil, err + } + _, _, err = c.text.ReadCodeLine(250) + + dicts := make([]Dict, len(lines)) + for i := range dicts { + d := &dicts[i] + a, _ := fields(lines[i]) + if len(a) < 2 { + return nil, textproto.ProtocolError("invalid dictionary: " + lines[i]) + } + d.Name = a[0] + d.Desc = a[1] + } + return dicts, err +} + +// A Defn represents a definition. +type Defn struct { + Dict Dict // Dict where definition was found + Word string // Word being defined + Text []byte // Definition text, typically multiple lines +} + +// Define requests the definition of the given word. +// The argument dict names the dictionary to use, +// the Name field of a Dict returned by Dicts. +// +// The special dictionary name "*" means to look in all the +// server's dictionaries. +// The special dictionary name "!" means to look in all the +// server's dictionaries in turn, stopping after finding the word +// in one of them. +func (c *Client) Define(dict, word string) ([]*Defn, error) { + id, err := c.text.Cmd("DEFINE %s %q", dict, word) + if err != nil { + return nil, err + } + + c.text.StartResponse(id) + defer c.text.EndResponse(id) + + _, line, err := c.text.ReadCodeLine(150) + if err != nil { + return nil, err + } + a, _ := fields(line) + if len(a) < 1 { + return nil, textproto.ProtocolError("malformed response: " + line) + } + n, err := strconv.Atoi(a[0]) + if err != nil { + return nil, textproto.ProtocolError("invalid definition count: " + a[0]) + } + def := make([]*Defn, n) + for i := 0; i < n; i++ { + _, line, err = c.text.ReadCodeLine(151) + if err != nil { + return nil, err + } + a, _ := fields(line) + if len(a) < 3 { + // skip it, to keep protocol in sync + i-- + n-- + def = def[0:n] + continue + } + d := &Defn{Word: a[0], Dict: Dict{a[1], a[2]}} + d.Text, err = c.text.ReadDotBytes() + if err != nil { + return nil, err + } + def[i] = d + } + _, _, err = c.text.ReadCodeLine(250) + return def, err +} + +// Fields returns the fields in s. +// Fields are space separated unquoted words +// or quoted with single or double quote. +func fields(s string) ([]string, error) { + var v []string + i := 0 + for { + for i < len(s) && (s[i] == ' ' || s[i] == '\t') { + i++ + } + if i >= len(s) { + break + } + if s[i] == '"' || s[i] == '\'' { + q := s[i] + // quoted string + var j int + for j = i + 1; ; j++ { + if j >= len(s) { + return nil, textproto.ProtocolError("malformed quoted string") + } + if s[j] == '\\' { + j++ + continue + } + if s[j] == q { + j++ + break + } + } + v = append(v, unquote(s[i+1:j-1])) + i = j + } else { + // atom + var j int + for j = i; j < len(s); j++ { + if s[j] == ' ' || s[j] == '\t' || s[j] == '\\' || s[j] == '"' || s[j] == '\'' { + break + } + } + v = append(v, s[i:j]) + i = j + } + if i < len(s) { + c := s[i] + if c != ' ' && c != '\t' { + return nil, textproto.ProtocolError("quotes not on word boundaries") + } + } + } + return v, nil +} + +func unquote(s string) string { + if strings.Index(s, "\\") < 0 { + return s + } + b := []byte(s) + w := 0 + for r := 0; r < len(b); r++ { + c := b[r] + if c == '\\' { + r++ + c = b[r] + } + b[w] = c + w++ + } + return string(b[0:w]) +} diff --git a/src/golang.org/x/net/html/atom/atom.go b/src/golang.org/x/net/html/atom/atom.go new file mode 100644 index 0000000000..cd0a8ac154 --- /dev/null +++ b/src/golang.org/x/net/html/atom/atom.go @@ -0,0 +1,78 @@ +// Copyright 2012 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. + +// Package atom provides integer codes (also known as atoms) for a fixed set of +// frequently occurring HTML strings: tag names and attribute keys such as "p" +// and "id". +// +// Sharing an atom's name between all elements with the same tag can result in +// fewer string allocations when tokenizing and parsing HTML. Integer +// comparisons are also generally faster than string comparisons. +// +// The value of an atom's particular code is not guaranteed to stay the same +// between versions of this package. Neither is any ordering guaranteed: +// whether atom.H1 < atom.H2 may also change. The codes are not guaranteed to +// be dense. The only guarantees are that e.g. looking up "div" will yield +// atom.Div, calling atom.Div.String will return "div", and atom.Div != 0. +package atom // import "golang.org/x/net/html/atom" + +// Atom is an integer code for a string. The zero value maps to "". +type Atom uint32 + +// String returns the atom's name. +func (a Atom) String() string { + start := uint32(a >> 8) + n := uint32(a & 0xff) + if start+n > uint32(len(atomText)) { + return "" + } + return atomText[start : start+n] +} + +func (a Atom) string() string { + return atomText[a>>8 : a>>8+a&0xff] +} + +// fnv computes the FNV hash with an arbitrary starting value h. +func fnv(h uint32, s []byte) uint32 { + for i := range s { + h ^= uint32(s[i]) + h *= 16777619 + } + return h +} + +func match(s string, t []byte) bool { + for i, c := range t { + if s[i] != c { + return false + } + } + return true +} + +// Lookup returns the atom whose name is s. It returns zero if there is no +// such atom. The lookup is case sensitive. +func Lookup(s []byte) Atom { + if len(s) == 0 || len(s) > maxAtomLen { + return 0 + } + h := fnv(hash0, s) + if a := table[h&uint32(len(table)-1)]; int(a&0xff) == len(s) && match(a.string(), s) { + return a + } + if a := table[(h>>16)&uint32(len(table)-1)]; int(a&0xff) == len(s) && match(a.string(), s) { + return a + } + return 0 +} + +// String returns a string whose contents are equal to s. In that sense, it is +// equivalent to string(s) but may be more efficient. +func String(s []byte) string { + if a := Lookup(s); a != 0 { + return a.String() + } + return string(s) +} diff --git a/src/golang.org/x/net/html/atom/atom_test.go b/src/golang.org/x/net/html/atom/atom_test.go new file mode 100644 index 0000000000..6e33704dd5 --- /dev/null +++ b/src/golang.org/x/net/html/atom/atom_test.go @@ -0,0 +1,109 @@ +// Copyright 2012 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. + +package atom + +import ( + "sort" + "testing" +) + +func TestKnown(t *testing.T) { + for _, s := range testAtomList { + if atom := Lookup([]byte(s)); atom.String() != s { + t.Errorf("Lookup(%q) = %#x (%q)", s, uint32(atom), atom.String()) + } + } +} + +func TestHits(t *testing.T) { + for _, a := range table { + if a == 0 { + continue + } + got := Lookup([]byte(a.String())) + if got != a { + t.Errorf("Lookup(%q) = %#x, want %#x", a.String(), uint32(got), uint32(a)) + } + } +} + +func TestMisses(t *testing.T) { + testCases := []string{ + "", + "\x00", + "\xff", + "A", + "DIV", + "Div", + "dIV", + "aa", + "a\x00", + "ab", + "abb", + "abbr0", + "abbr ", + " abbr", + " a", + "acceptcharset", + "acceptCharset", + "accept_charset", + "h0", + "h1h2", + "h7", + "onClick", + "λ", + // The following string has the same hash (0xa1d7fab7) as "onmouseover". + "\x00\x00\x00\x00\x00\x50\x18\xae\x38\xd0\xb7", + } + for _, tc := range testCases { + got := Lookup([]byte(tc)) + if got != 0 { + t.Errorf("Lookup(%q): got %d, want 0", tc, got) + } + } +} + +func TestForeignObject(t *testing.T) { + const ( + afo = Foreignobject + afO = ForeignObject + sfo = "foreignobject" + sfO = "foreignObject" + ) + if got := Lookup([]byte(sfo)); got != afo { + t.Errorf("Lookup(%q): got %#v, want %#v", sfo, got, afo) + } + if got := Lookup([]byte(sfO)); got != afO { + t.Errorf("Lookup(%q): got %#v, want %#v", sfO, got, afO) + } + if got := afo.String(); got != sfo { + t.Errorf("Atom(%#v).String(): got %q, want %q", afo, got, sfo) + } + if got := afO.String(); got != sfO { + t.Errorf("Atom(%#v).String(): got %q, want %q", afO, got, sfO) + } +} + +func BenchmarkLookup(b *testing.B) { + sortedTable := make([]string, 0, len(table)) + for _, a := range table { + if a != 0 { + sortedTable = append(sortedTable, a.String()) + } + } + sort.Strings(sortedTable) + + x := make([][]byte, 1000) + for i := range x { + x[i] = []byte(sortedTable[i%len(sortedTable)]) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, s := range x { + Lookup(s) + } + } +} diff --git a/src/golang.org/x/net/html/atom/gen.go b/src/golang.org/x/net/html/atom/gen.go new file mode 100644 index 0000000000..6bfa866019 --- /dev/null +++ b/src/golang.org/x/net/html/atom/gen.go @@ -0,0 +1,648 @@ +// Copyright 2012 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 ignore + +package main + +// This program generates table.go and table_test.go. +// Invoke as +// +// go run gen.go |gofmt >table.go +// go run gen.go -test |gofmt >table_test.go + +import ( + "flag" + "fmt" + "math/rand" + "os" + "sort" + "strings" +) + +// identifier converts s to a Go exported identifier. +// It converts "div" to "Div" and "accept-charset" to "AcceptCharset". +func identifier(s string) string { + b := make([]byte, 0, len(s)) + cap := true + for _, c := range s { + if c == '-' { + cap = true + continue + } + if cap && 'a' <= c && c <= 'z' { + c -= 'a' - 'A' + } + cap = false + b = append(b, byte(c)) + } + return string(b) +} + +var test = flag.Bool("test", false, "generate table_test.go") + +func main() { + flag.Parse() + + var all []string + all = append(all, elements...) + all = append(all, attributes...) + all = append(all, eventHandlers...) + all = append(all, extra...) + sort.Strings(all) + + if *test { + fmt.Printf("// generated by go run gen.go -test; DO NOT EDIT\n\n") + fmt.Printf("package atom\n\n") + fmt.Printf("var testAtomList = []string{\n") + for _, s := range all { + fmt.Printf("\t%q,\n", s) + } + fmt.Printf("}\n") + return + } + + // uniq - lists have dups + // compute max len too + maxLen := 0 + w := 0 + for _, s := range all { + if w == 0 || all[w-1] != s { + if maxLen < len(s) { + maxLen = len(s) + } + all[w] = s + w++ + } + } + all = all[:w] + + // Find hash that minimizes table size. + var best *table + for i := 0; i < 1000000; i++ { + if best != nil && 1<<(best.k-1) < len(all) { + break + } + h := rand.Uint32() + for k := uint(0); k <= 16; k++ { + if best != nil && k >= best.k { + break + } + var t table + if t.init(h, k, all) { + best = &t + break + } + } + } + if best == nil { + fmt.Fprintf(os.Stderr, "failed to construct string table\n") + os.Exit(1) + } + + // Lay out strings, using overlaps when possible. + layout := append([]string{}, all...) + + // Remove strings that are substrings of other strings + for changed := true; changed; { + changed = false + for i, s := range layout { + if s == "" { + continue + } + for j, t := range layout { + if i != j && t != "" && strings.Contains(s, t) { + changed = true + layout[j] = "" + } + } + } + } + + // Join strings where one suffix matches another prefix. + for { + // Find best i, j, k such that layout[i][len-k:] == layout[j][:k], + // maximizing overlap length k. + besti := -1 + bestj := -1 + bestk := 0 + for i, s := range layout { + if s == "" { + continue + } + for j, t := range layout { + if i == j { + continue + } + for k := bestk + 1; k <= len(s) && k <= len(t); k++ { + if s[len(s)-k:] == t[:k] { + besti = i + bestj = j + bestk = k + } + } + } + } + if bestk > 0 { + layout[besti] += layout[bestj][bestk:] + layout[bestj] = "" + continue + } + break + } + + text := strings.Join(layout, "") + + atom := map[string]uint32{} + for _, s := range all { + off := strings.Index(text, s) + if off < 0 { + panic("lost string " + s) + } + atom[s] = uint32(off<<8 | len(s)) + } + + // Generate the Go code. + fmt.Printf("// generated by go run gen.go; DO NOT EDIT\n\n") + fmt.Printf("package atom\n\nconst (\n") + for _, s := range all { + fmt.Printf("\t%s Atom = %#x\n", identifier(s), atom[s]) + } + fmt.Printf(")\n\n") + + fmt.Printf("const hash0 = %#x\n\n", best.h0) + fmt.Printf("const maxAtomLen = %d\n\n", maxLen) + + fmt.Printf("var table = [1<<%d]Atom{\n", best.k) + for i, s := range best.tab { + if s == "" { + continue + } + fmt.Printf("\t%#x: %#x, // %s\n", i, atom[s], s) + } + fmt.Printf("}\n") + datasize := (1 << best.k) * 4 + + fmt.Printf("const atomText =\n") + textsize := len(text) + for len(text) > 60 { + fmt.Printf("\t%q +\n", text[:60]) + text = text[60:] + } + fmt.Printf("\t%q\n\n", text) + + fmt.Fprintf(os.Stderr, "%d atoms; %d string bytes + %d tables = %d total data\n", len(all), textsize, datasize, textsize+datasize) +} + +type byLen []string + +func (x byLen) Less(i, j int) bool { return len(x[i]) > len(x[j]) } +func (x byLen) Swap(i, j int) { x[i], x[j] = x[j], x[i] } +func (x byLen) Len() int { return len(x) } + +// fnv computes the FNV hash with an arbitrary starting value h. +func fnv(h uint32, s string) uint32 { + for i := 0; i < len(s); i++ { + h ^= uint32(s[i]) + h *= 16777619 + } + return h +} + +// A table represents an attempt at constructing the lookup table. +// The lookup table uses cuckoo hashing, meaning that each string +// can be found in one of two positions. +type table struct { + h0 uint32 + k uint + mask uint32 + tab []string +} + +// hash returns the two hashes for s. +func (t *table) hash(s string) (h1, h2 uint32) { + h := fnv(t.h0, s) + h1 = h & t.mask + h2 = (h >> 16) & t.mask + return +} + +// init initializes the table with the given parameters. +// h0 is the initial hash value, +// k is the number of bits of hash value to use, and +// x is the list of strings to store in the table. +// init returns false if the table cannot be constructed. +func (t *table) init(h0 uint32, k uint, x []string) bool { + t.h0 = h0 + t.k = k + t.tab = make([]string, 1< len(t.tab) { + return false + } + s := t.tab[i] + h1, h2 := t.hash(s) + j := h1 + h2 - i + if t.tab[j] != "" && !t.push(j, depth+1) { + return false + } + t.tab[j] = s + return true +} + +// The lists of element names and attribute keys were taken from +// https://html.spec.whatwg.org/multipage/indices.html#index +// as of the "HTML Living Standard - Last Updated 21 February 2015" version. + +var elements = []string{ + "a", + "abbr", + "address", + "area", + "article", + "aside", + "audio", + "b", + "base", + "bdi", + "bdo", + "blockquote", + "body", + "br", + "button", + "canvas", + "caption", + "cite", + "code", + "col", + "colgroup", + "command", + "data", + "datalist", + "dd", + "del", + "details", + "dfn", + "dialog", + "div", + "dl", + "dt", + "em", + "embed", + "fieldset", + "figcaption", + "figure", + "footer", + "form", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "head", + "header", + "hgroup", + "hr", + "html", + "i", + "iframe", + "img", + "input", + "ins", + "kbd", + "keygen", + "label", + "legend", + "li", + "link", + "map", + "mark", + "menu", + "menuitem", + "meta", + "meter", + "nav", + "noscript", + "object", + "ol", + "optgroup", + "option", + "output", + "p", + "param", + "pre", + "progress", + "q", + "rp", + "rt", + "ruby", + "s", + "samp", + "script", + "section", + "select", + "small", + "source", + "span", + "strong", + "style", + "sub", + "summary", + "sup", + "table", + "tbody", + "td", + "template", + "textarea", + "tfoot", + "th", + "thead", + "time", + "title", + "tr", + "track", + "u", + "ul", + "var", + "video", + "wbr", +} + +// https://html.spec.whatwg.org/multipage/indices.html#attributes-3 + +var attributes = []string{ + "abbr", + "accept", + "accept-charset", + "accesskey", + "action", + "alt", + "async", + "autocomplete", + "autofocus", + "autoplay", + "challenge", + "charset", + "checked", + "cite", + "class", + "cols", + "colspan", + "command", + "content", + "contenteditable", + "contextmenu", + "controls", + "coords", + "crossorigin", + "data", + "datetime", + "default", + "defer", + "dir", + "dirname", + "disabled", + "download", + "draggable", + "dropzone", + "enctype", + "for", + "form", + "formaction", + "formenctype", + "formmethod", + "formnovalidate", + "formtarget", + "headers", + "height", + "hidden", + "high", + "href", + "hreflang", + "http-equiv", + "icon", + "id", + "inputmode", + "ismap", + "itemid", + "itemprop", + "itemref", + "itemscope", + "itemtype", + "keytype", + "kind", + "label", + "lang", + "list", + "loop", + "low", + "manifest", + "max", + "maxlength", + "media", + "mediagroup", + "method", + "min", + "minlength", + "multiple", + "muted", + "name", + "novalidate", + "open", + "optimum", + "pattern", + "ping", + "placeholder", + "poster", + "preload", + "radiogroup", + "readonly", + "rel", + "required", + "reversed", + "rows", + "rowspan", + "sandbox", + "spellcheck", + "scope", + "scoped", + "seamless", + "selected", + "shape", + "size", + "sizes", + "sortable", + "sorted", + "span", + "src", + "srcdoc", + "srclang", + "start", + "step", + "style", + "tabindex", + "target", + "title", + "translate", + "type", + "typemustmatch", + "usemap", + "value", + "width", + "wrap", +} + +var eventHandlers = []string{ + "onabort", + "onautocomplete", + "onautocompleteerror", + "onafterprint", + "onbeforeprint", + "onbeforeunload", + "onblur", + "oncancel", + "oncanplay", + "oncanplaythrough", + "onchange", + "onclick", + "onclose", + "oncontextmenu", + "oncuechange", + "ondblclick", + "ondrag", + "ondragend", + "ondragenter", + "ondragleave", + "ondragover", + "ondragstart", + "ondrop", + "ondurationchange", + "onemptied", + "onended", + "onerror", + "onfocus", + "onhashchange", + "oninput", + "oninvalid", + "onkeydown", + "onkeypress", + "onkeyup", + "onlanguagechange", + "onload", + "onloadeddata", + "onloadedmetadata", + "onloadstart", + "onmessage", + "onmousedown", + "onmousemove", + "onmouseout", + "onmouseover", + "onmouseup", + "onmousewheel", + "onoffline", + "ononline", + "onpagehide", + "onpageshow", + "onpause", + "onplay", + "onplaying", + "onpopstate", + "onprogress", + "onratechange", + "onreset", + "onresize", + "onscroll", + "onseeked", + "onseeking", + "onselect", + "onshow", + "onsort", + "onstalled", + "onstorage", + "onsubmit", + "onsuspend", + "ontimeupdate", + "ontoggle", + "onunload", + "onvolumechange", + "onwaiting", +} + +// extra are ad-hoc values not covered by any of the lists above. +var extra = []string{ + "align", + "annotation", + "annotation-xml", + "applet", + "basefont", + "bgsound", + "big", + "blink", + "center", + "color", + "desc", + "face", + "font", + "foreignObject", // HTML is case-insensitive, but SVG-embedded-in-HTML is case-sensitive. + "foreignobject", + "frame", + "frameset", + "image", + "isindex", + "listing", + "malignmark", + "marquee", + "math", + "mglyph", + "mi", + "mn", + "mo", + "ms", + "mtext", + "nobr", + "noembed", + "noframes", + "plaintext", + "prompt", + "public", + "spacer", + "strike", + "svg", + "system", + "tt", + "xmp", +} diff --git a/src/golang.org/x/net/html/atom/table.go b/src/golang.org/x/net/html/atom/table.go new file mode 100644 index 0000000000..2605ba3102 --- /dev/null +++ b/src/golang.org/x/net/html/atom/table.go @@ -0,0 +1,713 @@ +// generated by go run gen.go; DO NOT EDIT + +package atom + +const ( + A Atom = 0x1 + Abbr Atom = 0x4 + Accept Atom = 0x2106 + AcceptCharset Atom = 0x210e + Accesskey Atom = 0x3309 + Action Atom = 0x1f606 + Address Atom = 0x4f307 + Align Atom = 0x1105 + Alt Atom = 0x4503 + Annotation Atom = 0x1670a + AnnotationXml Atom = 0x1670e + Applet Atom = 0x2b306 + Area Atom = 0x2fa04 + Article Atom = 0x38807 + Aside Atom = 0x8305 + Async Atom = 0x7b05 + Audio Atom = 0xa605 + Autocomplete Atom = 0x1fc0c + Autofocus Atom = 0xb309 + Autoplay Atom = 0xce08 + B Atom = 0x101 + Base Atom = 0xd604 + Basefont Atom = 0xd608 + Bdi Atom = 0x1a03 + Bdo Atom = 0xe703 + Bgsound Atom = 0x11807 + Big Atom = 0x12403 + Blink Atom = 0x12705 + Blockquote Atom = 0x12c0a + Body Atom = 0x2f04 + Br Atom = 0x202 + Button Atom = 0x13606 + Canvas Atom = 0x7f06 + Caption Atom = 0x1bb07 + Center Atom = 0x5b506 + Challenge Atom = 0x21f09 + Charset Atom = 0x2807 + Checked Atom = 0x32807 + Cite Atom = 0x3c804 + Class Atom = 0x4de05 + Code Atom = 0x14904 + Col Atom = 0x15003 + Colgroup Atom = 0x15008 + Color Atom = 0x15d05 + Cols Atom = 0x16204 + Colspan Atom = 0x16207 + Command Atom = 0x17507 + Content Atom = 0x42307 + Contenteditable Atom = 0x4230f + Contextmenu Atom = 0x3310b + Controls Atom = 0x18808 + Coords Atom = 0x19406 + Crossorigin Atom = 0x19f0b + Data Atom = 0x44a04 + Datalist Atom = 0x44a08 + Datetime Atom = 0x23c08 + Dd Atom = 0x26702 + Default Atom = 0x8607 + Defer Atom = 0x14b05 + Del Atom = 0x3ef03 + Desc Atom = 0x4db04 + Details Atom = 0x4807 + Dfn Atom = 0x6103 + Dialog Atom = 0x1b06 + Dir Atom = 0x6903 + Dirname Atom = 0x6907 + Disabled Atom = 0x10c08 + Div Atom = 0x11303 + Dl Atom = 0x11e02 + Download Atom = 0x40008 + Draggable Atom = 0x17b09 + Dropzone Atom = 0x39108 + Dt Atom = 0x50902 + Em Atom = 0x6502 + Embed Atom = 0x6505 + Enctype Atom = 0x21107 + Face Atom = 0x5b304 + Fieldset Atom = 0x1b008 + Figcaption Atom = 0x1b80a + Figure Atom = 0x1cc06 + Font Atom = 0xda04 + Footer Atom = 0x8d06 + For Atom = 0x1d803 + ForeignObject Atom = 0x1d80d + Foreignobject Atom = 0x1e50d + Form Atom = 0x1f204 + Formaction Atom = 0x1f20a + Formenctype Atom = 0x20d0b + Formmethod Atom = 0x2280a + Formnovalidate Atom = 0x2320e + Formtarget Atom = 0x2470a + Frame Atom = 0x9a05 + Frameset Atom = 0x9a08 + H1 Atom = 0x26e02 + H2 Atom = 0x29402 + H3 Atom = 0x2a702 + H4 Atom = 0x2e902 + H5 Atom = 0x2f302 + H6 Atom = 0x50b02 + Head Atom = 0x2d504 + Header Atom = 0x2d506 + Headers Atom = 0x2d507 + Height Atom = 0x25106 + Hgroup Atom = 0x25906 + Hidden Atom = 0x26506 + High Atom = 0x26b04 + Hr Atom = 0x27002 + Href Atom = 0x27004 + Hreflang Atom = 0x27008 + Html Atom = 0x25504 + HttpEquiv Atom = 0x2780a + I Atom = 0x601 + Icon Atom = 0x42204 + Id Atom = 0x8502 + Iframe Atom = 0x29606 + Image Atom = 0x29c05 + Img Atom = 0x2a103 + Input Atom = 0x3e805 + Inputmode Atom = 0x3e809 + Ins Atom = 0x1a803 + Isindex Atom = 0x2a907 + Ismap Atom = 0x2b005 + Itemid Atom = 0x33c06 + Itemprop Atom = 0x3c908 + Itemref Atom = 0x5ad07 + Itemscope Atom = 0x2b909 + Itemtype Atom = 0x2c308 + Kbd Atom = 0x1903 + Keygen Atom = 0x3906 + Keytype Atom = 0x53707 + Kind Atom = 0x10904 + Label Atom = 0xf005 + Lang Atom = 0x27404 + Legend Atom = 0x18206 + Li Atom = 0x1202 + Link Atom = 0x12804 + List Atom = 0x44e04 + Listing Atom = 0x44e07 + Loop Atom = 0xf404 + Low Atom = 0x11f03 + Malignmark Atom = 0x100a + Manifest Atom = 0x5f108 + Map Atom = 0x2b203 + Mark Atom = 0x1604 + Marquee Atom = 0x2cb07 + Math Atom = 0x2d204 + Max Atom = 0x2e103 + Maxlength Atom = 0x2e109 + Media Atom = 0x6e05 + Mediagroup Atom = 0x6e0a + Menu Atom = 0x33804 + Menuitem Atom = 0x33808 + Meta Atom = 0x45d04 + Meter Atom = 0x24205 + Method Atom = 0x22c06 + Mglyph Atom = 0x2a206 + Mi Atom = 0x2eb02 + Min Atom = 0x2eb03 + Minlength Atom = 0x2eb09 + Mn Atom = 0x23502 + Mo Atom = 0x3ed02 + Ms Atom = 0x2bc02 + Mtext Atom = 0x2f505 + Multiple Atom = 0x30308 + Muted Atom = 0x30b05 + Name Atom = 0x6c04 + Nav Atom = 0x3e03 + Nobr Atom = 0x5704 + Noembed Atom = 0x6307 + Noframes Atom = 0x9808 + Noscript Atom = 0x3d208 + Novalidate Atom = 0x2360a + Object Atom = 0x1ec06 + Ol Atom = 0xc902 + Onabort Atom = 0x13a07 + Onafterprint Atom = 0x1c00c + Onautocomplete Atom = 0x1fa0e + Onautocompleteerror Atom = 0x1fa13 + Onbeforeprint Atom = 0x6040d + Onbeforeunload Atom = 0x4e70e + Onblur Atom = 0xaa06 + Oncancel Atom = 0xe908 + Oncanplay Atom = 0x28509 + Oncanplaythrough Atom = 0x28510 + Onchange Atom = 0x3a708 + Onclick Atom = 0x31007 + Onclose Atom = 0x31707 + Oncontextmenu Atom = 0x32f0d + Oncuechange Atom = 0x3420b + Ondblclick Atom = 0x34d0a + Ondrag Atom = 0x35706 + Ondragend Atom = 0x35709 + Ondragenter Atom = 0x3600b + Ondragleave Atom = 0x36b0b + Ondragover Atom = 0x3760a + Ondragstart Atom = 0x3800b + Ondrop Atom = 0x38f06 + Ondurationchange Atom = 0x39f10 + Onemptied Atom = 0x39609 + Onended Atom = 0x3af07 + Onerror Atom = 0x3b607 + Onfocus Atom = 0x3bd07 + Onhashchange Atom = 0x3da0c + Oninput Atom = 0x3e607 + Oninvalid Atom = 0x3f209 + Onkeydown Atom = 0x3fb09 + Onkeypress Atom = 0x4080a + Onkeyup Atom = 0x41807 + Onlanguagechange Atom = 0x43210 + Onload Atom = 0x44206 + Onloadeddata Atom = 0x4420c + Onloadedmetadata Atom = 0x45510 + Onloadstart Atom = 0x46b0b + Onmessage Atom = 0x47609 + Onmousedown Atom = 0x47f0b + Onmousemove Atom = 0x48a0b + Onmouseout Atom = 0x4950a + Onmouseover Atom = 0x4a20b + Onmouseup Atom = 0x4ad09 + Onmousewheel Atom = 0x4b60c + Onoffline Atom = 0x4c209 + Ononline Atom = 0x4cb08 + Onpagehide Atom = 0x4d30a + Onpageshow Atom = 0x4fe0a + Onpause Atom = 0x50d07 + Onplay Atom = 0x51706 + Onplaying Atom = 0x51709 + Onpopstate Atom = 0x5200a + Onprogress Atom = 0x52a0a + Onratechange Atom = 0x53e0c + Onreset Atom = 0x54a07 + Onresize Atom = 0x55108 + Onscroll Atom = 0x55f08 + Onseeked Atom = 0x56708 + Onseeking Atom = 0x56f09 + Onselect Atom = 0x57808 + Onshow Atom = 0x58206 + Onsort Atom = 0x58b06 + Onstalled Atom = 0x59509 + Onstorage Atom = 0x59e09 + Onsubmit Atom = 0x5a708 + Onsuspend Atom = 0x5bb09 + Ontimeupdate Atom = 0xdb0c + Ontoggle Atom = 0x5c408 + Onunload Atom = 0x5cc08 + Onvolumechange Atom = 0x5d40e + Onwaiting Atom = 0x5e209 + Open Atom = 0x3cf04 + Optgroup Atom = 0xf608 + Optimum Atom = 0x5eb07 + Option Atom = 0x60006 + Output Atom = 0x49c06 + P Atom = 0xc01 + Param Atom = 0xc05 + Pattern Atom = 0x5107 + Ping Atom = 0x7704 + Placeholder Atom = 0xc30b + Plaintext Atom = 0xfd09 + Poster Atom = 0x15706 + Pre Atom = 0x25e03 + Preload Atom = 0x25e07 + Progress Atom = 0x52c08 + Prompt Atom = 0x5fa06 + Public Atom = 0x41e06 + Q Atom = 0x13101 + Radiogroup Atom = 0x30a + Readonly Atom = 0x2fb08 + Rel Atom = 0x25f03 + Required Atom = 0x1d008 + Reversed Atom = 0x5a08 + Rows Atom = 0x9204 + Rowspan Atom = 0x9207 + Rp Atom = 0x1c602 + Rt Atom = 0x13f02 + Ruby Atom = 0xaf04 + S Atom = 0x2c01 + Samp Atom = 0x4e04 + Sandbox Atom = 0xbb07 + Scope Atom = 0x2bd05 + Scoped Atom = 0x2bd06 + Script Atom = 0x3d406 + Seamless Atom = 0x31c08 + Section Atom = 0x4e207 + Select Atom = 0x57a06 + Selected Atom = 0x57a08 + Shape Atom = 0x4f905 + Size Atom = 0x55504 + Sizes Atom = 0x55505 + Small Atom = 0x18f05 + Sortable Atom = 0x58d08 + Sorted Atom = 0x19906 + Source Atom = 0x1aa06 + Spacer Atom = 0x2db06 + Span Atom = 0x9504 + Spellcheck Atom = 0x3230a + Src Atom = 0x3c303 + Srcdoc Atom = 0x3c306 + Srclang Atom = 0x41107 + Start Atom = 0x38605 + Step Atom = 0x5f704 + Strike Atom = 0x53306 + Strong Atom = 0x55906 + Style Atom = 0x61105 + Sub Atom = 0x5a903 + Summary Atom = 0x61607 + Sup Atom = 0x61d03 + Svg Atom = 0x62003 + System Atom = 0x62306 + Tabindex Atom = 0x46308 + Table Atom = 0x42d05 + Target Atom = 0x24b06 + Tbody Atom = 0x2e05 + Td Atom = 0x4702 + Template Atom = 0x62608 + Textarea Atom = 0x2f608 + Tfoot Atom = 0x8c05 + Th Atom = 0x22e02 + Thead Atom = 0x2d405 + Time Atom = 0xdd04 + Title Atom = 0xa105 + Tr Atom = 0x10502 + Track Atom = 0x10505 + Translate Atom = 0x14009 + Tt Atom = 0x5302 + Type Atom = 0x21404 + Typemustmatch Atom = 0x2140d + U Atom = 0xb01 + Ul Atom = 0x8a02 + Usemap Atom = 0x51106 + Value Atom = 0x4005 + Var Atom = 0x11503 + Video Atom = 0x28105 + Wbr Atom = 0x12103 + Width Atom = 0x50705 + Wrap Atom = 0x58704 + Xmp Atom = 0xc103 +) + +const hash0 = 0xc17da63e + +const maxAtomLen = 19 + +var table = [1 << 9]Atom{ + 0x1: 0x48a0b, // onmousemove + 0x2: 0x5e209, // onwaiting + 0x3: 0x1fa13, // onautocompleteerror + 0x4: 0x5fa06, // prompt + 0x7: 0x5eb07, // optimum + 0x8: 0x1604, // mark + 0xa: 0x5ad07, // itemref + 0xb: 0x4fe0a, // onpageshow + 0xc: 0x57a06, // select + 0xd: 0x17b09, // draggable + 0xe: 0x3e03, // nav + 0xf: 0x17507, // command + 0x11: 0xb01, // u + 0x14: 0x2d507, // headers + 0x15: 0x44a08, // datalist + 0x17: 0x4e04, // samp + 0x1a: 0x3fb09, // onkeydown + 0x1b: 0x55f08, // onscroll + 0x1c: 0x15003, // col + 0x20: 0x3c908, // itemprop + 0x21: 0x2780a, // http-equiv + 0x22: 0x61d03, // sup + 0x24: 0x1d008, // required + 0x2b: 0x25e07, // preload + 0x2c: 0x6040d, // onbeforeprint + 0x2d: 0x3600b, // ondragenter + 0x2e: 0x50902, // dt + 0x2f: 0x5a708, // onsubmit + 0x30: 0x27002, // hr + 0x31: 0x32f0d, // oncontextmenu + 0x33: 0x29c05, // image + 0x34: 0x50d07, // onpause + 0x35: 0x25906, // hgroup + 0x36: 0x7704, // ping + 0x37: 0x57808, // onselect + 0x3a: 0x11303, // div + 0x3b: 0x1fa0e, // onautocomplete + 0x40: 0x2eb02, // mi + 0x41: 0x31c08, // seamless + 0x42: 0x2807, // charset + 0x43: 0x8502, // id + 0x44: 0x5200a, // onpopstate + 0x45: 0x3ef03, // del + 0x46: 0x2cb07, // marquee + 0x47: 0x3309, // accesskey + 0x49: 0x8d06, // footer + 0x4a: 0x44e04, // list + 0x4b: 0x2b005, // ismap + 0x51: 0x33804, // menu + 0x52: 0x2f04, // body + 0x55: 0x9a08, // frameset + 0x56: 0x54a07, // onreset + 0x57: 0x12705, // blink + 0x58: 0xa105, // title + 0x59: 0x38807, // article + 0x5b: 0x22e02, // th + 0x5d: 0x13101, // q + 0x5e: 0x3cf04, // open + 0x5f: 0x2fa04, // area + 0x61: 0x44206, // onload + 0x62: 0xda04, // font + 0x63: 0xd604, // base + 0x64: 0x16207, // colspan + 0x65: 0x53707, // keytype + 0x66: 0x11e02, // dl + 0x68: 0x1b008, // fieldset + 0x6a: 0x2eb03, // min + 0x6b: 0x11503, // var + 0x6f: 0x2d506, // header + 0x70: 0x13f02, // rt + 0x71: 0x15008, // colgroup + 0x72: 0x23502, // mn + 0x74: 0x13a07, // onabort + 0x75: 0x3906, // keygen + 0x76: 0x4c209, // onoffline + 0x77: 0x21f09, // challenge + 0x78: 0x2b203, // map + 0x7a: 0x2e902, // h4 + 0x7b: 0x3b607, // onerror + 0x7c: 0x2e109, // maxlength + 0x7d: 0x2f505, // mtext + 0x7e: 0xbb07, // sandbox + 0x7f: 0x58b06, // onsort + 0x80: 0x100a, // malignmark + 0x81: 0x45d04, // meta + 0x82: 0x7b05, // async + 0x83: 0x2a702, // h3 + 0x84: 0x26702, // dd + 0x85: 0x27004, // href + 0x86: 0x6e0a, // mediagroup + 0x87: 0x19406, // coords + 0x88: 0x41107, // srclang + 0x89: 0x34d0a, // ondblclick + 0x8a: 0x4005, // value + 0x8c: 0xe908, // oncancel + 0x8e: 0x3230a, // spellcheck + 0x8f: 0x9a05, // frame + 0x91: 0x12403, // big + 0x94: 0x1f606, // action + 0x95: 0x6903, // dir + 0x97: 0x2fb08, // readonly + 0x99: 0x42d05, // table + 0x9a: 0x61607, // summary + 0x9b: 0x12103, // wbr + 0x9c: 0x30a, // radiogroup + 0x9d: 0x6c04, // name + 0x9f: 0x62306, // system + 0xa1: 0x15d05, // color + 0xa2: 0x7f06, // canvas + 0xa3: 0x25504, // html + 0xa5: 0x56f09, // onseeking + 0xac: 0x4f905, // shape + 0xad: 0x25f03, // rel + 0xae: 0x28510, // oncanplaythrough + 0xaf: 0x3760a, // ondragover + 0xb0: 0x62608, // template + 0xb1: 0x1d80d, // foreignObject + 0xb3: 0x9204, // rows + 0xb6: 0x44e07, // listing + 0xb7: 0x49c06, // output + 0xb9: 0x3310b, // contextmenu + 0xbb: 0x11f03, // low + 0xbc: 0x1c602, // rp + 0xbd: 0x5bb09, // onsuspend + 0xbe: 0x13606, // button + 0xbf: 0x4db04, // desc + 0xc1: 0x4e207, // section + 0xc2: 0x52a0a, // onprogress + 0xc3: 0x59e09, // onstorage + 0xc4: 0x2d204, // math + 0xc5: 0x4503, // alt + 0xc7: 0x8a02, // ul + 0xc8: 0x5107, // pattern + 0xc9: 0x4b60c, // onmousewheel + 0xca: 0x35709, // ondragend + 0xcb: 0xaf04, // ruby + 0xcc: 0xc01, // p + 0xcd: 0x31707, // onclose + 0xce: 0x24205, // meter + 0xcf: 0x11807, // bgsound + 0xd2: 0x25106, // height + 0xd4: 0x101, // b + 0xd5: 0x2c308, // itemtype + 0xd8: 0x1bb07, // caption + 0xd9: 0x10c08, // disabled + 0xdb: 0x33808, // menuitem + 0xdc: 0x62003, // svg + 0xdd: 0x18f05, // small + 0xde: 0x44a04, // data + 0xe0: 0x4cb08, // ononline + 0xe1: 0x2a206, // mglyph + 0xe3: 0x6505, // embed + 0xe4: 0x10502, // tr + 0xe5: 0x46b0b, // onloadstart + 0xe7: 0x3c306, // srcdoc + 0xeb: 0x5c408, // ontoggle + 0xed: 0xe703, // bdo + 0xee: 0x4702, // td + 0xef: 0x8305, // aside + 0xf0: 0x29402, // h2 + 0xf1: 0x52c08, // progress + 0xf2: 0x12c0a, // blockquote + 0xf4: 0xf005, // label + 0xf5: 0x601, // i + 0xf7: 0x9207, // rowspan + 0xfb: 0x51709, // onplaying + 0xfd: 0x2a103, // img + 0xfe: 0xf608, // optgroup + 0xff: 0x42307, // content + 0x101: 0x53e0c, // onratechange + 0x103: 0x3da0c, // onhashchange + 0x104: 0x4807, // details + 0x106: 0x40008, // download + 0x109: 0x14009, // translate + 0x10b: 0x4230f, // contenteditable + 0x10d: 0x36b0b, // ondragleave + 0x10e: 0x2106, // accept + 0x10f: 0x57a08, // selected + 0x112: 0x1f20a, // formaction + 0x113: 0x5b506, // center + 0x115: 0x45510, // onloadedmetadata + 0x116: 0x12804, // link + 0x117: 0xdd04, // time + 0x118: 0x19f0b, // crossorigin + 0x119: 0x3bd07, // onfocus + 0x11a: 0x58704, // wrap + 0x11b: 0x42204, // icon + 0x11d: 0x28105, // video + 0x11e: 0x4de05, // class + 0x121: 0x5d40e, // onvolumechange + 0x122: 0xaa06, // onblur + 0x123: 0x2b909, // itemscope + 0x124: 0x61105, // style + 0x127: 0x41e06, // public + 0x129: 0x2320e, // formnovalidate + 0x12a: 0x58206, // onshow + 0x12c: 0x51706, // onplay + 0x12d: 0x3c804, // cite + 0x12e: 0x2bc02, // ms + 0x12f: 0xdb0c, // ontimeupdate + 0x130: 0x10904, // kind + 0x131: 0x2470a, // formtarget + 0x135: 0x3af07, // onended + 0x136: 0x26506, // hidden + 0x137: 0x2c01, // s + 0x139: 0x2280a, // formmethod + 0x13a: 0x3e805, // input + 0x13c: 0x50b02, // h6 + 0x13d: 0xc902, // ol + 0x13e: 0x3420b, // oncuechange + 0x13f: 0x1e50d, // foreignobject + 0x143: 0x4e70e, // onbeforeunload + 0x144: 0x2bd05, // scope + 0x145: 0x39609, // onemptied + 0x146: 0x14b05, // defer + 0x147: 0xc103, // xmp + 0x148: 0x39f10, // ondurationchange + 0x149: 0x1903, // kbd + 0x14c: 0x47609, // onmessage + 0x14d: 0x60006, // option + 0x14e: 0x2eb09, // minlength + 0x14f: 0x32807, // checked + 0x150: 0xce08, // autoplay + 0x152: 0x202, // br + 0x153: 0x2360a, // novalidate + 0x156: 0x6307, // noembed + 0x159: 0x31007, // onclick + 0x15a: 0x47f0b, // onmousedown + 0x15b: 0x3a708, // onchange + 0x15e: 0x3f209, // oninvalid + 0x15f: 0x2bd06, // scoped + 0x160: 0x18808, // controls + 0x161: 0x30b05, // muted + 0x162: 0x58d08, // sortable + 0x163: 0x51106, // usemap + 0x164: 0x1b80a, // figcaption + 0x165: 0x35706, // ondrag + 0x166: 0x26b04, // high + 0x168: 0x3c303, // src + 0x169: 0x15706, // poster + 0x16b: 0x1670e, // annotation-xml + 0x16c: 0x5f704, // step + 0x16d: 0x4, // abbr + 0x16e: 0x1b06, // dialog + 0x170: 0x1202, // li + 0x172: 0x3ed02, // mo + 0x175: 0x1d803, // for + 0x176: 0x1a803, // ins + 0x178: 0x55504, // size + 0x179: 0x43210, // onlanguagechange + 0x17a: 0x8607, // default + 0x17b: 0x1a03, // bdi + 0x17c: 0x4d30a, // onpagehide + 0x17d: 0x6907, // dirname + 0x17e: 0x21404, // type + 0x17f: 0x1f204, // form + 0x181: 0x28509, // oncanplay + 0x182: 0x6103, // dfn + 0x183: 0x46308, // tabindex + 0x186: 0x6502, // em + 0x187: 0x27404, // lang + 0x189: 0x39108, // dropzone + 0x18a: 0x4080a, // onkeypress + 0x18b: 0x23c08, // datetime + 0x18c: 0x16204, // cols + 0x18d: 0x1, // a + 0x18e: 0x4420c, // onloadeddata + 0x190: 0xa605, // audio + 0x192: 0x2e05, // tbody + 0x193: 0x22c06, // method + 0x195: 0xf404, // loop + 0x196: 0x29606, // iframe + 0x198: 0x2d504, // head + 0x19e: 0x5f108, // manifest + 0x19f: 0xb309, // autofocus + 0x1a0: 0x14904, // code + 0x1a1: 0x55906, // strong + 0x1a2: 0x30308, // multiple + 0x1a3: 0xc05, // param + 0x1a6: 0x21107, // enctype + 0x1a7: 0x5b304, // face + 0x1a8: 0xfd09, // plaintext + 0x1a9: 0x26e02, // h1 + 0x1aa: 0x59509, // onstalled + 0x1ad: 0x3d406, // script + 0x1ae: 0x2db06, // spacer + 0x1af: 0x55108, // onresize + 0x1b0: 0x4a20b, // onmouseover + 0x1b1: 0x5cc08, // onunload + 0x1b2: 0x56708, // onseeked + 0x1b4: 0x2140d, // typemustmatch + 0x1b5: 0x1cc06, // figure + 0x1b6: 0x4950a, // onmouseout + 0x1b7: 0x25e03, // pre + 0x1b8: 0x50705, // width + 0x1b9: 0x19906, // sorted + 0x1bb: 0x5704, // nobr + 0x1be: 0x5302, // tt + 0x1bf: 0x1105, // align + 0x1c0: 0x3e607, // oninput + 0x1c3: 0x41807, // onkeyup + 0x1c6: 0x1c00c, // onafterprint + 0x1c7: 0x210e, // accept-charset + 0x1c8: 0x33c06, // itemid + 0x1c9: 0x3e809, // inputmode + 0x1cb: 0x53306, // strike + 0x1cc: 0x5a903, // sub + 0x1cd: 0x10505, // track + 0x1ce: 0x38605, // start + 0x1d0: 0xd608, // basefont + 0x1d6: 0x1aa06, // source + 0x1d7: 0x18206, // legend + 0x1d8: 0x2d405, // thead + 0x1da: 0x8c05, // tfoot + 0x1dd: 0x1ec06, // object + 0x1de: 0x6e05, // media + 0x1df: 0x1670a, // annotation + 0x1e0: 0x20d0b, // formenctype + 0x1e2: 0x3d208, // noscript + 0x1e4: 0x55505, // sizes + 0x1e5: 0x1fc0c, // autocomplete + 0x1e6: 0x9504, // span + 0x1e7: 0x9808, // noframes + 0x1e8: 0x24b06, // target + 0x1e9: 0x38f06, // ondrop + 0x1ea: 0x2b306, // applet + 0x1ec: 0x5a08, // reversed + 0x1f0: 0x2a907, // isindex + 0x1f3: 0x27008, // hreflang + 0x1f5: 0x2f302, // h5 + 0x1f6: 0x4f307, // address + 0x1fa: 0x2e103, // max + 0x1fb: 0xc30b, // placeholder + 0x1fc: 0x2f608, // textarea + 0x1fe: 0x4ad09, // onmouseup + 0x1ff: 0x3800b, // ondragstart +} + +const atomText = "abbradiogrouparamalignmarkbdialogaccept-charsetbodyaccesskey" + + "genavaluealtdetailsampatternobreversedfnoembedirnamediagroup" + + "ingasyncanvasidefaultfooterowspanoframesetitleaudionblurubya" + + "utofocusandboxmplaceholderautoplaybasefontimeupdatebdoncance" + + "labelooptgrouplaintextrackindisabledivarbgsoundlowbrbigblink" + + "blockquotebuttonabortranslatecodefercolgroupostercolorcolspa" + + "nnotation-xmlcommandraggablegendcontrolsmallcoordsortedcross" + + "originsourcefieldsetfigcaptionafterprintfigurequiredforeignO" + + "bjectforeignobjectformactionautocompleteerrorformenctypemust" + + "matchallengeformmethodformnovalidatetimeterformtargetheightm" + + "lhgroupreloadhiddenhigh1hreflanghttp-equivideoncanplaythroug" + + "h2iframeimageimglyph3isindexismappletitemscopeditemtypemarqu" + + "eematheaderspacermaxlength4minlength5mtextareadonlymultiplem" + + "utedonclickoncloseamlesspellcheckedoncontextmenuitemidoncuec" + + "hangeondblclickondragendondragenterondragleaveondragoverondr" + + "agstarticleondropzonemptiedondurationchangeonendedonerroronf" + + "ocusrcdocitempropenoscriptonhashchangeoninputmodeloninvalido" + + "nkeydownloadonkeypressrclangonkeyupublicontenteditableonlang" + + "uagechangeonloadeddatalistingonloadedmetadatabindexonloadsta" + + "rtonmessageonmousedownonmousemoveonmouseoutputonmouseoveronm" + + "ouseuponmousewheelonofflineononlineonpagehidesclassectionbef" + + "oreunloaddresshapeonpageshowidth6onpausemaponplayingonpopsta" + + "teonprogresstrikeytypeonratechangeonresetonresizestrongonscr" + + "ollonseekedonseekingonselectedonshowraponsortableonstalledon" + + "storageonsubmitemrefacenteronsuspendontoggleonunloadonvolume" + + "changeonwaitingoptimumanifestepromptoptionbeforeprintstylesu" + + "mmarysupsvgsystemplate" diff --git a/src/golang.org/x/net/html/atom/table_test.go b/src/golang.org/x/net/html/atom/table_test.go new file mode 100644 index 0000000000..0f2ecce4fd --- /dev/null +++ b/src/golang.org/x/net/html/atom/table_test.go @@ -0,0 +1,351 @@ +// generated by go run gen.go -test; DO NOT EDIT + +package atom + +var testAtomList = []string{ + "a", + "abbr", + "abbr", + "accept", + "accept-charset", + "accesskey", + "action", + "address", + "align", + "alt", + "annotation", + "annotation-xml", + "applet", + "area", + "article", + "aside", + "async", + "audio", + "autocomplete", + "autofocus", + "autoplay", + "b", + "base", + "basefont", + "bdi", + "bdo", + "bgsound", + "big", + "blink", + "blockquote", + "body", + "br", + "button", + "canvas", + "caption", + "center", + "challenge", + "charset", + "checked", + "cite", + "cite", + "class", + "code", + "col", + "colgroup", + "color", + "cols", + "colspan", + "command", + "command", + "content", + "contenteditable", + "contextmenu", + "controls", + "coords", + "crossorigin", + "data", + "data", + "datalist", + "datetime", + "dd", + "default", + "defer", + "del", + "desc", + "details", + "dfn", + "dialog", + "dir", + "dirname", + "disabled", + "div", + "dl", + "download", + "draggable", + "dropzone", + "dt", + "em", + "embed", + "enctype", + "face", + "fieldset", + "figcaption", + "figure", + "font", + "footer", + "for", + "foreignObject", + "foreignobject", + "form", + "form", + "formaction", + "formenctype", + "formmethod", + "formnovalidate", + "formtarget", + "frame", + "frameset", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "head", + "header", + "headers", + "height", + "hgroup", + "hidden", + "high", + "hr", + "href", + "hreflang", + "html", + "http-equiv", + "i", + "icon", + "id", + "iframe", + "image", + "img", + "input", + "inputmode", + "ins", + "isindex", + "ismap", + "itemid", + "itemprop", + "itemref", + "itemscope", + "itemtype", + "kbd", + "keygen", + "keytype", + "kind", + "label", + "label", + "lang", + "legend", + "li", + "link", + "list", + "listing", + "loop", + "low", + "malignmark", + "manifest", + "map", + "mark", + "marquee", + "math", + "max", + "maxlength", + "media", + "mediagroup", + "menu", + "menuitem", + "meta", + "meter", + "method", + "mglyph", + "mi", + "min", + "minlength", + "mn", + "mo", + "ms", + "mtext", + "multiple", + "muted", + "name", + "nav", + "nobr", + "noembed", + "noframes", + "noscript", + "novalidate", + "object", + "ol", + "onabort", + "onafterprint", + "onautocomplete", + "onautocompleteerror", + "onbeforeprint", + "onbeforeunload", + "onblur", + "oncancel", + "oncanplay", + "oncanplaythrough", + "onchange", + "onclick", + "onclose", + "oncontextmenu", + "oncuechange", + "ondblclick", + "ondrag", + "ondragend", + "ondragenter", + "ondragleave", + "ondragover", + "ondragstart", + "ondrop", + "ondurationchange", + "onemptied", + "onended", + "onerror", + "onfocus", + "onhashchange", + "oninput", + "oninvalid", + "onkeydown", + "onkeypress", + "onkeyup", + "onlanguagechange", + "onload", + "onloadeddata", + "onloadedmetadata", + "onloadstart", + "onmessage", + "onmousedown", + "onmousemove", + "onmouseout", + "onmouseover", + "onmouseup", + "onmousewheel", + "onoffline", + "ononline", + "onpagehide", + "onpageshow", + "onpause", + "onplay", + "onplaying", + "onpopstate", + "onprogress", + "onratechange", + "onreset", + "onresize", + "onscroll", + "onseeked", + "onseeking", + "onselect", + "onshow", + "onsort", + "onstalled", + "onstorage", + "onsubmit", + "onsuspend", + "ontimeupdate", + "ontoggle", + "onunload", + "onvolumechange", + "onwaiting", + "open", + "optgroup", + "optimum", + "option", + "output", + "p", + "param", + "pattern", + "ping", + "placeholder", + "plaintext", + "poster", + "pre", + "preload", + "progress", + "prompt", + "public", + "q", + "radiogroup", + "readonly", + "rel", + "required", + "reversed", + "rows", + "rowspan", + "rp", + "rt", + "ruby", + "s", + "samp", + "sandbox", + "scope", + "scoped", + "script", + "seamless", + "section", + "select", + "selected", + "shape", + "size", + "sizes", + "small", + "sortable", + "sorted", + "source", + "spacer", + "span", + "span", + "spellcheck", + "src", + "srcdoc", + "srclang", + "start", + "step", + "strike", + "strong", + "style", + "style", + "sub", + "summary", + "sup", + "svg", + "system", + "tabindex", + "table", + "target", + "tbody", + "td", + "template", + "textarea", + "tfoot", + "th", + "thead", + "time", + "title", + "title", + "tr", + "track", + "translate", + "tt", + "type", + "typemustmatch", + "u", + "ul", + "usemap", + "value", + "var", + "video", + "wbr", + "width", + "wrap", + "xmp", +} diff --git a/src/golang.org/x/net/html/charset/charset.go b/src/golang.org/x/net/html/charset/charset.go new file mode 100644 index 0000000000..464c82162f --- /dev/null +++ b/src/golang.org/x/net/html/charset/charset.go @@ -0,0 +1,244 @@ +// 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. + +// Package charset provides common text encodings for HTML documents. +// +// The mapping from encoding labels to encodings is defined at +// https://encoding.spec.whatwg.org/. +package charset // import "golang.org/x/net/html/charset" + +import ( + "bytes" + "fmt" + "io" + "mime" + "strings" + "unicode/utf8" + + "golang.org/x/net/html" + "golang.org/x/text/encoding" + "golang.org/x/text/encoding/charmap" + "golang.org/x/text/transform" +) + +// Lookup returns the encoding with the specified label, and its canonical +// name. It returns nil and the empty string if label is not one of the +// standard encodings for HTML. Matching is case-insensitive and ignores +// leading and trailing whitespace. +func Lookup(label string) (e encoding.Encoding, name string) { + label = strings.ToLower(strings.Trim(label, "\t\n\r\f ")) + enc := encodings[label] + return enc.e, enc.name +} + +// DetermineEncoding determines the encoding of an HTML document by examining +// up to the first 1024 bytes of content and the declared Content-Type. +// +// See http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#determining-the-character-encoding +func DetermineEncoding(content []byte, contentType string) (e encoding.Encoding, name string, certain bool) { + if len(content) > 1024 { + content = content[:1024] + } + + for _, b := range boms { + if bytes.HasPrefix(content, b.bom) { + e, name = Lookup(b.enc) + return e, name, true + } + } + + if _, params, err := mime.ParseMediaType(contentType); err == nil { + if cs, ok := params["charset"]; ok { + if e, name = Lookup(cs); e != nil { + return e, name, true + } + } + } + + if len(content) > 0 { + e, name = prescan(content) + if e != nil { + return e, name, false + } + } + + // Try to detect UTF-8. + // First eliminate any partial rune at the end. + for i := len(content) - 1; i >= 0 && i > len(content)-4; i-- { + b := content[i] + if b < 0x80 { + break + } + if utf8.RuneStart(b) { + content = content[:i] + break + } + } + hasHighBit := false + for _, c := range content { + if c >= 0x80 { + hasHighBit = true + break + } + } + if hasHighBit && utf8.Valid(content) { + return encoding.Nop, "utf-8", false + } + + // TODO: change default depending on user's locale? + return charmap.Windows1252, "windows-1252", false +} + +// NewReader returns an io.Reader that converts the content of r to UTF-8. +// It calls DetermineEncoding to find out what r's encoding is. +func NewReader(r io.Reader, contentType string) (io.Reader, error) { + preview := make([]byte, 1024) + n, err := io.ReadFull(r, preview) + switch { + case err == io.ErrUnexpectedEOF: + preview = preview[:n] + r = bytes.NewReader(preview) + case err != nil: + return nil, err + default: + r = io.MultiReader(bytes.NewReader(preview), r) + } + + if e, _, _ := DetermineEncoding(preview, contentType); e != encoding.Nop { + r = transform.NewReader(r, e.NewDecoder()) + } + return r, nil +} + +// NewReaderLabel returns a reader that converts from the specified charset to +// UTF-8. It uses Lookup to find the encoding that corresponds to label, and +// returns an error if Lookup returns nil. It is suitable for use as +// encoding/xml.Decoder's CharsetReader function. +func NewReaderLabel(label string, input io.Reader) (io.Reader, error) { + e, _ := Lookup(label) + if e == nil { + return nil, fmt.Errorf("unsupported charset: %q", label) + } + return transform.NewReader(input, e.NewDecoder()), nil +} + +func prescan(content []byte) (e encoding.Encoding, name string) { + z := html.NewTokenizer(bytes.NewReader(content)) + for { + switch z.Next() { + case html.ErrorToken: + return nil, "" + + case html.StartTagToken, html.SelfClosingTagToken: + tagName, hasAttr := z.TagName() + if !bytes.Equal(tagName, []byte("meta")) { + continue + } + attrList := make(map[string]bool) + gotPragma := false + + const ( + dontKnow = iota + doNeedPragma + doNotNeedPragma + ) + needPragma := dontKnow + + name = "" + e = nil + for hasAttr { + var key, val []byte + key, val, hasAttr = z.TagAttr() + ks := string(key) + if attrList[ks] { + continue + } + attrList[ks] = true + for i, c := range val { + if 'A' <= c && c <= 'Z' { + val[i] = c + 0x20 + } + } + + switch ks { + case "http-equiv": + if bytes.Equal(val, []byte("content-type")) { + gotPragma = true + } + + case "content": + if e == nil { + name = fromMetaElement(string(val)) + if name != "" { + e, name = Lookup(name) + if e != nil { + needPragma = doNeedPragma + } + } + } + + case "charset": + e, name = Lookup(string(val)) + needPragma = doNotNeedPragma + } + } + + if needPragma == dontKnow || needPragma == doNeedPragma && !gotPragma { + continue + } + + if strings.HasPrefix(name, "utf-16") { + name = "utf-8" + e = encoding.Nop + } + + if e != nil { + return e, name + } + } + } +} + +func fromMetaElement(s string) string { + for s != "" { + csLoc := strings.Index(s, "charset") + if csLoc == -1 { + return "" + } + s = s[csLoc+len("charset"):] + s = strings.TrimLeft(s, " \t\n\f\r") + if !strings.HasPrefix(s, "=") { + continue + } + s = s[1:] + s = strings.TrimLeft(s, " \t\n\f\r") + if s == "" { + return "" + } + if q := s[0]; q == '"' || q == '\'' { + s = s[1:] + closeQuote := strings.IndexRune(s, rune(q)) + if closeQuote == -1 { + return "" + } + return s[:closeQuote] + } + + end := strings.IndexAny(s, "; \t\n\f\r") + if end == -1 { + end = len(s) + } + return s[:end] + } + return "" +} + +var boms = []struct { + bom []byte + enc string +}{ + {[]byte{0xfe, 0xff}, "utf-16be"}, + {[]byte{0xff, 0xfe}, "utf-16le"}, + {[]byte{0xef, 0xbb, 0xbf}, "utf-8"}, +} diff --git a/src/golang.org/x/net/html/charset/charset_test.go b/src/golang.org/x/net/html/charset/charset_test.go new file mode 100644 index 0000000000..8b10399d3d --- /dev/null +++ b/src/golang.org/x/net/html/charset/charset_test.go @@ -0,0 +1,236 @@ +// 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. + +package charset + +import ( + "bytes" + "encoding/xml" + "io/ioutil" + "runtime" + "strings" + "testing" + + "golang.org/x/text/transform" +) + +func transformString(t transform.Transformer, s string) (string, error) { + r := transform.NewReader(strings.NewReader(s), t) + b, err := ioutil.ReadAll(r) + return string(b), err +} + +var testCases = []struct { + utf8, other, otherEncoding string +}{ + {"Résumé", "Résumé", "utf8"}, + {"Résumé", "R\xe9sum\xe9", "latin1"}, + {"これは漢字です。", "S0\x8c0o0\"oW[g0Y0\x020", "UTF-16LE"}, + {"これは漢字です。", "0S0\x8c0oo\"[W0g0Y0\x02", "UTF-16BE"}, + {"Hello, world", "Hello, world", "ASCII"}, + {"Gdańsk", "Gda\xf1sk", "ISO-8859-2"}, + {"Ââ Čč Đđ Ŋŋ Õõ Šš Žž Åå Ää", "\xc2\xe2 \xc8\xe8 \xa9\xb9 \xaf\xbf \xd5\xf5 \xaa\xba \xac\xbc \xc5\xe5 \xc4\xe4", "ISO-8859-10"}, + {"สำหรับ", "\xca\xd3\xcb\xc3\u047a", "ISO-8859-11"}, + {"latviešu", "latvie\xf0u", "ISO-8859-13"}, + {"Seònaid", "Se\xf2naid", "ISO-8859-14"}, + {"€1 is cheap", "\xa41 is cheap", "ISO-8859-15"}, + {"românește", "rom\xe2ne\xbate", "ISO-8859-16"}, + {"nutraĵo", "nutra\xbco", "ISO-8859-3"}, + {"Kalâdlit", "Kal\xe2dlit", "ISO-8859-4"}, + {"русский", "\xe0\xe3\xe1\xe1\xda\xd8\xd9", "ISO-8859-5"}, + {"ελληνικά", "\xe5\xeb\xeb\xe7\xed\xe9\xea\xdc", "ISO-8859-7"}, + {"Kağan", "Ka\xf0an", "ISO-8859-9"}, + {"Résumé", "R\x8esum\x8e", "macintosh"}, + {"Gdańsk", "Gda\xf1sk", "windows-1250"}, + {"русский", "\xf0\xf3\xf1\xf1\xea\xe8\xe9", "windows-1251"}, + {"Résumé", "R\xe9sum\xe9", "windows-1252"}, + {"ελληνικά", "\xe5\xeb\xeb\xe7\xed\xe9\xea\xdc", "windows-1253"}, + {"Kağan", "Ka\xf0an", "windows-1254"}, + {"עִבְרִית", "\xf2\xc4\xe1\xc0\xf8\xc4\xe9\xfa", "windows-1255"}, + {"العربية", "\xc7\xe1\xda\xd1\xc8\xed\xc9", "windows-1256"}, + {"latviešu", "latvie\xf0u", "windows-1257"}, + {"Việt", "Vi\xea\xf2t", "windows-1258"}, + {"สำหรับ", "\xca\xd3\xcb\xc3\u047a", "windows-874"}, + {"русский", "\xd2\xd5\xd3\xd3\xcb\xc9\xca", "KOI8-R"}, + {"українська", "\xd5\xcb\xd2\xc1\xa7\xce\xd3\xd8\xcb\xc1", "KOI8-U"}, + {"Hello 常用國字標準字體表", "Hello \xb1`\xa5\u03b0\xea\xa6r\xbc\u0437\u01e6r\xc5\xe9\xaa\xed", "big5"}, + {"Hello 常用國字標準字體表", "Hello \xb3\xa3\xd3\xc3\x87\xf8\xd7\xd6\x98\xcb\x9c\xca\xd7\xd6\xf3\x77\xb1\xed", "gbk"}, + {"Hello 常用國字標準字體表", "Hello \xb3\xa3\xd3\xc3\x87\xf8\xd7\xd6\x98\xcb\x9c\xca\xd7\xd6\xf3\x77\xb1\xed", "gb18030"}, + {"עִבְרִית", "\x81\x30\xfb\x30\x81\x30\xf6\x34\x81\x30\xf9\x33\x81\x30\xf6\x30\x81\x30\xfb\x36\x81\x30\xf6\x34\x81\x30\xfa\x31\x81\x30\xfb\x38", "gb18030"}, + {"㧯", "\x82\x31\x89\x38", "gb18030"}, + {"これは漢字です。", "\x82\xb1\x82\xea\x82\xcd\x8a\xbf\x8e\x9a\x82\xc5\x82\xb7\x81B", "SJIS"}, + {"Hello, 世界!", "Hello, \x90\xa2\x8aE!", "SJIS"}, + {"イウエオカ", "\xb2\xb3\xb4\xb5\xb6", "SJIS"}, + {"これは漢字です。", "\xa4\xb3\xa4\xec\xa4\u03f4\xc1\xbb\xfa\xa4\u01e4\xb9\xa1\xa3", "EUC-JP"}, + {"Hello, 世界!", "Hello, \x1b$B@$3&\x1b(B!", "ISO-2022-JP"}, + {"네이트 | 즐거움의 시작, 슈파스(Spaβ) NATE", "\xb3\xd7\xc0\xcc\xc6\xae | \xc1\xf1\xb0\xc5\xbf\xf2\xc0\xc7 \xbd\xc3\xc0\xdb, \xbd\xb4\xc6\xc4\xbd\xba(Spa\xa5\xe2) NATE", "EUC-KR"}, +} + +func TestDecode(t *testing.T) { + for _, tc := range testCases { + e, _ := Lookup(tc.otherEncoding) + if e == nil { + t.Errorf("%s: not found", tc.otherEncoding) + continue + } + s, err := transformString(e.NewDecoder(), tc.other) + if err != nil { + t.Errorf("%s: decode %q: %v", tc.otherEncoding, tc.other, err) + continue + } + if s != tc.utf8 { + t.Errorf("%s: got %q, want %q", tc.otherEncoding, s, tc.utf8) + } + } +} + +func TestEncode(t *testing.T) { + for _, tc := range testCases { + e, _ := Lookup(tc.otherEncoding) + if e == nil { + t.Errorf("%s: not found", tc.otherEncoding) + continue + } + s, err := transformString(e.NewEncoder(), tc.utf8) + if err != nil { + t.Errorf("%s: encode %q: %s", tc.otherEncoding, tc.utf8, err) + continue + } + if s != tc.other { + t.Errorf("%s: got %q, want %q", tc.otherEncoding, s, tc.other) + } + } +} + +// TestNames verifies that you can pass an encoding's name to Lookup and get +// the same encoding back (except for "replacement"). +func TestNames(t *testing.T) { + for _, e := range encodings { + if e.name == "replacement" { + continue + } + _, got := Lookup(e.name) + if got != e.name { + t.Errorf("got %q, want %q", got, e.name) + continue + } + } +} + +var sniffTestCases = []struct { + filename, declared, want string +}{ + {"HTTP-charset.html", "text/html; charset=iso-8859-15", "iso-8859-15"}, + {"UTF-16LE-BOM.html", "", "utf-16le"}, + {"UTF-16BE-BOM.html", "", "utf-16be"}, + {"meta-content-attribute.html", "text/html", "iso-8859-15"}, + {"meta-charset-attribute.html", "text/html", "iso-8859-15"}, + {"No-encoding-declaration.html", "text/html", "utf-8"}, + {"HTTP-vs-UTF-8-BOM.html", "text/html; charset=iso-8859-15", "utf-8"}, + {"HTTP-vs-meta-content.html", "text/html; charset=iso-8859-15", "iso-8859-15"}, + {"HTTP-vs-meta-charset.html", "text/html; charset=iso-8859-15", "iso-8859-15"}, + {"UTF-8-BOM-vs-meta-content.html", "text/html", "utf-8"}, + {"UTF-8-BOM-vs-meta-charset.html", "text/html", "utf-8"}, +} + +func TestSniff(t *testing.T) { + switch runtime.GOOS { + case "nacl": // platforms that don't permit direct file system access + t.Skipf("not supported on %q", runtime.GOOS) + } + + for _, tc := range sniffTestCases { + content, err := ioutil.ReadFile("testdata/" + tc.filename) + if err != nil { + t.Errorf("%s: error reading file: %v", tc.filename, err) + continue + } + + _, name, _ := DetermineEncoding(content, tc.declared) + if name != tc.want { + t.Errorf("%s: got %q, want %q", tc.filename, name, tc.want) + continue + } + } +} + +func TestReader(t *testing.T) { + switch runtime.GOOS { + case "nacl": // platforms that don't permit direct file system access + t.Skipf("not supported on %q", runtime.GOOS) + } + + for _, tc := range sniffTestCases { + content, err := ioutil.ReadFile("testdata/" + tc.filename) + if err != nil { + t.Errorf("%s: error reading file: %v", tc.filename, err) + continue + } + + r, err := NewReader(bytes.NewReader(content), tc.declared) + if err != nil { + t.Errorf("%s: error creating reader: %v", tc.filename, err) + continue + } + + got, err := ioutil.ReadAll(r) + if err != nil { + t.Errorf("%s: error reading from charset.NewReader: %v", tc.filename, err) + continue + } + + e, _ := Lookup(tc.want) + want, err := ioutil.ReadAll(transform.NewReader(bytes.NewReader(content), e.NewDecoder())) + if err != nil { + t.Errorf("%s: error decoding with hard-coded charset name: %v", tc.filename, err) + continue + } + + if !bytes.Equal(got, want) { + t.Errorf("%s: got %q, want %q", tc.filename, got, want) + continue + } + } +} + +var metaTestCases = []struct { + meta, want string +}{ + {"", ""}, + {"text/html", ""}, + {"text/html; charset utf-8", ""}, + {"text/html; charset=latin-2", "latin-2"}, + {"text/html; charset; charset = utf-8", "utf-8"}, + {`charset="big5"`, "big5"}, + {"charset='shift_jis'", "shift_jis"}, +} + +func TestFromMeta(t *testing.T) { + for _, tc := range metaTestCases { + got := fromMetaElement(tc.meta) + if got != tc.want { + t.Errorf("%q: got %q, want %q", tc.meta, got, tc.want) + } + } +} + +func TestXML(t *testing.T) { + const s = "r\xe9sum\xe9" + + d := xml.NewDecoder(strings.NewReader(s)) + d.CharsetReader = NewReaderLabel + + var a struct { + Word string + } + err := d.Decode(&a) + if err != nil { + t.Fatalf("Decode: %v", err) + } + + want := "résumé" + if a.Word != want { + t.Errorf("got %q, want %q", a.Word, want) + } +} diff --git a/src/golang.org/x/net/html/charset/gen.go b/src/golang.org/x/net/html/charset/gen.go new file mode 100644 index 0000000000..828347f4ae --- /dev/null +++ b/src/golang.org/x/net/html/charset/gen.go @@ -0,0 +1,111 @@ +// 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 ignore + +package main + +// Download https://encoding.spec.whatwg.org/encodings.json and use it to +// generate table.go. + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "strings" +) + +type enc struct { + Name string + Labels []string +} + +type group struct { + Encodings []enc + Heading string +} + +const specURL = "https://encoding.spec.whatwg.org/encodings.json" + +func main() { + resp, err := http.Get(specURL) + if err != nil { + log.Fatalf("error fetching %s: %s", specURL, err) + } + if resp.StatusCode != 200 { + log.Fatalf("error fetching %s: HTTP status %s", specURL, resp.Status) + } + defer resp.Body.Close() + + var groups []group + d := json.NewDecoder(resp.Body) + err = d.Decode(&groups) + if err != nil { + log.Fatalf("error reading encodings.json: %s", err) + } + + fmt.Println("// generated by go run gen.go; DO NOT EDIT") + fmt.Println() + fmt.Println("package charset") + fmt.Println() + + fmt.Println("import (") + fmt.Println(`"golang.org/x/text/encoding"`) + for _, pkg := range []string{"charmap", "japanese", "korean", "simplifiedchinese", "traditionalchinese", "unicode"} { + fmt.Printf("\"golang.org/x/text/encoding/%s\"\n", pkg) + } + fmt.Println(")") + fmt.Println() + + fmt.Println("var encodings = map[string]struct{e encoding.Encoding; name string} {") + for _, g := range groups { + for _, e := range g.Encodings { + goName, ok := miscNames[e.Name] + if !ok { + for k, v := range prefixes { + if strings.HasPrefix(e.Name, k) { + goName = v + e.Name[len(k):] + break + } + } + if goName == "" { + log.Fatalf("unrecognized encoding name: %s", e.Name) + } + } + + for _, label := range e.Labels { + fmt.Printf("%q: {%s, %q},\n", label, goName, e.Name) + } + } + } + fmt.Println("}") +} + +var prefixes = map[string]string{ + "iso-8859-": "charmap.ISO8859_", + "windows-": "charmap.Windows", +} + +var miscNames = map[string]string{ + "utf-8": "encoding.Nop", + "ibm866": "charmap.CodePage866", + "iso-8859-8-i": "charmap.ISO8859_8", + "koi8-r": "charmap.KOI8R", + "koi8-u": "charmap.KOI8U", + "macintosh": "charmap.Macintosh", + "x-mac-cyrillic": "charmap.MacintoshCyrillic", + "gbk": "simplifiedchinese.GBK", + "gb18030": "simplifiedchinese.GB18030", + "hz-gb-2312": "simplifiedchinese.HZGB2312", + "big5": "traditionalchinese.Big5", + "euc-jp": "japanese.EUCJP", + "iso-2022-jp": "japanese.ISO2022JP", + "shift_jis": "japanese.ShiftJIS", + "euc-kr": "korean.EUCKR", + "replacement": "encoding.Replacement", + "utf-16be": "unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM)", + "utf-16le": "unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)", + "x-user-defined": "charmap.XUserDefined", +} diff --git a/src/golang.org/x/net/html/charset/table.go b/src/golang.org/x/net/html/charset/table.go new file mode 100644 index 0000000000..aa0d948407 --- /dev/null +++ b/src/golang.org/x/net/html/charset/table.go @@ -0,0 +1,235 @@ +// generated by go run gen.go; DO NOT EDIT + +package charset + +import ( + "golang.org/x/text/encoding" + "golang.org/x/text/encoding/charmap" + "golang.org/x/text/encoding/japanese" + "golang.org/x/text/encoding/korean" + "golang.org/x/text/encoding/simplifiedchinese" + "golang.org/x/text/encoding/traditionalchinese" + "golang.org/x/text/encoding/unicode" +) + +var encodings = map[string]struct { + e encoding.Encoding + name string +}{ + "unicode-1-1-utf-8": {encoding.Nop, "utf-8"}, + "utf-8": {encoding.Nop, "utf-8"}, + "utf8": {encoding.Nop, "utf-8"}, + "866": {charmap.CodePage866, "ibm866"}, + "cp866": {charmap.CodePage866, "ibm866"}, + "csibm866": {charmap.CodePage866, "ibm866"}, + "ibm866": {charmap.CodePage866, "ibm866"}, + "csisolatin2": {charmap.ISO8859_2, "iso-8859-2"}, + "iso-8859-2": {charmap.ISO8859_2, "iso-8859-2"}, + "iso-ir-101": {charmap.ISO8859_2, "iso-8859-2"}, + "iso8859-2": {charmap.ISO8859_2, "iso-8859-2"}, + "iso88592": {charmap.ISO8859_2, "iso-8859-2"}, + "iso_8859-2": {charmap.ISO8859_2, "iso-8859-2"}, + "iso_8859-2:1987": {charmap.ISO8859_2, "iso-8859-2"}, + "l2": {charmap.ISO8859_2, "iso-8859-2"}, + "latin2": {charmap.ISO8859_2, "iso-8859-2"}, + "csisolatin3": {charmap.ISO8859_3, "iso-8859-3"}, + "iso-8859-3": {charmap.ISO8859_3, "iso-8859-3"}, + "iso-ir-109": {charmap.ISO8859_3, "iso-8859-3"}, + "iso8859-3": {charmap.ISO8859_3, "iso-8859-3"}, + "iso88593": {charmap.ISO8859_3, "iso-8859-3"}, + "iso_8859-3": {charmap.ISO8859_3, "iso-8859-3"}, + "iso_8859-3:1988": {charmap.ISO8859_3, "iso-8859-3"}, + "l3": {charmap.ISO8859_3, "iso-8859-3"}, + "latin3": {charmap.ISO8859_3, "iso-8859-3"}, + "csisolatin4": {charmap.ISO8859_4, "iso-8859-4"}, + "iso-8859-4": {charmap.ISO8859_4, "iso-8859-4"}, + "iso-ir-110": {charmap.ISO8859_4, "iso-8859-4"}, + "iso8859-4": {charmap.ISO8859_4, "iso-8859-4"}, + "iso88594": {charmap.ISO8859_4, "iso-8859-4"}, + "iso_8859-4": {charmap.ISO8859_4, "iso-8859-4"}, + "iso_8859-4:1988": {charmap.ISO8859_4, "iso-8859-4"}, + "l4": {charmap.ISO8859_4, "iso-8859-4"}, + "latin4": {charmap.ISO8859_4, "iso-8859-4"}, + "csisolatincyrillic": {charmap.ISO8859_5, "iso-8859-5"}, + "cyrillic": {charmap.ISO8859_5, "iso-8859-5"}, + "iso-8859-5": {charmap.ISO8859_5, "iso-8859-5"}, + "iso-ir-144": {charmap.ISO8859_5, "iso-8859-5"}, + "iso8859-5": {charmap.ISO8859_5, "iso-8859-5"}, + "iso88595": {charmap.ISO8859_5, "iso-8859-5"}, + "iso_8859-5": {charmap.ISO8859_5, "iso-8859-5"}, + "iso_8859-5:1988": {charmap.ISO8859_5, "iso-8859-5"}, + "arabic": {charmap.ISO8859_6, "iso-8859-6"}, + "asmo-708": {charmap.ISO8859_6, "iso-8859-6"}, + "csiso88596e": {charmap.ISO8859_6, "iso-8859-6"}, + "csiso88596i": {charmap.ISO8859_6, "iso-8859-6"}, + "csisolatinarabic": {charmap.ISO8859_6, "iso-8859-6"}, + "ecma-114": {charmap.ISO8859_6, "iso-8859-6"}, + "iso-8859-6": {charmap.ISO8859_6, "iso-8859-6"}, + "iso-8859-6-e": {charmap.ISO8859_6, "iso-8859-6"}, + "iso-8859-6-i": {charmap.ISO8859_6, "iso-8859-6"}, + "iso-ir-127": {charmap.ISO8859_6, "iso-8859-6"}, + "iso8859-6": {charmap.ISO8859_6, "iso-8859-6"}, + "iso88596": {charmap.ISO8859_6, "iso-8859-6"}, + "iso_8859-6": {charmap.ISO8859_6, "iso-8859-6"}, + "iso_8859-6:1987": {charmap.ISO8859_6, "iso-8859-6"}, + "csisolatingreek": {charmap.ISO8859_7, "iso-8859-7"}, + "ecma-118": {charmap.ISO8859_7, "iso-8859-7"}, + "elot_928": {charmap.ISO8859_7, "iso-8859-7"}, + "greek": {charmap.ISO8859_7, "iso-8859-7"}, + "greek8": {charmap.ISO8859_7, "iso-8859-7"}, + "iso-8859-7": {charmap.ISO8859_7, "iso-8859-7"}, + "iso-ir-126": {charmap.ISO8859_7, "iso-8859-7"}, + "iso8859-7": {charmap.ISO8859_7, "iso-8859-7"}, + "iso88597": {charmap.ISO8859_7, "iso-8859-7"}, + "iso_8859-7": {charmap.ISO8859_7, "iso-8859-7"}, + "iso_8859-7:1987": {charmap.ISO8859_7, "iso-8859-7"}, + "sun_eu_greek": {charmap.ISO8859_7, "iso-8859-7"}, + "csiso88598e": {charmap.ISO8859_8, "iso-8859-8"}, + "csisolatinhebrew": {charmap.ISO8859_8, "iso-8859-8"}, + "hebrew": {charmap.ISO8859_8, "iso-8859-8"}, + "iso-8859-8": {charmap.ISO8859_8, "iso-8859-8"}, + "iso-8859-8-e": {charmap.ISO8859_8, "iso-8859-8"}, + "iso-ir-138": {charmap.ISO8859_8, "iso-8859-8"}, + "iso8859-8": {charmap.ISO8859_8, "iso-8859-8"}, + "iso88598": {charmap.ISO8859_8, "iso-8859-8"}, + "iso_8859-8": {charmap.ISO8859_8, "iso-8859-8"}, + "iso_8859-8:1988": {charmap.ISO8859_8, "iso-8859-8"}, + "visual": {charmap.ISO8859_8, "iso-8859-8"}, + "csiso88598i": {charmap.ISO8859_8, "iso-8859-8-i"}, + "iso-8859-8-i": {charmap.ISO8859_8, "iso-8859-8-i"}, + "logical": {charmap.ISO8859_8, "iso-8859-8-i"}, + "csisolatin6": {charmap.ISO8859_10, "iso-8859-10"}, + "iso-8859-10": {charmap.ISO8859_10, "iso-8859-10"}, + "iso-ir-157": {charmap.ISO8859_10, "iso-8859-10"}, + "iso8859-10": {charmap.ISO8859_10, "iso-8859-10"}, + "iso885910": {charmap.ISO8859_10, "iso-8859-10"}, + "l6": {charmap.ISO8859_10, "iso-8859-10"}, + "latin6": {charmap.ISO8859_10, "iso-8859-10"}, + "iso-8859-13": {charmap.ISO8859_13, "iso-8859-13"}, + "iso8859-13": {charmap.ISO8859_13, "iso-8859-13"}, + "iso885913": {charmap.ISO8859_13, "iso-8859-13"}, + "iso-8859-14": {charmap.ISO8859_14, "iso-8859-14"}, + "iso8859-14": {charmap.ISO8859_14, "iso-8859-14"}, + "iso885914": {charmap.ISO8859_14, "iso-8859-14"}, + "csisolatin9": {charmap.ISO8859_15, "iso-8859-15"}, + "iso-8859-15": {charmap.ISO8859_15, "iso-8859-15"}, + "iso8859-15": {charmap.ISO8859_15, "iso-8859-15"}, + "iso885915": {charmap.ISO8859_15, "iso-8859-15"}, + "iso_8859-15": {charmap.ISO8859_15, "iso-8859-15"}, + "l9": {charmap.ISO8859_15, "iso-8859-15"}, + "iso-8859-16": {charmap.ISO8859_16, "iso-8859-16"}, + "cskoi8r": {charmap.KOI8R, "koi8-r"}, + "koi": {charmap.KOI8R, "koi8-r"}, + "koi8": {charmap.KOI8R, "koi8-r"}, + "koi8-r": {charmap.KOI8R, "koi8-r"}, + "koi8_r": {charmap.KOI8R, "koi8-r"}, + "koi8-u": {charmap.KOI8U, "koi8-u"}, + "csmacintosh": {charmap.Macintosh, "macintosh"}, + "mac": {charmap.Macintosh, "macintosh"}, + "macintosh": {charmap.Macintosh, "macintosh"}, + "x-mac-roman": {charmap.Macintosh, "macintosh"}, + "dos-874": {charmap.Windows874, "windows-874"}, + "iso-8859-11": {charmap.Windows874, "windows-874"}, + "iso8859-11": {charmap.Windows874, "windows-874"}, + "iso885911": {charmap.Windows874, "windows-874"}, + "tis-620": {charmap.Windows874, "windows-874"}, + "windows-874": {charmap.Windows874, "windows-874"}, + "cp1250": {charmap.Windows1250, "windows-1250"}, + "windows-1250": {charmap.Windows1250, "windows-1250"}, + "x-cp1250": {charmap.Windows1250, "windows-1250"}, + "cp1251": {charmap.Windows1251, "windows-1251"}, + "windows-1251": {charmap.Windows1251, "windows-1251"}, + "x-cp1251": {charmap.Windows1251, "windows-1251"}, + "ansi_x3.4-1968": {charmap.Windows1252, "windows-1252"}, + "ascii": {charmap.Windows1252, "windows-1252"}, + "cp1252": {charmap.Windows1252, "windows-1252"}, + "cp819": {charmap.Windows1252, "windows-1252"}, + "csisolatin1": {charmap.Windows1252, "windows-1252"}, + "ibm819": {charmap.Windows1252, "windows-1252"}, + "iso-8859-1": {charmap.Windows1252, "windows-1252"}, + "iso-ir-100": {charmap.Windows1252, "windows-1252"}, + "iso8859-1": {charmap.Windows1252, "windows-1252"}, + "iso88591": {charmap.Windows1252, "windows-1252"}, + "iso_8859-1": {charmap.Windows1252, "windows-1252"}, + "iso_8859-1:1987": {charmap.Windows1252, "windows-1252"}, + "l1": {charmap.Windows1252, "windows-1252"}, + "latin1": {charmap.Windows1252, "windows-1252"}, + "us-ascii": {charmap.Windows1252, "windows-1252"}, + "windows-1252": {charmap.Windows1252, "windows-1252"}, + "x-cp1252": {charmap.Windows1252, "windows-1252"}, + "cp1253": {charmap.Windows1253, "windows-1253"}, + "windows-1253": {charmap.Windows1253, "windows-1253"}, + "x-cp1253": {charmap.Windows1253, "windows-1253"}, + "cp1254": {charmap.Windows1254, "windows-1254"}, + "csisolatin5": {charmap.Windows1254, "windows-1254"}, + "iso-8859-9": {charmap.Windows1254, "windows-1254"}, + "iso-ir-148": {charmap.Windows1254, "windows-1254"}, + "iso8859-9": {charmap.Windows1254, "windows-1254"}, + "iso88599": {charmap.Windows1254, "windows-1254"}, + "iso_8859-9": {charmap.Windows1254, "windows-1254"}, + "iso_8859-9:1989": {charmap.Windows1254, "windows-1254"}, + "l5": {charmap.Windows1254, "windows-1254"}, + "latin5": {charmap.Windows1254, "windows-1254"}, + "windows-1254": {charmap.Windows1254, "windows-1254"}, + "x-cp1254": {charmap.Windows1254, "windows-1254"}, + "cp1255": {charmap.Windows1255, "windows-1255"}, + "windows-1255": {charmap.Windows1255, "windows-1255"}, + "x-cp1255": {charmap.Windows1255, "windows-1255"}, + "cp1256": {charmap.Windows1256, "windows-1256"}, + "windows-1256": {charmap.Windows1256, "windows-1256"}, + "x-cp1256": {charmap.Windows1256, "windows-1256"}, + "cp1257": {charmap.Windows1257, "windows-1257"}, + "windows-1257": {charmap.Windows1257, "windows-1257"}, + "x-cp1257": {charmap.Windows1257, "windows-1257"}, + "cp1258": {charmap.Windows1258, "windows-1258"}, + "windows-1258": {charmap.Windows1258, "windows-1258"}, + "x-cp1258": {charmap.Windows1258, "windows-1258"}, + "x-mac-cyrillic": {charmap.MacintoshCyrillic, "x-mac-cyrillic"}, + "x-mac-ukrainian": {charmap.MacintoshCyrillic, "x-mac-cyrillic"}, + "chinese": {simplifiedchinese.GBK, "gbk"}, + "csgb2312": {simplifiedchinese.GBK, "gbk"}, + "csiso58gb231280": {simplifiedchinese.GBK, "gbk"}, + "gb2312": {simplifiedchinese.GBK, "gbk"}, + "gb_2312": {simplifiedchinese.GBK, "gbk"}, + "gb_2312-80": {simplifiedchinese.GBK, "gbk"}, + "gbk": {simplifiedchinese.GBK, "gbk"}, + "iso-ir-58": {simplifiedchinese.GBK, "gbk"}, + "x-gbk": {simplifiedchinese.GBK, "gbk"}, + "gb18030": {simplifiedchinese.GB18030, "gb18030"}, + "hz-gb-2312": {simplifiedchinese.HZGB2312, "hz-gb-2312"}, + "big5": {traditionalchinese.Big5, "big5"}, + "big5-hkscs": {traditionalchinese.Big5, "big5"}, + "cn-big5": {traditionalchinese.Big5, "big5"}, + "csbig5": {traditionalchinese.Big5, "big5"}, + "x-x-big5": {traditionalchinese.Big5, "big5"}, + "cseucpkdfmtjapanese": {japanese.EUCJP, "euc-jp"}, + "euc-jp": {japanese.EUCJP, "euc-jp"}, + "x-euc-jp": {japanese.EUCJP, "euc-jp"}, + "csiso2022jp": {japanese.ISO2022JP, "iso-2022-jp"}, + "iso-2022-jp": {japanese.ISO2022JP, "iso-2022-jp"}, + "csshiftjis": {japanese.ShiftJIS, "shift_jis"}, + "ms_kanji": {japanese.ShiftJIS, "shift_jis"}, + "shift-jis": {japanese.ShiftJIS, "shift_jis"}, + "shift_jis": {japanese.ShiftJIS, "shift_jis"}, + "sjis": {japanese.ShiftJIS, "shift_jis"}, + "windows-31j": {japanese.ShiftJIS, "shift_jis"}, + "x-sjis": {japanese.ShiftJIS, "shift_jis"}, + "cseuckr": {korean.EUCKR, "euc-kr"}, + "csksc56011987": {korean.EUCKR, "euc-kr"}, + "euc-kr": {korean.EUCKR, "euc-kr"}, + "iso-ir-149": {korean.EUCKR, "euc-kr"}, + "korean": {korean.EUCKR, "euc-kr"}, + "ks_c_5601-1987": {korean.EUCKR, "euc-kr"}, + "ks_c_5601-1989": {korean.EUCKR, "euc-kr"}, + "ksc5601": {korean.EUCKR, "euc-kr"}, + "ksc_5601": {korean.EUCKR, "euc-kr"}, + "windows-949": {korean.EUCKR, "euc-kr"}, + "csiso2022kr": {encoding.Replacement, "replacement"}, + "iso-2022-kr": {encoding.Replacement, "replacement"}, + "iso-2022-cn": {encoding.Replacement, "replacement"}, + "iso-2022-cn-ext": {encoding.Replacement, "replacement"}, + "utf-16be": {unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM), "utf-16be"}, + "utf-16": {unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM), "utf-16le"}, + "utf-16le": {unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM), "utf-16le"}, + "x-user-defined": {charmap.XUserDefined, "x-user-defined"}, +} diff --git a/src/golang.org/x/net/html/charset/testdata/HTTP-charset.html b/src/golang.org/x/net/html/charset/testdata/HTTP-charset.html new file mode 100644 index 0000000000..9915fa0ee4 --- /dev/null +++ b/src/golang.org/x/net/html/charset/testdata/HTTP-charset.html @@ -0,0 +1,48 @@ + + + + HTTP charset + + + + + + + + + + + +

HTTP charset

+ + +
+ + +
 
+ + + + + +
+

The character encoding of a page can be set using the HTTP header charset declaration.

+

The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ÜÀÚ. This matches the sequence of bytes above when they are interpreted as ISO 8859-15. If the class name matches the selector then the test will pass.

The only character encoding declaration for this HTML file is in the HTTP header, which sets the encoding to ISO 8859-15.

+
+
+
HTML5
+

the-input-byte-stream-001
Result summary & related tests
Detailed results for this test
Link to spec

+
Assumptions:
  • The default encoding for the browser you are testing is not set to ISO 8859-15.
  • +
  • The test is read from a server that supports HTTP.
+
+ + + + + + diff --git a/src/golang.org/x/net/html/charset/testdata/HTTP-vs-UTF-8-BOM.html b/src/golang.org/x/net/html/charset/testdata/HTTP-vs-UTF-8-BOM.html new file mode 100644 index 0000000000..26e5d8b4eb --- /dev/null +++ b/src/golang.org/x/net/html/charset/testdata/HTTP-vs-UTF-8-BOM.html @@ -0,0 +1,48 @@ + + + + HTTP vs UTF-8 BOM + + + + + + + + + + + +

HTTP vs UTF-8 BOM

+ + +
+ + +
 
+ + + + + +
+

A character encoding set in the HTTP header has lower precedence than the UTF-8 signature.

+

The HTTP header attempts to set the character encoding to ISO 8859-15. The page starts with a UTF-8 signature.

The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ýäè. This matches the sequence of bytes above when they are interpreted as UTF-8. If the class name matches the selector then the test will pass.

If the test is unsuccessful, the characters  should appear at the top of the page. These represent the bytes that make up the UTF-8 signature when encountered in the ISO 8859-15 encoding.

+
+
+
HTML5
+

the-input-byte-stream-034
Result summary & related tests
Detailed results for this test
Link to spec

+
Assumptions:
  • The default encoding for the browser you are testing is not set to ISO 8859-15.
  • +
  • The test is read from a server that supports HTTP.
+
+ + + + + + diff --git a/src/golang.org/x/net/html/charset/testdata/HTTP-vs-meta-charset.html b/src/golang.org/x/net/html/charset/testdata/HTTP-vs-meta-charset.html new file mode 100644 index 0000000000..2f07e95158 --- /dev/null +++ b/src/golang.org/x/net/html/charset/testdata/HTTP-vs-meta-charset.html @@ -0,0 +1,49 @@ + + + + HTTP vs meta charset + + + + + + + + + + + +

HTTP vs meta charset

+ + +
+ + +
 
+ + + + + +
+

The HTTP header has a higher precedence than an encoding declaration in a meta charset attribute.

+

The HTTP header attempts to set the character encoding to ISO 8859-15. The page contains an encoding declaration in a meta charset attribute that attempts to set the character encoding to ISO 8859-1.

The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ÜÀÚ. This matches the sequence of bytes above when they are interpreted as ISO 8859-15. If the class name matches the selector then the test will pass.

+
+
+
HTML5
+

the-input-byte-stream-018
Result summary & related tests
Detailed results for this test
Link to spec

+
Assumptions:
  • The default encoding for the browser you are testing is not set to ISO 8859-15.
  • +
  • The test is read from a server that supports HTTP.
+
+ + + + + + diff --git a/src/golang.org/x/net/html/charset/testdata/HTTP-vs-meta-content.html b/src/golang.org/x/net/html/charset/testdata/HTTP-vs-meta-content.html new file mode 100644 index 0000000000..6853cddecc --- /dev/null +++ b/src/golang.org/x/net/html/charset/testdata/HTTP-vs-meta-content.html @@ -0,0 +1,49 @@ + + + + HTTP vs meta content + + + + + + + + + + + +

HTTP vs meta content

+ + +
+ + +
 
+ + + + + +
+

The HTTP header has a higher precedence than an encoding declaration in a meta content attribute.

+

The HTTP header attempts to set the character encoding to ISO 8859-15. The page contains an encoding declaration in a meta content attribute that attempts to set the character encoding to ISO 8859-1.

The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ÜÀÚ. This matches the sequence of bytes above when they are interpreted as ISO 8859-15. If the class name matches the selector then the test will pass.

+
+
+
HTML5
+

the-input-byte-stream-016
Result summary & related tests
Detailed results for this test
Link to spec

+
Assumptions:
  • The default encoding for the browser you are testing is not set to ISO 8859-15.
  • +
  • The test is read from a server that supports HTTP.
+
+ + + + + + diff --git a/src/golang.org/x/net/html/charset/testdata/No-encoding-declaration.html b/src/golang.org/x/net/html/charset/testdata/No-encoding-declaration.html new file mode 100644 index 0000000000..612e26c6c5 --- /dev/null +++ b/src/golang.org/x/net/html/charset/testdata/No-encoding-declaration.html @@ -0,0 +1,47 @@ + + + + No encoding declaration + + + + + + + + + + + +

No encoding declaration

+ + +
+ + +
 
+ + + + + +
+

A page with no encoding information in HTTP, BOM, XML declaration or meta element will be treated as UTF-8.

+

The test on this page contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ýäè. This matches the sequence of bytes above when they are interpreted as UTF-8. If the class name matches the selector then the test will pass.

+
+
+
HTML5
+

the-input-byte-stream-015
Result summary & related tests
Detailed results for this test
Link to spec

+
Assumptions:
  • The test is read from a server that supports HTTP.
+
+ + + + + + diff --git a/src/golang.org/x/net/html/charset/testdata/README b/src/golang.org/x/net/html/charset/testdata/README new file mode 100644 index 0000000000..38ef0f9f12 --- /dev/null +++ b/src/golang.org/x/net/html/charset/testdata/README @@ -0,0 +1,9 @@ +These test cases come from +http://www.w3.org/International/tests/repository/html5/the-input-byte-stream/results-basics + +Distributed under both the W3C Test Suite License +(http://www.w3.org/Consortium/Legal/2008/04-testsuite-license) +and the W3C 3-clause BSD License +(http://www.w3.org/Consortium/Legal/2008/03-bsd-license). +To contribute to a W3C Test Suite, see the policies and contribution +forms (http://www.w3.org/2004/10/27-testcases). diff --git a/src/golang.org/x/net/html/charset/testdata/UTF-16BE-BOM.html b/src/golang.org/x/net/html/charset/testdata/UTF-16BE-BOM.html new file mode 100644 index 0000000000..3abf7a9343 Binary files /dev/null and b/src/golang.org/x/net/html/charset/testdata/UTF-16BE-BOM.html differ diff --git a/src/golang.org/x/net/html/charset/testdata/UTF-16LE-BOM.html b/src/golang.org/x/net/html/charset/testdata/UTF-16LE-BOM.html new file mode 100644 index 0000000000..76254c980c Binary files /dev/null and b/src/golang.org/x/net/html/charset/testdata/UTF-16LE-BOM.html differ diff --git a/src/golang.org/x/net/html/charset/testdata/UTF-8-BOM-vs-meta-charset.html b/src/golang.org/x/net/html/charset/testdata/UTF-8-BOM-vs-meta-charset.html new file mode 100644 index 0000000000..83de43338e --- /dev/null +++ b/src/golang.org/x/net/html/charset/testdata/UTF-8-BOM-vs-meta-charset.html @@ -0,0 +1,49 @@ + + + + UTF-8 BOM vs meta charset + + + + + + + + + + + +

UTF-8 BOM vs meta charset

+ + +
+ + +
 
+ + + + + +
+

A page with a UTF-8 BOM will be recognized as UTF-8 even if the meta charset attribute declares a different encoding.

+

The page contains an encoding declaration in a meta charset attribute that attempts to set the character encoding to ISO 8859-15, but the file starts with a UTF-8 signature.

The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ýäè. This matches the sequence of bytes above when they are interpreted as UTF-8. If the class name matches the selector then the test will pass.

+
+
+
HTML5
+

the-input-byte-stream-038
Result summary & related tests
Detailed results for this test
Link to spec

+
Assumptions:
  • The default encoding for the browser you are testing is not set to ISO 8859-15.
  • +
  • The test is read from a server that supports HTTP.
+
+ + + + + + diff --git a/src/golang.org/x/net/html/charset/testdata/UTF-8-BOM-vs-meta-content.html b/src/golang.org/x/net/html/charset/testdata/UTF-8-BOM-vs-meta-content.html new file mode 100644 index 0000000000..501aac2d6a --- /dev/null +++ b/src/golang.org/x/net/html/charset/testdata/UTF-8-BOM-vs-meta-content.html @@ -0,0 +1,48 @@ + + + + UTF-8 BOM vs meta content + + + + + + + + + + + +

UTF-8 BOM vs meta content

+ + +
+ + +
 
+ + + + + +
+

A page with a UTF-8 BOM will be recognized as UTF-8 even if the meta content attribute declares a different encoding.

+

The page contains an encoding declaration in a meta content attribute that attempts to set the character encoding to ISO 8859-15, but the file starts with a UTF-8 signature.

The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ýäè. This matches the sequence of bytes above when they are interpreted as UTF-8. If the class name matches the selector then the test will pass.

+
+
+
HTML5
+

the-input-byte-stream-037
Result summary & related tests
Detailed results for this test
Link to spec

+
Assumptions:
  • The default encoding for the browser you are testing is not set to ISO 8859-15.
  • +
  • The test is read from a server that supports HTTP.
+
+ + + + + + diff --git a/src/golang.org/x/net/html/charset/testdata/meta-charset-attribute.html b/src/golang.org/x/net/html/charset/testdata/meta-charset-attribute.html new file mode 100644 index 0000000000..2d7d25aba1 --- /dev/null +++ b/src/golang.org/x/net/html/charset/testdata/meta-charset-attribute.html @@ -0,0 +1,48 @@ + + + + meta charset attribute + + + + + + + + + + + +

meta charset attribute

+ + +
+ + +
 
+ + + + + +
+

The character encoding of the page can be set by a meta element with charset attribute.

+

The only character encoding declaration for this HTML file is in the charset attribute of the meta element, which declares the encoding to be ISO 8859-15.

The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ÜÀÚ. This matches the sequence of bytes above when they are interpreted as ISO 8859-15. If the class name matches the selector then the test will pass.

+
+
+
HTML5
+

the-input-byte-stream-009
Result summary & related tests
Detailed results for this test
Link to spec

+
Assumptions:
  • The default encoding for the browser you are testing is not set to ISO 8859-15.
  • +
  • The test is read from a server that supports HTTP.
+
+ + + + + + diff --git a/src/golang.org/x/net/html/charset/testdata/meta-content-attribute.html b/src/golang.org/x/net/html/charset/testdata/meta-content-attribute.html new file mode 100644 index 0000000000..1c3f228e7c --- /dev/null +++ b/src/golang.org/x/net/html/charset/testdata/meta-content-attribute.html @@ -0,0 +1,48 @@ + + + + meta content attribute + + + + + + + + + + + +

meta content attribute

+ + +
+ + +
 
+ + + + + +
+

The character encoding of the page can be set by a meta element with http-equiv and content attributes.

+

The only character encoding declaration for this HTML file is in the content attribute of the meta element, which declares the encoding to be ISO 8859-15.

The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ÜÀÚ. This matches the sequence of bytes above when they are interpreted as ISO 8859-15. If the class name matches the selector then the test will pass.

+
+
+
HTML5
+

the-input-byte-stream-007
Result summary & related tests
Detailed results for this test
Link to spec

+
Assumptions:
  • The default encoding for the browser you are testing is not set to ISO 8859-15.
  • +
  • The test is read from a server that supports HTTP.
+
+ + + + + + diff --git a/src/golang.org/x/net/html/const.go b/src/golang.org/x/net/html/const.go new file mode 100644 index 0000000000..52f651ff6d --- /dev/null +++ b/src/golang.org/x/net/html/const.go @@ -0,0 +1,102 @@ +// 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. + +package html + +// Section 12.2.3.2 of the HTML5 specification says "The following elements +// have varying levels of special parsing rules". +// https://html.spec.whatwg.org/multipage/syntax.html#the-stack-of-open-elements +var isSpecialElementMap = map[string]bool{ + "address": true, + "applet": true, + "area": true, + "article": true, + "aside": true, + "base": true, + "basefont": true, + "bgsound": true, + "blockquote": true, + "body": true, + "br": true, + "button": true, + "caption": true, + "center": true, + "col": true, + "colgroup": true, + "dd": true, + "details": true, + "dir": true, + "div": true, + "dl": true, + "dt": true, + "embed": true, + "fieldset": true, + "figcaption": true, + "figure": true, + "footer": true, + "form": true, + "frame": true, + "frameset": true, + "h1": true, + "h2": true, + "h3": true, + "h4": true, + "h5": true, + "h6": true, + "head": true, + "header": true, + "hgroup": true, + "hr": true, + "html": true, + "iframe": true, + "img": true, + "input": true, + "isindex": true, + "li": true, + "link": true, + "listing": true, + "marquee": true, + "menu": true, + "meta": true, + "nav": true, + "noembed": true, + "noframes": true, + "noscript": true, + "object": true, + "ol": true, + "p": true, + "param": true, + "plaintext": true, + "pre": true, + "script": true, + "section": true, + "select": true, + "source": true, + "style": true, + "summary": true, + "table": true, + "tbody": true, + "td": true, + "template": true, + "textarea": true, + "tfoot": true, + "th": true, + "thead": true, + "title": true, + "tr": true, + "track": true, + "ul": true, + "wbr": true, + "xmp": true, +} + +func isSpecialElement(element *Node) bool { + switch element.Namespace { + case "", "html": + return isSpecialElementMap[element.Data] + case "svg": + return element.Data == "foreignObject" + } + return false +} diff --git a/src/golang.org/x/net/html/doc.go b/src/golang.org/x/net/html/doc.go new file mode 100644 index 0000000000..94f496874a --- /dev/null +++ b/src/golang.org/x/net/html/doc.go @@ -0,0 +1,106 @@ +// Copyright 2010 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. + +/* +Package html implements an HTML5-compliant tokenizer and parser. + +Tokenization is done by creating a Tokenizer for an io.Reader r. It is the +caller's responsibility to ensure that r provides UTF-8 encoded HTML. + + z := html.NewTokenizer(r) + +Given a Tokenizer z, the HTML is tokenized by repeatedly calling z.Next(), +which parses the next token and returns its type, or an error: + + for { + tt := z.Next() + if tt == html.ErrorToken { + // ... + return ... + } + // Process the current token. + } + +There are two APIs for retrieving the current token. The high-level API is to +call Token; the low-level API is to call Text or TagName / TagAttr. Both APIs +allow optionally calling Raw after Next but before Token, Text, TagName, or +TagAttr. In EBNF notation, the valid call sequence per token is: + + Next {Raw} [ Token | Text | TagName {TagAttr} ] + +Token returns an independent data structure that completely describes a token. +Entities (such as "<") are unescaped, tag names and attribute keys are +lower-cased, and attributes are collected into a []Attribute. For example: + + for { + if z.Next() == html.ErrorToken { + // Returning io.EOF indicates success. + return z.Err() + } + emitToken(z.Token()) + } + +The low-level API performs fewer allocations and copies, but the contents of +the []byte values returned by Text, TagName and TagAttr may change on the next +call to Next. For example, to extract an HTML page's anchor text: + + depth := 0 + for { + tt := z.Next() + switch tt { + case ErrorToken: + return z.Err() + case TextToken: + if depth > 0 { + // emitBytes should copy the []byte it receives, + // if it doesn't process it immediately. + emitBytes(z.Text()) + } + case StartTagToken, EndTagToken: + tn, _ := z.TagName() + if len(tn) == 1 && tn[0] == 'a' { + if tt == StartTagToken { + depth++ + } else { + depth-- + } + } + } + } + +Parsing is done by calling Parse with an io.Reader, which returns the root of +the parse tree (the document element) as a *Node. It is the caller's +responsibility to ensure that the Reader provides UTF-8 encoded HTML. For +example, to process each anchor node in depth-first order: + + doc, err := html.Parse(r) + if err != nil { + // ... + } + var f func(*html.Node) + f = func(n *html.Node) { + if n.Type == html.ElementNode && n.Data == "a" { + // Do something with n... + } + for c := n.FirstChild; c != nil; c = c.NextSibling { + f(c) + } + } + f(doc) + +The relevant specifications include: +https://html.spec.whatwg.org/multipage/syntax.html and +https://html.spec.whatwg.org/multipage/syntax.html#tokenization +*/ +package html // import "golang.org/x/net/html" + +// The tokenization algorithm implemented by this package is not a line-by-line +// transliteration of the relatively verbose state-machine in the WHATWG +// specification. A more direct approach is used instead, where the program +// counter implies the state, such as whether it is tokenizing a tag or a text +// node. Specification compliance is verified by checking expected and actual +// outputs over a test suite rather than aiming for algorithmic fidelity. + +// TODO(nigeltao): Does a DOM API belong in this package or a separate one? +// TODO(nigeltao): How does parsing interact with a JavaScript engine? diff --git a/src/golang.org/x/net/html/doctype.go b/src/golang.org/x/net/html/doctype.go new file mode 100644 index 0000000000..c484e5a94f --- /dev/null +++ b/src/golang.org/x/net/html/doctype.go @@ -0,0 +1,156 @@ +// 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. + +package html + +import ( + "strings" +) + +// parseDoctype parses the data from a DoctypeToken into a name, +// public identifier, and system identifier. It returns a Node whose Type +// is DoctypeNode, whose Data is the name, and which has attributes +// named "system" and "public" for the two identifiers if they were present. +// quirks is whether the document should be parsed in "quirks mode". +func parseDoctype(s string) (n *Node, quirks bool) { + n = &Node{Type: DoctypeNode} + + // Find the name. + space := strings.IndexAny(s, whitespace) + if space == -1 { + space = len(s) + } + n.Data = s[:space] + // The comparison to "html" is case-sensitive. + if n.Data != "html" { + quirks = true + } + n.Data = strings.ToLower(n.Data) + s = strings.TrimLeft(s[space:], whitespace) + + if len(s) < 6 { + // It can't start with "PUBLIC" or "SYSTEM". + // Ignore the rest of the string. + return n, quirks || s != "" + } + + key := strings.ToLower(s[:6]) + s = s[6:] + for key == "public" || key == "system" { + s = strings.TrimLeft(s, whitespace) + if s == "" { + break + } + quote := s[0] + if quote != '"' && quote != '\'' { + break + } + s = s[1:] + q := strings.IndexRune(s, rune(quote)) + var id string + if q == -1 { + id = s + s = "" + } else { + id = s[:q] + s = s[q+1:] + } + n.Attr = append(n.Attr, Attribute{Key: key, Val: id}) + if key == "public" { + key = "system" + } else { + key = "" + } + } + + if key != "" || s != "" { + quirks = true + } else if len(n.Attr) > 0 { + if n.Attr[0].Key == "public" { + public := strings.ToLower(n.Attr[0].Val) + switch public { + case "-//w3o//dtd w3 html strict 3.0//en//", "-/w3d/dtd html 4.0 transitional/en", "html": + quirks = true + default: + for _, q := range quirkyIDs { + if strings.HasPrefix(public, q) { + quirks = true + break + } + } + } + // The following two public IDs only cause quirks mode if there is no system ID. + if len(n.Attr) == 1 && (strings.HasPrefix(public, "-//w3c//dtd html 4.01 frameset//") || + strings.HasPrefix(public, "-//w3c//dtd html 4.01 transitional//")) { + quirks = true + } + } + if lastAttr := n.Attr[len(n.Attr)-1]; lastAttr.Key == "system" && + strings.ToLower(lastAttr.Val) == "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd" { + quirks = true + } + } + + return n, quirks +} + +// quirkyIDs is a list of public doctype identifiers that cause a document +// to be interpreted in quirks mode. The identifiers should be in lower case. +var quirkyIDs = []string{ + "+//silmaril//dtd html pro v0r11 19970101//", + "-//advasoft ltd//dtd html 3.0 aswedit + extensions//", + "-//as//dtd html 3.0 aswedit + extensions//", + "-//ietf//dtd html 2.0 level 1//", + "-//ietf//dtd html 2.0 level 2//", + "-//ietf//dtd html 2.0 strict level 1//", + "-//ietf//dtd html 2.0 strict level 2//", + "-//ietf//dtd html 2.0 strict//", + "-//ietf//dtd html 2.0//", + "-//ietf//dtd html 2.1e//", + "-//ietf//dtd html 3.0//", + "-//ietf//dtd html 3.2 final//", + "-//ietf//dtd html 3.2//", + "-//ietf//dtd html 3//", + "-//ietf//dtd html level 0//", + "-//ietf//dtd html level 1//", + "-//ietf//dtd html level 2//", + "-//ietf//dtd html level 3//", + "-//ietf//dtd html strict level 0//", + "-//ietf//dtd html strict level 1//", + "-//ietf//dtd html strict level 2//", + "-//ietf//dtd html strict level 3//", + "-//ietf//dtd html strict//", + "-//ietf//dtd html//", + "-//metrius//dtd metrius presentational//", + "-//microsoft//dtd internet explorer 2.0 html strict//", + "-//microsoft//dtd internet explorer 2.0 html//", + "-//microsoft//dtd internet explorer 2.0 tables//", + "-//microsoft//dtd internet explorer 3.0 html strict//", + "-//microsoft//dtd internet explorer 3.0 html//", + "-//microsoft//dtd internet explorer 3.0 tables//", + "-//netscape comm. corp.//dtd html//", + "-//netscape comm. corp.//dtd strict html//", + "-//o'reilly and associates//dtd html 2.0//", + "-//o'reilly and associates//dtd html extended 1.0//", + "-//o'reilly and associates//dtd html extended relaxed 1.0//", + "-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//", + "-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//", + "-//spyglass//dtd html 2.0 extended//", + "-//sq//dtd html 2.0 hotmetal + extensions//", + "-//sun microsystems corp.//dtd hotjava html//", + "-//sun microsystems corp.//dtd hotjava strict html//", + "-//w3c//dtd html 3 1995-03-24//", + "-//w3c//dtd html 3.2 draft//", + "-//w3c//dtd html 3.2 final//", + "-//w3c//dtd html 3.2//", + "-//w3c//dtd html 3.2s draft//", + "-//w3c//dtd html 4.0 frameset//", + "-//w3c//dtd html 4.0 transitional//", + "-//w3c//dtd html experimental 19960712//", + "-//w3c//dtd html experimental 970421//", + "-//w3c//dtd w3 html//", + "-//w3o//dtd w3 html 3.0//", + "-//webtechs//dtd mozilla html 2.0//", + "-//webtechs//dtd mozilla html//", +} diff --git a/src/golang.org/x/net/html/entity.go b/src/golang.org/x/net/html/entity.go new file mode 100644 index 0000000000..a50c04c60e --- /dev/null +++ b/src/golang.org/x/net/html/entity.go @@ -0,0 +1,2253 @@ +// Copyright 2010 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. + +package html + +// All entities that do not end with ';' are 6 or fewer bytes long. +const longestEntityWithoutSemicolon = 6 + +// entity is a map from HTML entity names to their values. The semicolon matters: +// https://html.spec.whatwg.org/multipage/syntax.html#named-character-references +// lists both "amp" and "amp;" as two separate entries. +// +// Note that the HTML5 list is larger than the HTML4 list at +// http://www.w3.org/TR/html4/sgml/entities.html +var entity = map[string]rune{ + "AElig;": '\U000000C6', + "AMP;": '\U00000026', + "Aacute;": '\U000000C1', + "Abreve;": '\U00000102', + "Acirc;": '\U000000C2', + "Acy;": '\U00000410', + "Afr;": '\U0001D504', + "Agrave;": '\U000000C0', + "Alpha;": '\U00000391', + "Amacr;": '\U00000100', + "And;": '\U00002A53', + "Aogon;": '\U00000104', + "Aopf;": '\U0001D538', + "ApplyFunction;": '\U00002061', + "Aring;": '\U000000C5', + "Ascr;": '\U0001D49C', + "Assign;": '\U00002254', + "Atilde;": '\U000000C3', + "Auml;": '\U000000C4', + "Backslash;": '\U00002216', + "Barv;": '\U00002AE7', + "Barwed;": '\U00002306', + "Bcy;": '\U00000411', + "Because;": '\U00002235', + "Bernoullis;": '\U0000212C', + "Beta;": '\U00000392', + "Bfr;": '\U0001D505', + "Bopf;": '\U0001D539', + "Breve;": '\U000002D8', + "Bscr;": '\U0000212C', + "Bumpeq;": '\U0000224E', + "CHcy;": '\U00000427', + "COPY;": '\U000000A9', + "Cacute;": '\U00000106', + "Cap;": '\U000022D2', + "CapitalDifferentialD;": '\U00002145', + "Cayleys;": '\U0000212D', + "Ccaron;": '\U0000010C', + "Ccedil;": '\U000000C7', + "Ccirc;": '\U00000108', + "Cconint;": '\U00002230', + "Cdot;": '\U0000010A', + "Cedilla;": '\U000000B8', + "CenterDot;": '\U000000B7', + "Cfr;": '\U0000212D', + "Chi;": '\U000003A7', + "CircleDot;": '\U00002299', + "CircleMinus;": '\U00002296', + "CirclePlus;": '\U00002295', + "CircleTimes;": '\U00002297', + "ClockwiseContourIntegral;": '\U00002232', + "CloseCurlyDoubleQuote;": '\U0000201D', + "CloseCurlyQuote;": '\U00002019', + "Colon;": '\U00002237', + "Colone;": '\U00002A74', + "Congruent;": '\U00002261', + "Conint;": '\U0000222F', + "ContourIntegral;": '\U0000222E', + "Copf;": '\U00002102', + "Coproduct;": '\U00002210', + "CounterClockwiseContourIntegral;": '\U00002233', + "Cross;": '\U00002A2F', + "Cscr;": '\U0001D49E', + "Cup;": '\U000022D3', + "CupCap;": '\U0000224D', + "DD;": '\U00002145', + "DDotrahd;": '\U00002911', + "DJcy;": '\U00000402', + "DScy;": '\U00000405', + "DZcy;": '\U0000040F', + "Dagger;": '\U00002021', + "Darr;": '\U000021A1', + "Dashv;": '\U00002AE4', + "Dcaron;": '\U0000010E', + "Dcy;": '\U00000414', + "Del;": '\U00002207', + "Delta;": '\U00000394', + "Dfr;": '\U0001D507', + "DiacriticalAcute;": '\U000000B4', + "DiacriticalDot;": '\U000002D9', + "DiacriticalDoubleAcute;": '\U000002DD', + "DiacriticalGrave;": '\U00000060', + "DiacriticalTilde;": '\U000002DC', + "Diamond;": '\U000022C4', + "DifferentialD;": '\U00002146', + "Dopf;": '\U0001D53B', + "Dot;": '\U000000A8', + "DotDot;": '\U000020DC', + "DotEqual;": '\U00002250', + "DoubleContourIntegral;": '\U0000222F', + "DoubleDot;": '\U000000A8', + "DoubleDownArrow;": '\U000021D3', + "DoubleLeftArrow;": '\U000021D0', + "DoubleLeftRightArrow;": '\U000021D4', + "DoubleLeftTee;": '\U00002AE4', + "DoubleLongLeftArrow;": '\U000027F8', + "DoubleLongLeftRightArrow;": '\U000027FA', + "DoubleLongRightArrow;": '\U000027F9', + "DoubleRightArrow;": '\U000021D2', + "DoubleRightTee;": '\U000022A8', + "DoubleUpArrow;": '\U000021D1', + "DoubleUpDownArrow;": '\U000021D5', + "DoubleVerticalBar;": '\U00002225', + "DownArrow;": '\U00002193', + "DownArrowBar;": '\U00002913', + "DownArrowUpArrow;": '\U000021F5', + "DownBreve;": '\U00000311', + "DownLeftRightVector;": '\U00002950', + "DownLeftTeeVector;": '\U0000295E', + "DownLeftVector;": '\U000021BD', + "DownLeftVectorBar;": '\U00002956', + "DownRightTeeVector;": '\U0000295F', + "DownRightVector;": '\U000021C1', + "DownRightVectorBar;": '\U00002957', + "DownTee;": '\U000022A4', + "DownTeeArrow;": '\U000021A7', + "Downarrow;": '\U000021D3', + "Dscr;": '\U0001D49F', + "Dstrok;": '\U00000110', + "ENG;": '\U0000014A', + "ETH;": '\U000000D0', + "Eacute;": '\U000000C9', + "Ecaron;": '\U0000011A', + "Ecirc;": '\U000000CA', + "Ecy;": '\U0000042D', + "Edot;": '\U00000116', + "Efr;": '\U0001D508', + "Egrave;": '\U000000C8', + "Element;": '\U00002208', + "Emacr;": '\U00000112', + "EmptySmallSquare;": '\U000025FB', + "EmptyVerySmallSquare;": '\U000025AB', + "Eogon;": '\U00000118', + "Eopf;": '\U0001D53C', + "Epsilon;": '\U00000395', + "Equal;": '\U00002A75', + "EqualTilde;": '\U00002242', + "Equilibrium;": '\U000021CC', + "Escr;": '\U00002130', + "Esim;": '\U00002A73', + "Eta;": '\U00000397', + "Euml;": '\U000000CB', + "Exists;": '\U00002203', + "ExponentialE;": '\U00002147', + "Fcy;": '\U00000424', + "Ffr;": '\U0001D509', + "FilledSmallSquare;": '\U000025FC', + "FilledVerySmallSquare;": '\U000025AA', + "Fopf;": '\U0001D53D', + "ForAll;": '\U00002200', + "Fouriertrf;": '\U00002131', + "Fscr;": '\U00002131', + "GJcy;": '\U00000403', + "GT;": '\U0000003E', + "Gamma;": '\U00000393', + "Gammad;": '\U000003DC', + "Gbreve;": '\U0000011E', + "Gcedil;": '\U00000122', + "Gcirc;": '\U0000011C', + "Gcy;": '\U00000413', + "Gdot;": '\U00000120', + "Gfr;": '\U0001D50A', + "Gg;": '\U000022D9', + "Gopf;": '\U0001D53E', + "GreaterEqual;": '\U00002265', + "GreaterEqualLess;": '\U000022DB', + "GreaterFullEqual;": '\U00002267', + "GreaterGreater;": '\U00002AA2', + "GreaterLess;": '\U00002277', + "GreaterSlantEqual;": '\U00002A7E', + "GreaterTilde;": '\U00002273', + "Gscr;": '\U0001D4A2', + "Gt;": '\U0000226B', + "HARDcy;": '\U0000042A', + "Hacek;": '\U000002C7', + "Hat;": '\U0000005E', + "Hcirc;": '\U00000124', + "Hfr;": '\U0000210C', + "HilbertSpace;": '\U0000210B', + "Hopf;": '\U0000210D', + "HorizontalLine;": '\U00002500', + "Hscr;": '\U0000210B', + "Hstrok;": '\U00000126', + "HumpDownHump;": '\U0000224E', + "HumpEqual;": '\U0000224F', + "IEcy;": '\U00000415', + "IJlig;": '\U00000132', + "IOcy;": '\U00000401', + "Iacute;": '\U000000CD', + "Icirc;": '\U000000CE', + "Icy;": '\U00000418', + "Idot;": '\U00000130', + "Ifr;": '\U00002111', + "Igrave;": '\U000000CC', + "Im;": '\U00002111', + "Imacr;": '\U0000012A', + "ImaginaryI;": '\U00002148', + "Implies;": '\U000021D2', + "Int;": '\U0000222C', + "Integral;": '\U0000222B', + "Intersection;": '\U000022C2', + "InvisibleComma;": '\U00002063', + "InvisibleTimes;": '\U00002062', + "Iogon;": '\U0000012E', + "Iopf;": '\U0001D540', + "Iota;": '\U00000399', + "Iscr;": '\U00002110', + "Itilde;": '\U00000128', + "Iukcy;": '\U00000406', + "Iuml;": '\U000000CF', + "Jcirc;": '\U00000134', + "Jcy;": '\U00000419', + "Jfr;": '\U0001D50D', + "Jopf;": '\U0001D541', + "Jscr;": '\U0001D4A5', + "Jsercy;": '\U00000408', + "Jukcy;": '\U00000404', + "KHcy;": '\U00000425', + "KJcy;": '\U0000040C', + "Kappa;": '\U0000039A', + "Kcedil;": '\U00000136', + "Kcy;": '\U0000041A', + "Kfr;": '\U0001D50E', + "Kopf;": '\U0001D542', + "Kscr;": '\U0001D4A6', + "LJcy;": '\U00000409', + "LT;": '\U0000003C', + "Lacute;": '\U00000139', + "Lambda;": '\U0000039B', + "Lang;": '\U000027EA', + "Laplacetrf;": '\U00002112', + "Larr;": '\U0000219E', + "Lcaron;": '\U0000013D', + "Lcedil;": '\U0000013B', + "Lcy;": '\U0000041B', + "LeftAngleBracket;": '\U000027E8', + "LeftArrow;": '\U00002190', + "LeftArrowBar;": '\U000021E4', + "LeftArrowRightArrow;": '\U000021C6', + "LeftCeiling;": '\U00002308', + "LeftDoubleBracket;": '\U000027E6', + "LeftDownTeeVector;": '\U00002961', + "LeftDownVector;": '\U000021C3', + "LeftDownVectorBar;": '\U00002959', + "LeftFloor;": '\U0000230A', + "LeftRightArrow;": '\U00002194', + "LeftRightVector;": '\U0000294E', + "LeftTee;": '\U000022A3', + "LeftTeeArrow;": '\U000021A4', + "LeftTeeVector;": '\U0000295A', + "LeftTriangle;": '\U000022B2', + "LeftTriangleBar;": '\U000029CF', + "LeftTriangleEqual;": '\U000022B4', + "LeftUpDownVector;": '\U00002951', + "LeftUpTeeVector;": '\U00002960', + "LeftUpVector;": '\U000021BF', + "LeftUpVectorBar;": '\U00002958', + "LeftVector;": '\U000021BC', + "LeftVectorBar;": '\U00002952', + "Leftarrow;": '\U000021D0', + "Leftrightarrow;": '\U000021D4', + "LessEqualGreater;": '\U000022DA', + "LessFullEqual;": '\U00002266', + "LessGreater;": '\U00002276', + "LessLess;": '\U00002AA1', + "LessSlantEqual;": '\U00002A7D', + "LessTilde;": '\U00002272', + "Lfr;": '\U0001D50F', + "Ll;": '\U000022D8', + "Lleftarrow;": '\U000021DA', + "Lmidot;": '\U0000013F', + "LongLeftArrow;": '\U000027F5', + "LongLeftRightArrow;": '\U000027F7', + "LongRightArrow;": '\U000027F6', + "Longleftarrow;": '\U000027F8', + "Longleftrightarrow;": '\U000027FA', + "Longrightarrow;": '\U000027F9', + "Lopf;": '\U0001D543', + "LowerLeftArrow;": '\U00002199', + "LowerRightArrow;": '\U00002198', + "Lscr;": '\U00002112', + "Lsh;": '\U000021B0', + "Lstrok;": '\U00000141', + "Lt;": '\U0000226A', + "Map;": '\U00002905', + "Mcy;": '\U0000041C', + "MediumSpace;": '\U0000205F', + "Mellintrf;": '\U00002133', + "Mfr;": '\U0001D510', + "MinusPlus;": '\U00002213', + "Mopf;": '\U0001D544', + "Mscr;": '\U00002133', + "Mu;": '\U0000039C', + "NJcy;": '\U0000040A', + "Nacute;": '\U00000143', + "Ncaron;": '\U00000147', + "Ncedil;": '\U00000145', + "Ncy;": '\U0000041D', + "NegativeMediumSpace;": '\U0000200B', + "NegativeThickSpace;": '\U0000200B', + "NegativeThinSpace;": '\U0000200B', + "NegativeVeryThinSpace;": '\U0000200B', + "NestedGreaterGreater;": '\U0000226B', + "NestedLessLess;": '\U0000226A', + "NewLine;": '\U0000000A', + "Nfr;": '\U0001D511', + "NoBreak;": '\U00002060', + "NonBreakingSpace;": '\U000000A0', + "Nopf;": '\U00002115', + "Not;": '\U00002AEC', + "NotCongruent;": '\U00002262', + "NotCupCap;": '\U0000226D', + "NotDoubleVerticalBar;": '\U00002226', + "NotElement;": '\U00002209', + "NotEqual;": '\U00002260', + "NotExists;": '\U00002204', + "NotGreater;": '\U0000226F', + "NotGreaterEqual;": '\U00002271', + "NotGreaterLess;": '\U00002279', + "NotGreaterTilde;": '\U00002275', + "NotLeftTriangle;": '\U000022EA', + "NotLeftTriangleEqual;": '\U000022EC', + "NotLess;": '\U0000226E', + "NotLessEqual;": '\U00002270', + "NotLessGreater;": '\U00002278', + "NotLessTilde;": '\U00002274', + "NotPrecedes;": '\U00002280', + "NotPrecedesSlantEqual;": '\U000022E0', + "NotReverseElement;": '\U0000220C', + "NotRightTriangle;": '\U000022EB', + "NotRightTriangleEqual;": '\U000022ED', + "NotSquareSubsetEqual;": '\U000022E2', + "NotSquareSupersetEqual;": '\U000022E3', + "NotSubsetEqual;": '\U00002288', + "NotSucceeds;": '\U00002281', + "NotSucceedsSlantEqual;": '\U000022E1', + "NotSupersetEqual;": '\U00002289', + "NotTilde;": '\U00002241', + "NotTildeEqual;": '\U00002244', + "NotTildeFullEqual;": '\U00002247', + "NotTildeTilde;": '\U00002249', + "NotVerticalBar;": '\U00002224', + "Nscr;": '\U0001D4A9', + "Ntilde;": '\U000000D1', + "Nu;": '\U0000039D', + "OElig;": '\U00000152', + "Oacute;": '\U000000D3', + "Ocirc;": '\U000000D4', + "Ocy;": '\U0000041E', + "Odblac;": '\U00000150', + "Ofr;": '\U0001D512', + "Ograve;": '\U000000D2', + "Omacr;": '\U0000014C', + "Omega;": '\U000003A9', + "Omicron;": '\U0000039F', + "Oopf;": '\U0001D546', + "OpenCurlyDoubleQuote;": '\U0000201C', + "OpenCurlyQuote;": '\U00002018', + "Or;": '\U00002A54', + "Oscr;": '\U0001D4AA', + "Oslash;": '\U000000D8', + "Otilde;": '\U000000D5', + "Otimes;": '\U00002A37', + "Ouml;": '\U000000D6', + "OverBar;": '\U0000203E', + "OverBrace;": '\U000023DE', + "OverBracket;": '\U000023B4', + "OverParenthesis;": '\U000023DC', + "PartialD;": '\U00002202', + "Pcy;": '\U0000041F', + "Pfr;": '\U0001D513', + "Phi;": '\U000003A6', + "Pi;": '\U000003A0', + "PlusMinus;": '\U000000B1', + "Poincareplane;": '\U0000210C', + "Popf;": '\U00002119', + "Pr;": '\U00002ABB', + "Precedes;": '\U0000227A', + "PrecedesEqual;": '\U00002AAF', + "PrecedesSlantEqual;": '\U0000227C', + "PrecedesTilde;": '\U0000227E', + "Prime;": '\U00002033', + "Product;": '\U0000220F', + "Proportion;": '\U00002237', + "Proportional;": '\U0000221D', + "Pscr;": '\U0001D4AB', + "Psi;": '\U000003A8', + "QUOT;": '\U00000022', + "Qfr;": '\U0001D514', + "Qopf;": '\U0000211A', + "Qscr;": '\U0001D4AC', + "RBarr;": '\U00002910', + "REG;": '\U000000AE', + "Racute;": '\U00000154', + "Rang;": '\U000027EB', + "Rarr;": '\U000021A0', + "Rarrtl;": '\U00002916', + "Rcaron;": '\U00000158', + "Rcedil;": '\U00000156', + "Rcy;": '\U00000420', + "Re;": '\U0000211C', + "ReverseElement;": '\U0000220B', + "ReverseEquilibrium;": '\U000021CB', + "ReverseUpEquilibrium;": '\U0000296F', + "Rfr;": '\U0000211C', + "Rho;": '\U000003A1', + "RightAngleBracket;": '\U000027E9', + "RightArrow;": '\U00002192', + "RightArrowBar;": '\U000021E5', + "RightArrowLeftArrow;": '\U000021C4', + "RightCeiling;": '\U00002309', + "RightDoubleBracket;": '\U000027E7', + "RightDownTeeVector;": '\U0000295D', + "RightDownVector;": '\U000021C2', + "RightDownVectorBar;": '\U00002955', + "RightFloor;": '\U0000230B', + "RightTee;": '\U000022A2', + "RightTeeArrow;": '\U000021A6', + "RightTeeVector;": '\U0000295B', + "RightTriangle;": '\U000022B3', + "RightTriangleBar;": '\U000029D0', + "RightTriangleEqual;": '\U000022B5', + "RightUpDownVector;": '\U0000294F', + "RightUpTeeVector;": '\U0000295C', + "RightUpVector;": '\U000021BE', + "RightUpVectorBar;": '\U00002954', + "RightVector;": '\U000021C0', + "RightVectorBar;": '\U00002953', + "Rightarrow;": '\U000021D2', + "Ropf;": '\U0000211D', + "RoundImplies;": '\U00002970', + "Rrightarrow;": '\U000021DB', + "Rscr;": '\U0000211B', + "Rsh;": '\U000021B1', + "RuleDelayed;": '\U000029F4', + "SHCHcy;": '\U00000429', + "SHcy;": '\U00000428', + "SOFTcy;": '\U0000042C', + "Sacute;": '\U0000015A', + "Sc;": '\U00002ABC', + "Scaron;": '\U00000160', + "Scedil;": '\U0000015E', + "Scirc;": '\U0000015C', + "Scy;": '\U00000421', + "Sfr;": '\U0001D516', + "ShortDownArrow;": '\U00002193', + "ShortLeftArrow;": '\U00002190', + "ShortRightArrow;": '\U00002192', + "ShortUpArrow;": '\U00002191', + "Sigma;": '\U000003A3', + "SmallCircle;": '\U00002218', + "Sopf;": '\U0001D54A', + "Sqrt;": '\U0000221A', + "Square;": '\U000025A1', + "SquareIntersection;": '\U00002293', + "SquareSubset;": '\U0000228F', + "SquareSubsetEqual;": '\U00002291', + "SquareSuperset;": '\U00002290', + "SquareSupersetEqual;": '\U00002292', + "SquareUnion;": '\U00002294', + "Sscr;": '\U0001D4AE', + "Star;": '\U000022C6', + "Sub;": '\U000022D0', + "Subset;": '\U000022D0', + "SubsetEqual;": '\U00002286', + "Succeeds;": '\U0000227B', + "SucceedsEqual;": '\U00002AB0', + "SucceedsSlantEqual;": '\U0000227D', + "SucceedsTilde;": '\U0000227F', + "SuchThat;": '\U0000220B', + "Sum;": '\U00002211', + "Sup;": '\U000022D1', + "Superset;": '\U00002283', + "SupersetEqual;": '\U00002287', + "Supset;": '\U000022D1', + "THORN;": '\U000000DE', + "TRADE;": '\U00002122', + "TSHcy;": '\U0000040B', + "TScy;": '\U00000426', + "Tab;": '\U00000009', + "Tau;": '\U000003A4', + "Tcaron;": '\U00000164', + "Tcedil;": '\U00000162', + "Tcy;": '\U00000422', + "Tfr;": '\U0001D517', + "Therefore;": '\U00002234', + "Theta;": '\U00000398', + "ThinSpace;": '\U00002009', + "Tilde;": '\U0000223C', + "TildeEqual;": '\U00002243', + "TildeFullEqual;": '\U00002245', + "TildeTilde;": '\U00002248', + "Topf;": '\U0001D54B', + "TripleDot;": '\U000020DB', + "Tscr;": '\U0001D4AF', + "Tstrok;": '\U00000166', + "Uacute;": '\U000000DA', + "Uarr;": '\U0000219F', + "Uarrocir;": '\U00002949', + "Ubrcy;": '\U0000040E', + "Ubreve;": '\U0000016C', + "Ucirc;": '\U000000DB', + "Ucy;": '\U00000423', + "Udblac;": '\U00000170', + "Ufr;": '\U0001D518', + "Ugrave;": '\U000000D9', + "Umacr;": '\U0000016A', + "UnderBar;": '\U0000005F', + "UnderBrace;": '\U000023DF', + "UnderBracket;": '\U000023B5', + "UnderParenthesis;": '\U000023DD', + "Union;": '\U000022C3', + "UnionPlus;": '\U0000228E', + "Uogon;": '\U00000172', + "Uopf;": '\U0001D54C', + "UpArrow;": '\U00002191', + "UpArrowBar;": '\U00002912', + "UpArrowDownArrow;": '\U000021C5', + "UpDownArrow;": '\U00002195', + "UpEquilibrium;": '\U0000296E', + "UpTee;": '\U000022A5', + "UpTeeArrow;": '\U000021A5', + "Uparrow;": '\U000021D1', + "Updownarrow;": '\U000021D5', + "UpperLeftArrow;": '\U00002196', + "UpperRightArrow;": '\U00002197', + "Upsi;": '\U000003D2', + "Upsilon;": '\U000003A5', + "Uring;": '\U0000016E', + "Uscr;": '\U0001D4B0', + "Utilde;": '\U00000168', + "Uuml;": '\U000000DC', + "VDash;": '\U000022AB', + "Vbar;": '\U00002AEB', + "Vcy;": '\U00000412', + "Vdash;": '\U000022A9', + "Vdashl;": '\U00002AE6', + "Vee;": '\U000022C1', + "Verbar;": '\U00002016', + "Vert;": '\U00002016', + "VerticalBar;": '\U00002223', + "VerticalLine;": '\U0000007C', + "VerticalSeparator;": '\U00002758', + "VerticalTilde;": '\U00002240', + "VeryThinSpace;": '\U0000200A', + "Vfr;": '\U0001D519', + "Vopf;": '\U0001D54D', + "Vscr;": '\U0001D4B1', + "Vvdash;": '\U000022AA', + "Wcirc;": '\U00000174', + "Wedge;": '\U000022C0', + "Wfr;": '\U0001D51A', + "Wopf;": '\U0001D54E', + "Wscr;": '\U0001D4B2', + "Xfr;": '\U0001D51B', + "Xi;": '\U0000039E', + "Xopf;": '\U0001D54F', + "Xscr;": '\U0001D4B3', + "YAcy;": '\U0000042F', + "YIcy;": '\U00000407', + "YUcy;": '\U0000042E', + "Yacute;": '\U000000DD', + "Ycirc;": '\U00000176', + "Ycy;": '\U0000042B', + "Yfr;": '\U0001D51C', + "Yopf;": '\U0001D550', + "Yscr;": '\U0001D4B4', + "Yuml;": '\U00000178', + "ZHcy;": '\U00000416', + "Zacute;": '\U00000179', + "Zcaron;": '\U0000017D', + "Zcy;": '\U00000417', + "Zdot;": '\U0000017B', + "ZeroWidthSpace;": '\U0000200B', + "Zeta;": '\U00000396', + "Zfr;": '\U00002128', + "Zopf;": '\U00002124', + "Zscr;": '\U0001D4B5', + "aacute;": '\U000000E1', + "abreve;": '\U00000103', + "ac;": '\U0000223E', + "acd;": '\U0000223F', + "acirc;": '\U000000E2', + "acute;": '\U000000B4', + "acy;": '\U00000430', + "aelig;": '\U000000E6', + "af;": '\U00002061', + "afr;": '\U0001D51E', + "agrave;": '\U000000E0', + "alefsym;": '\U00002135', + "aleph;": '\U00002135', + "alpha;": '\U000003B1', + "amacr;": '\U00000101', + "amalg;": '\U00002A3F', + "amp;": '\U00000026', + "and;": '\U00002227', + "andand;": '\U00002A55', + "andd;": '\U00002A5C', + "andslope;": '\U00002A58', + "andv;": '\U00002A5A', + "ang;": '\U00002220', + "ange;": '\U000029A4', + "angle;": '\U00002220', + "angmsd;": '\U00002221', + "angmsdaa;": '\U000029A8', + "angmsdab;": '\U000029A9', + "angmsdac;": '\U000029AA', + "angmsdad;": '\U000029AB', + "angmsdae;": '\U000029AC', + "angmsdaf;": '\U000029AD', + "angmsdag;": '\U000029AE', + "angmsdah;": '\U000029AF', + "angrt;": '\U0000221F', + "angrtvb;": '\U000022BE', + "angrtvbd;": '\U0000299D', + "angsph;": '\U00002222', + "angst;": '\U000000C5', + "angzarr;": '\U0000237C', + "aogon;": '\U00000105', + "aopf;": '\U0001D552', + "ap;": '\U00002248', + "apE;": '\U00002A70', + "apacir;": '\U00002A6F', + "ape;": '\U0000224A', + "apid;": '\U0000224B', + "apos;": '\U00000027', + "approx;": '\U00002248', + "approxeq;": '\U0000224A', + "aring;": '\U000000E5', + "ascr;": '\U0001D4B6', + "ast;": '\U0000002A', + "asymp;": '\U00002248', + "asympeq;": '\U0000224D', + "atilde;": '\U000000E3', + "auml;": '\U000000E4', + "awconint;": '\U00002233', + "awint;": '\U00002A11', + "bNot;": '\U00002AED', + "backcong;": '\U0000224C', + "backepsilon;": '\U000003F6', + "backprime;": '\U00002035', + "backsim;": '\U0000223D', + "backsimeq;": '\U000022CD', + "barvee;": '\U000022BD', + "barwed;": '\U00002305', + "barwedge;": '\U00002305', + "bbrk;": '\U000023B5', + "bbrktbrk;": '\U000023B6', + "bcong;": '\U0000224C', + "bcy;": '\U00000431', + "bdquo;": '\U0000201E', + "becaus;": '\U00002235', + "because;": '\U00002235', + "bemptyv;": '\U000029B0', + "bepsi;": '\U000003F6', + "bernou;": '\U0000212C', + "beta;": '\U000003B2', + "beth;": '\U00002136', + "between;": '\U0000226C', + "bfr;": '\U0001D51F', + "bigcap;": '\U000022C2', + "bigcirc;": '\U000025EF', + "bigcup;": '\U000022C3', + "bigodot;": '\U00002A00', + "bigoplus;": '\U00002A01', + "bigotimes;": '\U00002A02', + "bigsqcup;": '\U00002A06', + "bigstar;": '\U00002605', + "bigtriangledown;": '\U000025BD', + "bigtriangleup;": '\U000025B3', + "biguplus;": '\U00002A04', + "bigvee;": '\U000022C1', + "bigwedge;": '\U000022C0', + "bkarow;": '\U0000290D', + "blacklozenge;": '\U000029EB', + "blacksquare;": '\U000025AA', + "blacktriangle;": '\U000025B4', + "blacktriangledown;": '\U000025BE', + "blacktriangleleft;": '\U000025C2', + "blacktriangleright;": '\U000025B8', + "blank;": '\U00002423', + "blk12;": '\U00002592', + "blk14;": '\U00002591', + "blk34;": '\U00002593', + "block;": '\U00002588', + "bnot;": '\U00002310', + "bopf;": '\U0001D553', + "bot;": '\U000022A5', + "bottom;": '\U000022A5', + "bowtie;": '\U000022C8', + "boxDL;": '\U00002557', + "boxDR;": '\U00002554', + "boxDl;": '\U00002556', + "boxDr;": '\U00002553', + "boxH;": '\U00002550', + "boxHD;": '\U00002566', + "boxHU;": '\U00002569', + "boxHd;": '\U00002564', + "boxHu;": '\U00002567', + "boxUL;": '\U0000255D', + "boxUR;": '\U0000255A', + "boxUl;": '\U0000255C', + "boxUr;": '\U00002559', + "boxV;": '\U00002551', + "boxVH;": '\U0000256C', + "boxVL;": '\U00002563', + "boxVR;": '\U00002560', + "boxVh;": '\U0000256B', + "boxVl;": '\U00002562', + "boxVr;": '\U0000255F', + "boxbox;": '\U000029C9', + "boxdL;": '\U00002555', + "boxdR;": '\U00002552', + "boxdl;": '\U00002510', + "boxdr;": '\U0000250C', + "boxh;": '\U00002500', + "boxhD;": '\U00002565', + "boxhU;": '\U00002568', + "boxhd;": '\U0000252C', + "boxhu;": '\U00002534', + "boxminus;": '\U0000229F', + "boxplus;": '\U0000229E', + "boxtimes;": '\U000022A0', + "boxuL;": '\U0000255B', + "boxuR;": '\U00002558', + "boxul;": '\U00002518', + "boxur;": '\U00002514', + "boxv;": '\U00002502', + "boxvH;": '\U0000256A', + "boxvL;": '\U00002561', + "boxvR;": '\U0000255E', + "boxvh;": '\U0000253C', + "boxvl;": '\U00002524', + "boxvr;": '\U0000251C', + "bprime;": '\U00002035', + "breve;": '\U000002D8', + "brvbar;": '\U000000A6', + "bscr;": '\U0001D4B7', + "bsemi;": '\U0000204F', + "bsim;": '\U0000223D', + "bsime;": '\U000022CD', + "bsol;": '\U0000005C', + "bsolb;": '\U000029C5', + "bsolhsub;": '\U000027C8', + "bull;": '\U00002022', + "bullet;": '\U00002022', + "bump;": '\U0000224E', + "bumpE;": '\U00002AAE', + "bumpe;": '\U0000224F', + "bumpeq;": '\U0000224F', + "cacute;": '\U00000107', + "cap;": '\U00002229', + "capand;": '\U00002A44', + "capbrcup;": '\U00002A49', + "capcap;": '\U00002A4B', + "capcup;": '\U00002A47', + "capdot;": '\U00002A40', + "caret;": '\U00002041', + "caron;": '\U000002C7', + "ccaps;": '\U00002A4D', + "ccaron;": '\U0000010D', + "ccedil;": '\U000000E7', + "ccirc;": '\U00000109', + "ccups;": '\U00002A4C', + "ccupssm;": '\U00002A50', + "cdot;": '\U0000010B', + "cedil;": '\U000000B8', + "cemptyv;": '\U000029B2', + "cent;": '\U000000A2', + "centerdot;": '\U000000B7', + "cfr;": '\U0001D520', + "chcy;": '\U00000447', + "check;": '\U00002713', + "checkmark;": '\U00002713', + "chi;": '\U000003C7', + "cir;": '\U000025CB', + "cirE;": '\U000029C3', + "circ;": '\U000002C6', + "circeq;": '\U00002257', + "circlearrowleft;": '\U000021BA', + "circlearrowright;": '\U000021BB', + "circledR;": '\U000000AE', + "circledS;": '\U000024C8', + "circledast;": '\U0000229B', + "circledcirc;": '\U0000229A', + "circleddash;": '\U0000229D', + "cire;": '\U00002257', + "cirfnint;": '\U00002A10', + "cirmid;": '\U00002AEF', + "cirscir;": '\U000029C2', + "clubs;": '\U00002663', + "clubsuit;": '\U00002663', + "colon;": '\U0000003A', + "colone;": '\U00002254', + "coloneq;": '\U00002254', + "comma;": '\U0000002C', + "commat;": '\U00000040', + "comp;": '\U00002201', + "compfn;": '\U00002218', + "complement;": '\U00002201', + "complexes;": '\U00002102', + "cong;": '\U00002245', + "congdot;": '\U00002A6D', + "conint;": '\U0000222E', + "copf;": '\U0001D554', + "coprod;": '\U00002210', + "copy;": '\U000000A9', + "copysr;": '\U00002117', + "crarr;": '\U000021B5', + "cross;": '\U00002717', + "cscr;": '\U0001D4B8', + "csub;": '\U00002ACF', + "csube;": '\U00002AD1', + "csup;": '\U00002AD0', + "csupe;": '\U00002AD2', + "ctdot;": '\U000022EF', + "cudarrl;": '\U00002938', + "cudarrr;": '\U00002935', + "cuepr;": '\U000022DE', + "cuesc;": '\U000022DF', + "cularr;": '\U000021B6', + "cularrp;": '\U0000293D', + "cup;": '\U0000222A', + "cupbrcap;": '\U00002A48', + "cupcap;": '\U00002A46', + "cupcup;": '\U00002A4A', + "cupdot;": '\U0000228D', + "cupor;": '\U00002A45', + "curarr;": '\U000021B7', + "curarrm;": '\U0000293C', + "curlyeqprec;": '\U000022DE', + "curlyeqsucc;": '\U000022DF', + "curlyvee;": '\U000022CE', + "curlywedge;": '\U000022CF', + "curren;": '\U000000A4', + "curvearrowleft;": '\U000021B6', + "curvearrowright;": '\U000021B7', + "cuvee;": '\U000022CE', + "cuwed;": '\U000022CF', + "cwconint;": '\U00002232', + "cwint;": '\U00002231', + "cylcty;": '\U0000232D', + "dArr;": '\U000021D3', + "dHar;": '\U00002965', + "dagger;": '\U00002020', + "daleth;": '\U00002138', + "darr;": '\U00002193', + "dash;": '\U00002010', + "dashv;": '\U000022A3', + "dbkarow;": '\U0000290F', + "dblac;": '\U000002DD', + "dcaron;": '\U0000010F', + "dcy;": '\U00000434', + "dd;": '\U00002146', + "ddagger;": '\U00002021', + "ddarr;": '\U000021CA', + "ddotseq;": '\U00002A77', + "deg;": '\U000000B0', + "delta;": '\U000003B4', + "demptyv;": '\U000029B1', + "dfisht;": '\U0000297F', + "dfr;": '\U0001D521', + "dharl;": '\U000021C3', + "dharr;": '\U000021C2', + "diam;": '\U000022C4', + "diamond;": '\U000022C4', + "diamondsuit;": '\U00002666', + "diams;": '\U00002666', + "die;": '\U000000A8', + "digamma;": '\U000003DD', + "disin;": '\U000022F2', + "div;": '\U000000F7', + "divide;": '\U000000F7', + "divideontimes;": '\U000022C7', + "divonx;": '\U000022C7', + "djcy;": '\U00000452', + "dlcorn;": '\U0000231E', + "dlcrop;": '\U0000230D', + "dollar;": '\U00000024', + "dopf;": '\U0001D555', + "dot;": '\U000002D9', + "doteq;": '\U00002250', + "doteqdot;": '\U00002251', + "dotminus;": '\U00002238', + "dotplus;": '\U00002214', + "dotsquare;": '\U000022A1', + "doublebarwedge;": '\U00002306', + "downarrow;": '\U00002193', + "downdownarrows;": '\U000021CA', + "downharpoonleft;": '\U000021C3', + "downharpoonright;": '\U000021C2', + "drbkarow;": '\U00002910', + "drcorn;": '\U0000231F', + "drcrop;": '\U0000230C', + "dscr;": '\U0001D4B9', + "dscy;": '\U00000455', + "dsol;": '\U000029F6', + "dstrok;": '\U00000111', + "dtdot;": '\U000022F1', + "dtri;": '\U000025BF', + "dtrif;": '\U000025BE', + "duarr;": '\U000021F5', + "duhar;": '\U0000296F', + "dwangle;": '\U000029A6', + "dzcy;": '\U0000045F', + "dzigrarr;": '\U000027FF', + "eDDot;": '\U00002A77', + "eDot;": '\U00002251', + "eacute;": '\U000000E9', + "easter;": '\U00002A6E', + "ecaron;": '\U0000011B', + "ecir;": '\U00002256', + "ecirc;": '\U000000EA', + "ecolon;": '\U00002255', + "ecy;": '\U0000044D', + "edot;": '\U00000117', + "ee;": '\U00002147', + "efDot;": '\U00002252', + "efr;": '\U0001D522', + "eg;": '\U00002A9A', + "egrave;": '\U000000E8', + "egs;": '\U00002A96', + "egsdot;": '\U00002A98', + "el;": '\U00002A99', + "elinters;": '\U000023E7', + "ell;": '\U00002113', + "els;": '\U00002A95', + "elsdot;": '\U00002A97', + "emacr;": '\U00000113', + "empty;": '\U00002205', + "emptyset;": '\U00002205', + "emptyv;": '\U00002205', + "emsp;": '\U00002003', + "emsp13;": '\U00002004', + "emsp14;": '\U00002005', + "eng;": '\U0000014B', + "ensp;": '\U00002002', + "eogon;": '\U00000119', + "eopf;": '\U0001D556', + "epar;": '\U000022D5', + "eparsl;": '\U000029E3', + "eplus;": '\U00002A71', + "epsi;": '\U000003B5', + "epsilon;": '\U000003B5', + "epsiv;": '\U000003F5', + "eqcirc;": '\U00002256', + "eqcolon;": '\U00002255', + "eqsim;": '\U00002242', + "eqslantgtr;": '\U00002A96', + "eqslantless;": '\U00002A95', + "equals;": '\U0000003D', + "equest;": '\U0000225F', + "equiv;": '\U00002261', + "equivDD;": '\U00002A78', + "eqvparsl;": '\U000029E5', + "erDot;": '\U00002253', + "erarr;": '\U00002971', + "escr;": '\U0000212F', + "esdot;": '\U00002250', + "esim;": '\U00002242', + "eta;": '\U000003B7', + "eth;": '\U000000F0', + "euml;": '\U000000EB', + "euro;": '\U000020AC', + "excl;": '\U00000021', + "exist;": '\U00002203', + "expectation;": '\U00002130', + "exponentiale;": '\U00002147', + "fallingdotseq;": '\U00002252', + "fcy;": '\U00000444', + "female;": '\U00002640', + "ffilig;": '\U0000FB03', + "fflig;": '\U0000FB00', + "ffllig;": '\U0000FB04', + "ffr;": '\U0001D523', + "filig;": '\U0000FB01', + "flat;": '\U0000266D', + "fllig;": '\U0000FB02', + "fltns;": '\U000025B1', + "fnof;": '\U00000192', + "fopf;": '\U0001D557', + "forall;": '\U00002200', + "fork;": '\U000022D4', + "forkv;": '\U00002AD9', + "fpartint;": '\U00002A0D', + "frac12;": '\U000000BD', + "frac13;": '\U00002153', + "frac14;": '\U000000BC', + "frac15;": '\U00002155', + "frac16;": '\U00002159', + "frac18;": '\U0000215B', + "frac23;": '\U00002154', + "frac25;": '\U00002156', + "frac34;": '\U000000BE', + "frac35;": '\U00002157', + "frac38;": '\U0000215C', + "frac45;": '\U00002158', + "frac56;": '\U0000215A', + "frac58;": '\U0000215D', + "frac78;": '\U0000215E', + "frasl;": '\U00002044', + "frown;": '\U00002322', + "fscr;": '\U0001D4BB', + "gE;": '\U00002267', + "gEl;": '\U00002A8C', + "gacute;": '\U000001F5', + "gamma;": '\U000003B3', + "gammad;": '\U000003DD', + "gap;": '\U00002A86', + "gbreve;": '\U0000011F', + "gcirc;": '\U0000011D', + "gcy;": '\U00000433', + "gdot;": '\U00000121', + "ge;": '\U00002265', + "gel;": '\U000022DB', + "geq;": '\U00002265', + "geqq;": '\U00002267', + "geqslant;": '\U00002A7E', + "ges;": '\U00002A7E', + "gescc;": '\U00002AA9', + "gesdot;": '\U00002A80', + "gesdoto;": '\U00002A82', + "gesdotol;": '\U00002A84', + "gesles;": '\U00002A94', + "gfr;": '\U0001D524', + "gg;": '\U0000226B', + "ggg;": '\U000022D9', + "gimel;": '\U00002137', + "gjcy;": '\U00000453', + "gl;": '\U00002277', + "glE;": '\U00002A92', + "gla;": '\U00002AA5', + "glj;": '\U00002AA4', + "gnE;": '\U00002269', + "gnap;": '\U00002A8A', + "gnapprox;": '\U00002A8A', + "gne;": '\U00002A88', + "gneq;": '\U00002A88', + "gneqq;": '\U00002269', + "gnsim;": '\U000022E7', + "gopf;": '\U0001D558', + "grave;": '\U00000060', + "gscr;": '\U0000210A', + "gsim;": '\U00002273', + "gsime;": '\U00002A8E', + "gsiml;": '\U00002A90', + "gt;": '\U0000003E', + "gtcc;": '\U00002AA7', + "gtcir;": '\U00002A7A', + "gtdot;": '\U000022D7', + "gtlPar;": '\U00002995', + "gtquest;": '\U00002A7C', + "gtrapprox;": '\U00002A86', + "gtrarr;": '\U00002978', + "gtrdot;": '\U000022D7', + "gtreqless;": '\U000022DB', + "gtreqqless;": '\U00002A8C', + "gtrless;": '\U00002277', + "gtrsim;": '\U00002273', + "hArr;": '\U000021D4', + "hairsp;": '\U0000200A', + "half;": '\U000000BD', + "hamilt;": '\U0000210B', + "hardcy;": '\U0000044A', + "harr;": '\U00002194', + "harrcir;": '\U00002948', + "harrw;": '\U000021AD', + "hbar;": '\U0000210F', + "hcirc;": '\U00000125', + "hearts;": '\U00002665', + "heartsuit;": '\U00002665', + "hellip;": '\U00002026', + "hercon;": '\U000022B9', + "hfr;": '\U0001D525', + "hksearow;": '\U00002925', + "hkswarow;": '\U00002926', + "hoarr;": '\U000021FF', + "homtht;": '\U0000223B', + "hookleftarrow;": '\U000021A9', + "hookrightarrow;": '\U000021AA', + "hopf;": '\U0001D559', + "horbar;": '\U00002015', + "hscr;": '\U0001D4BD', + "hslash;": '\U0000210F', + "hstrok;": '\U00000127', + "hybull;": '\U00002043', + "hyphen;": '\U00002010', + "iacute;": '\U000000ED', + "ic;": '\U00002063', + "icirc;": '\U000000EE', + "icy;": '\U00000438', + "iecy;": '\U00000435', + "iexcl;": '\U000000A1', + "iff;": '\U000021D4', + "ifr;": '\U0001D526', + "igrave;": '\U000000EC', + "ii;": '\U00002148', + "iiiint;": '\U00002A0C', + "iiint;": '\U0000222D', + "iinfin;": '\U000029DC', + "iiota;": '\U00002129', + "ijlig;": '\U00000133', + "imacr;": '\U0000012B', + "image;": '\U00002111', + "imagline;": '\U00002110', + "imagpart;": '\U00002111', + "imath;": '\U00000131', + "imof;": '\U000022B7', + "imped;": '\U000001B5', + "in;": '\U00002208', + "incare;": '\U00002105', + "infin;": '\U0000221E', + "infintie;": '\U000029DD', + "inodot;": '\U00000131', + "int;": '\U0000222B', + "intcal;": '\U000022BA', + "integers;": '\U00002124', + "intercal;": '\U000022BA', + "intlarhk;": '\U00002A17', + "intprod;": '\U00002A3C', + "iocy;": '\U00000451', + "iogon;": '\U0000012F', + "iopf;": '\U0001D55A', + "iota;": '\U000003B9', + "iprod;": '\U00002A3C', + "iquest;": '\U000000BF', + "iscr;": '\U0001D4BE', + "isin;": '\U00002208', + "isinE;": '\U000022F9', + "isindot;": '\U000022F5', + "isins;": '\U000022F4', + "isinsv;": '\U000022F3', + "isinv;": '\U00002208', + "it;": '\U00002062', + "itilde;": '\U00000129', + "iukcy;": '\U00000456', + "iuml;": '\U000000EF', + "jcirc;": '\U00000135', + "jcy;": '\U00000439', + "jfr;": '\U0001D527', + "jmath;": '\U00000237', + "jopf;": '\U0001D55B', + "jscr;": '\U0001D4BF', + "jsercy;": '\U00000458', + "jukcy;": '\U00000454', + "kappa;": '\U000003BA', + "kappav;": '\U000003F0', + "kcedil;": '\U00000137', + "kcy;": '\U0000043A', + "kfr;": '\U0001D528', + "kgreen;": '\U00000138', + "khcy;": '\U00000445', + "kjcy;": '\U0000045C', + "kopf;": '\U0001D55C', + "kscr;": '\U0001D4C0', + "lAarr;": '\U000021DA', + "lArr;": '\U000021D0', + "lAtail;": '\U0000291B', + "lBarr;": '\U0000290E', + "lE;": '\U00002266', + "lEg;": '\U00002A8B', + "lHar;": '\U00002962', + "lacute;": '\U0000013A', + "laemptyv;": '\U000029B4', + "lagran;": '\U00002112', + "lambda;": '\U000003BB', + "lang;": '\U000027E8', + "langd;": '\U00002991', + "langle;": '\U000027E8', + "lap;": '\U00002A85', + "laquo;": '\U000000AB', + "larr;": '\U00002190', + "larrb;": '\U000021E4', + "larrbfs;": '\U0000291F', + "larrfs;": '\U0000291D', + "larrhk;": '\U000021A9', + "larrlp;": '\U000021AB', + "larrpl;": '\U00002939', + "larrsim;": '\U00002973', + "larrtl;": '\U000021A2', + "lat;": '\U00002AAB', + "latail;": '\U00002919', + "late;": '\U00002AAD', + "lbarr;": '\U0000290C', + "lbbrk;": '\U00002772', + "lbrace;": '\U0000007B', + "lbrack;": '\U0000005B', + "lbrke;": '\U0000298B', + "lbrksld;": '\U0000298F', + "lbrkslu;": '\U0000298D', + "lcaron;": '\U0000013E', + "lcedil;": '\U0000013C', + "lceil;": '\U00002308', + "lcub;": '\U0000007B', + "lcy;": '\U0000043B', + "ldca;": '\U00002936', + "ldquo;": '\U0000201C', + "ldquor;": '\U0000201E', + "ldrdhar;": '\U00002967', + "ldrushar;": '\U0000294B', + "ldsh;": '\U000021B2', + "le;": '\U00002264', + "leftarrow;": '\U00002190', + "leftarrowtail;": '\U000021A2', + "leftharpoondown;": '\U000021BD', + "leftharpoonup;": '\U000021BC', + "leftleftarrows;": '\U000021C7', + "leftrightarrow;": '\U00002194', + "leftrightarrows;": '\U000021C6', + "leftrightharpoons;": '\U000021CB', + "leftrightsquigarrow;": '\U000021AD', + "leftthreetimes;": '\U000022CB', + "leg;": '\U000022DA', + "leq;": '\U00002264', + "leqq;": '\U00002266', + "leqslant;": '\U00002A7D', + "les;": '\U00002A7D', + "lescc;": '\U00002AA8', + "lesdot;": '\U00002A7F', + "lesdoto;": '\U00002A81', + "lesdotor;": '\U00002A83', + "lesges;": '\U00002A93', + "lessapprox;": '\U00002A85', + "lessdot;": '\U000022D6', + "lesseqgtr;": '\U000022DA', + "lesseqqgtr;": '\U00002A8B', + "lessgtr;": '\U00002276', + "lesssim;": '\U00002272', + "lfisht;": '\U0000297C', + "lfloor;": '\U0000230A', + "lfr;": '\U0001D529', + "lg;": '\U00002276', + "lgE;": '\U00002A91', + "lhard;": '\U000021BD', + "lharu;": '\U000021BC', + "lharul;": '\U0000296A', + "lhblk;": '\U00002584', + "ljcy;": '\U00000459', + "ll;": '\U0000226A', + "llarr;": '\U000021C7', + "llcorner;": '\U0000231E', + "llhard;": '\U0000296B', + "lltri;": '\U000025FA', + "lmidot;": '\U00000140', + "lmoust;": '\U000023B0', + "lmoustache;": '\U000023B0', + "lnE;": '\U00002268', + "lnap;": '\U00002A89', + "lnapprox;": '\U00002A89', + "lne;": '\U00002A87', + "lneq;": '\U00002A87', + "lneqq;": '\U00002268', + "lnsim;": '\U000022E6', + "loang;": '\U000027EC', + "loarr;": '\U000021FD', + "lobrk;": '\U000027E6', + "longleftarrow;": '\U000027F5', + "longleftrightarrow;": '\U000027F7', + "longmapsto;": '\U000027FC', + "longrightarrow;": '\U000027F6', + "looparrowleft;": '\U000021AB', + "looparrowright;": '\U000021AC', + "lopar;": '\U00002985', + "lopf;": '\U0001D55D', + "loplus;": '\U00002A2D', + "lotimes;": '\U00002A34', + "lowast;": '\U00002217', + "lowbar;": '\U0000005F', + "loz;": '\U000025CA', + "lozenge;": '\U000025CA', + "lozf;": '\U000029EB', + "lpar;": '\U00000028', + "lparlt;": '\U00002993', + "lrarr;": '\U000021C6', + "lrcorner;": '\U0000231F', + "lrhar;": '\U000021CB', + "lrhard;": '\U0000296D', + "lrm;": '\U0000200E', + "lrtri;": '\U000022BF', + "lsaquo;": '\U00002039', + "lscr;": '\U0001D4C1', + "lsh;": '\U000021B0', + "lsim;": '\U00002272', + "lsime;": '\U00002A8D', + "lsimg;": '\U00002A8F', + "lsqb;": '\U0000005B', + "lsquo;": '\U00002018', + "lsquor;": '\U0000201A', + "lstrok;": '\U00000142', + "lt;": '\U0000003C', + "ltcc;": '\U00002AA6', + "ltcir;": '\U00002A79', + "ltdot;": '\U000022D6', + "lthree;": '\U000022CB', + "ltimes;": '\U000022C9', + "ltlarr;": '\U00002976', + "ltquest;": '\U00002A7B', + "ltrPar;": '\U00002996', + "ltri;": '\U000025C3', + "ltrie;": '\U000022B4', + "ltrif;": '\U000025C2', + "lurdshar;": '\U0000294A', + "luruhar;": '\U00002966', + "mDDot;": '\U0000223A', + "macr;": '\U000000AF', + "male;": '\U00002642', + "malt;": '\U00002720', + "maltese;": '\U00002720', + "map;": '\U000021A6', + "mapsto;": '\U000021A6', + "mapstodown;": '\U000021A7', + "mapstoleft;": '\U000021A4', + "mapstoup;": '\U000021A5', + "marker;": '\U000025AE', + "mcomma;": '\U00002A29', + "mcy;": '\U0000043C', + "mdash;": '\U00002014', + "measuredangle;": '\U00002221', + "mfr;": '\U0001D52A', + "mho;": '\U00002127', + "micro;": '\U000000B5', + "mid;": '\U00002223', + "midast;": '\U0000002A', + "midcir;": '\U00002AF0', + "middot;": '\U000000B7', + "minus;": '\U00002212', + "minusb;": '\U0000229F', + "minusd;": '\U00002238', + "minusdu;": '\U00002A2A', + "mlcp;": '\U00002ADB', + "mldr;": '\U00002026', + "mnplus;": '\U00002213', + "models;": '\U000022A7', + "mopf;": '\U0001D55E', + "mp;": '\U00002213', + "mscr;": '\U0001D4C2', + "mstpos;": '\U0000223E', + "mu;": '\U000003BC', + "multimap;": '\U000022B8', + "mumap;": '\U000022B8', + "nLeftarrow;": '\U000021CD', + "nLeftrightarrow;": '\U000021CE', + "nRightarrow;": '\U000021CF', + "nVDash;": '\U000022AF', + "nVdash;": '\U000022AE', + "nabla;": '\U00002207', + "nacute;": '\U00000144', + "nap;": '\U00002249', + "napos;": '\U00000149', + "napprox;": '\U00002249', + "natur;": '\U0000266E', + "natural;": '\U0000266E', + "naturals;": '\U00002115', + "nbsp;": '\U000000A0', + "ncap;": '\U00002A43', + "ncaron;": '\U00000148', + "ncedil;": '\U00000146', + "ncong;": '\U00002247', + "ncup;": '\U00002A42', + "ncy;": '\U0000043D', + "ndash;": '\U00002013', + "ne;": '\U00002260', + "neArr;": '\U000021D7', + "nearhk;": '\U00002924', + "nearr;": '\U00002197', + "nearrow;": '\U00002197', + "nequiv;": '\U00002262', + "nesear;": '\U00002928', + "nexist;": '\U00002204', + "nexists;": '\U00002204', + "nfr;": '\U0001D52B', + "nge;": '\U00002271', + "ngeq;": '\U00002271', + "ngsim;": '\U00002275', + "ngt;": '\U0000226F', + "ngtr;": '\U0000226F', + "nhArr;": '\U000021CE', + "nharr;": '\U000021AE', + "nhpar;": '\U00002AF2', + "ni;": '\U0000220B', + "nis;": '\U000022FC', + "nisd;": '\U000022FA', + "niv;": '\U0000220B', + "njcy;": '\U0000045A', + "nlArr;": '\U000021CD', + "nlarr;": '\U0000219A', + "nldr;": '\U00002025', + "nle;": '\U00002270', + "nleftarrow;": '\U0000219A', + "nleftrightarrow;": '\U000021AE', + "nleq;": '\U00002270', + "nless;": '\U0000226E', + "nlsim;": '\U00002274', + "nlt;": '\U0000226E', + "nltri;": '\U000022EA', + "nltrie;": '\U000022EC', + "nmid;": '\U00002224', + "nopf;": '\U0001D55F', + "not;": '\U000000AC', + "notin;": '\U00002209', + "notinva;": '\U00002209', + "notinvb;": '\U000022F7', + "notinvc;": '\U000022F6', + "notni;": '\U0000220C', + "notniva;": '\U0000220C', + "notnivb;": '\U000022FE', + "notnivc;": '\U000022FD', + "npar;": '\U00002226', + "nparallel;": '\U00002226', + "npolint;": '\U00002A14', + "npr;": '\U00002280', + "nprcue;": '\U000022E0', + "nprec;": '\U00002280', + "nrArr;": '\U000021CF', + "nrarr;": '\U0000219B', + "nrightarrow;": '\U0000219B', + "nrtri;": '\U000022EB', + "nrtrie;": '\U000022ED', + "nsc;": '\U00002281', + "nsccue;": '\U000022E1', + "nscr;": '\U0001D4C3', + "nshortmid;": '\U00002224', + "nshortparallel;": '\U00002226', + "nsim;": '\U00002241', + "nsime;": '\U00002244', + "nsimeq;": '\U00002244', + "nsmid;": '\U00002224', + "nspar;": '\U00002226', + "nsqsube;": '\U000022E2', + "nsqsupe;": '\U000022E3', + "nsub;": '\U00002284', + "nsube;": '\U00002288', + "nsubseteq;": '\U00002288', + "nsucc;": '\U00002281', + "nsup;": '\U00002285', + "nsupe;": '\U00002289', + "nsupseteq;": '\U00002289', + "ntgl;": '\U00002279', + "ntilde;": '\U000000F1', + "ntlg;": '\U00002278', + "ntriangleleft;": '\U000022EA', + "ntrianglelefteq;": '\U000022EC', + "ntriangleright;": '\U000022EB', + "ntrianglerighteq;": '\U000022ED', + "nu;": '\U000003BD', + "num;": '\U00000023', + "numero;": '\U00002116', + "numsp;": '\U00002007', + "nvDash;": '\U000022AD', + "nvHarr;": '\U00002904', + "nvdash;": '\U000022AC', + "nvinfin;": '\U000029DE', + "nvlArr;": '\U00002902', + "nvrArr;": '\U00002903', + "nwArr;": '\U000021D6', + "nwarhk;": '\U00002923', + "nwarr;": '\U00002196', + "nwarrow;": '\U00002196', + "nwnear;": '\U00002927', + "oS;": '\U000024C8', + "oacute;": '\U000000F3', + "oast;": '\U0000229B', + "ocir;": '\U0000229A', + "ocirc;": '\U000000F4', + "ocy;": '\U0000043E', + "odash;": '\U0000229D', + "odblac;": '\U00000151', + "odiv;": '\U00002A38', + "odot;": '\U00002299', + "odsold;": '\U000029BC', + "oelig;": '\U00000153', + "ofcir;": '\U000029BF', + "ofr;": '\U0001D52C', + "ogon;": '\U000002DB', + "ograve;": '\U000000F2', + "ogt;": '\U000029C1', + "ohbar;": '\U000029B5', + "ohm;": '\U000003A9', + "oint;": '\U0000222E', + "olarr;": '\U000021BA', + "olcir;": '\U000029BE', + "olcross;": '\U000029BB', + "oline;": '\U0000203E', + "olt;": '\U000029C0', + "omacr;": '\U0000014D', + "omega;": '\U000003C9', + "omicron;": '\U000003BF', + "omid;": '\U000029B6', + "ominus;": '\U00002296', + "oopf;": '\U0001D560', + "opar;": '\U000029B7', + "operp;": '\U000029B9', + "oplus;": '\U00002295', + "or;": '\U00002228', + "orarr;": '\U000021BB', + "ord;": '\U00002A5D', + "order;": '\U00002134', + "orderof;": '\U00002134', + "ordf;": '\U000000AA', + "ordm;": '\U000000BA', + "origof;": '\U000022B6', + "oror;": '\U00002A56', + "orslope;": '\U00002A57', + "orv;": '\U00002A5B', + "oscr;": '\U00002134', + "oslash;": '\U000000F8', + "osol;": '\U00002298', + "otilde;": '\U000000F5', + "otimes;": '\U00002297', + "otimesas;": '\U00002A36', + "ouml;": '\U000000F6', + "ovbar;": '\U0000233D', + "par;": '\U00002225', + "para;": '\U000000B6', + "parallel;": '\U00002225', + "parsim;": '\U00002AF3', + "parsl;": '\U00002AFD', + "part;": '\U00002202', + "pcy;": '\U0000043F', + "percnt;": '\U00000025', + "period;": '\U0000002E', + "permil;": '\U00002030', + "perp;": '\U000022A5', + "pertenk;": '\U00002031', + "pfr;": '\U0001D52D', + "phi;": '\U000003C6', + "phiv;": '\U000003D5', + "phmmat;": '\U00002133', + "phone;": '\U0000260E', + "pi;": '\U000003C0', + "pitchfork;": '\U000022D4', + "piv;": '\U000003D6', + "planck;": '\U0000210F', + "planckh;": '\U0000210E', + "plankv;": '\U0000210F', + "plus;": '\U0000002B', + "plusacir;": '\U00002A23', + "plusb;": '\U0000229E', + "pluscir;": '\U00002A22', + "plusdo;": '\U00002214', + "plusdu;": '\U00002A25', + "pluse;": '\U00002A72', + "plusmn;": '\U000000B1', + "plussim;": '\U00002A26', + "plustwo;": '\U00002A27', + "pm;": '\U000000B1', + "pointint;": '\U00002A15', + "popf;": '\U0001D561', + "pound;": '\U000000A3', + "pr;": '\U0000227A', + "prE;": '\U00002AB3', + "prap;": '\U00002AB7', + "prcue;": '\U0000227C', + "pre;": '\U00002AAF', + "prec;": '\U0000227A', + "precapprox;": '\U00002AB7', + "preccurlyeq;": '\U0000227C', + "preceq;": '\U00002AAF', + "precnapprox;": '\U00002AB9', + "precneqq;": '\U00002AB5', + "precnsim;": '\U000022E8', + "precsim;": '\U0000227E', + "prime;": '\U00002032', + "primes;": '\U00002119', + "prnE;": '\U00002AB5', + "prnap;": '\U00002AB9', + "prnsim;": '\U000022E8', + "prod;": '\U0000220F', + "profalar;": '\U0000232E', + "profline;": '\U00002312', + "profsurf;": '\U00002313', + "prop;": '\U0000221D', + "propto;": '\U0000221D', + "prsim;": '\U0000227E', + "prurel;": '\U000022B0', + "pscr;": '\U0001D4C5', + "psi;": '\U000003C8', + "puncsp;": '\U00002008', + "qfr;": '\U0001D52E', + "qint;": '\U00002A0C', + "qopf;": '\U0001D562', + "qprime;": '\U00002057', + "qscr;": '\U0001D4C6', + "quaternions;": '\U0000210D', + "quatint;": '\U00002A16', + "quest;": '\U0000003F', + "questeq;": '\U0000225F', + "quot;": '\U00000022', + "rAarr;": '\U000021DB', + "rArr;": '\U000021D2', + "rAtail;": '\U0000291C', + "rBarr;": '\U0000290F', + "rHar;": '\U00002964', + "racute;": '\U00000155', + "radic;": '\U0000221A', + "raemptyv;": '\U000029B3', + "rang;": '\U000027E9', + "rangd;": '\U00002992', + "range;": '\U000029A5', + "rangle;": '\U000027E9', + "raquo;": '\U000000BB', + "rarr;": '\U00002192', + "rarrap;": '\U00002975', + "rarrb;": '\U000021E5', + "rarrbfs;": '\U00002920', + "rarrc;": '\U00002933', + "rarrfs;": '\U0000291E', + "rarrhk;": '\U000021AA', + "rarrlp;": '\U000021AC', + "rarrpl;": '\U00002945', + "rarrsim;": '\U00002974', + "rarrtl;": '\U000021A3', + "rarrw;": '\U0000219D', + "ratail;": '\U0000291A', + "ratio;": '\U00002236', + "rationals;": '\U0000211A', + "rbarr;": '\U0000290D', + "rbbrk;": '\U00002773', + "rbrace;": '\U0000007D', + "rbrack;": '\U0000005D', + "rbrke;": '\U0000298C', + "rbrksld;": '\U0000298E', + "rbrkslu;": '\U00002990', + "rcaron;": '\U00000159', + "rcedil;": '\U00000157', + "rceil;": '\U00002309', + "rcub;": '\U0000007D', + "rcy;": '\U00000440', + "rdca;": '\U00002937', + "rdldhar;": '\U00002969', + "rdquo;": '\U0000201D', + "rdquor;": '\U0000201D', + "rdsh;": '\U000021B3', + "real;": '\U0000211C', + "realine;": '\U0000211B', + "realpart;": '\U0000211C', + "reals;": '\U0000211D', + "rect;": '\U000025AD', + "reg;": '\U000000AE', + "rfisht;": '\U0000297D', + "rfloor;": '\U0000230B', + "rfr;": '\U0001D52F', + "rhard;": '\U000021C1', + "rharu;": '\U000021C0', + "rharul;": '\U0000296C', + "rho;": '\U000003C1', + "rhov;": '\U000003F1', + "rightarrow;": '\U00002192', + "rightarrowtail;": '\U000021A3', + "rightharpoondown;": '\U000021C1', + "rightharpoonup;": '\U000021C0', + "rightleftarrows;": '\U000021C4', + "rightleftharpoons;": '\U000021CC', + "rightrightarrows;": '\U000021C9', + "rightsquigarrow;": '\U0000219D', + "rightthreetimes;": '\U000022CC', + "ring;": '\U000002DA', + "risingdotseq;": '\U00002253', + "rlarr;": '\U000021C4', + "rlhar;": '\U000021CC', + "rlm;": '\U0000200F', + "rmoust;": '\U000023B1', + "rmoustache;": '\U000023B1', + "rnmid;": '\U00002AEE', + "roang;": '\U000027ED', + "roarr;": '\U000021FE', + "robrk;": '\U000027E7', + "ropar;": '\U00002986', + "ropf;": '\U0001D563', + "roplus;": '\U00002A2E', + "rotimes;": '\U00002A35', + "rpar;": '\U00000029', + "rpargt;": '\U00002994', + "rppolint;": '\U00002A12', + "rrarr;": '\U000021C9', + "rsaquo;": '\U0000203A', + "rscr;": '\U0001D4C7', + "rsh;": '\U000021B1', + "rsqb;": '\U0000005D', + "rsquo;": '\U00002019', + "rsquor;": '\U00002019', + "rthree;": '\U000022CC', + "rtimes;": '\U000022CA', + "rtri;": '\U000025B9', + "rtrie;": '\U000022B5', + "rtrif;": '\U000025B8', + "rtriltri;": '\U000029CE', + "ruluhar;": '\U00002968', + "rx;": '\U0000211E', + "sacute;": '\U0000015B', + "sbquo;": '\U0000201A', + "sc;": '\U0000227B', + "scE;": '\U00002AB4', + "scap;": '\U00002AB8', + "scaron;": '\U00000161', + "sccue;": '\U0000227D', + "sce;": '\U00002AB0', + "scedil;": '\U0000015F', + "scirc;": '\U0000015D', + "scnE;": '\U00002AB6', + "scnap;": '\U00002ABA', + "scnsim;": '\U000022E9', + "scpolint;": '\U00002A13', + "scsim;": '\U0000227F', + "scy;": '\U00000441', + "sdot;": '\U000022C5', + "sdotb;": '\U000022A1', + "sdote;": '\U00002A66', + "seArr;": '\U000021D8', + "searhk;": '\U00002925', + "searr;": '\U00002198', + "searrow;": '\U00002198', + "sect;": '\U000000A7', + "semi;": '\U0000003B', + "seswar;": '\U00002929', + "setminus;": '\U00002216', + "setmn;": '\U00002216', + "sext;": '\U00002736', + "sfr;": '\U0001D530', + "sfrown;": '\U00002322', + "sharp;": '\U0000266F', + "shchcy;": '\U00000449', + "shcy;": '\U00000448', + "shortmid;": '\U00002223', + "shortparallel;": '\U00002225', + "shy;": '\U000000AD', + "sigma;": '\U000003C3', + "sigmaf;": '\U000003C2', + "sigmav;": '\U000003C2', + "sim;": '\U0000223C', + "simdot;": '\U00002A6A', + "sime;": '\U00002243', + "simeq;": '\U00002243', + "simg;": '\U00002A9E', + "simgE;": '\U00002AA0', + "siml;": '\U00002A9D', + "simlE;": '\U00002A9F', + "simne;": '\U00002246', + "simplus;": '\U00002A24', + "simrarr;": '\U00002972', + "slarr;": '\U00002190', + "smallsetminus;": '\U00002216', + "smashp;": '\U00002A33', + "smeparsl;": '\U000029E4', + "smid;": '\U00002223', + "smile;": '\U00002323', + "smt;": '\U00002AAA', + "smte;": '\U00002AAC', + "softcy;": '\U0000044C', + "sol;": '\U0000002F', + "solb;": '\U000029C4', + "solbar;": '\U0000233F', + "sopf;": '\U0001D564', + "spades;": '\U00002660', + "spadesuit;": '\U00002660', + "spar;": '\U00002225', + "sqcap;": '\U00002293', + "sqcup;": '\U00002294', + "sqsub;": '\U0000228F', + "sqsube;": '\U00002291', + "sqsubset;": '\U0000228F', + "sqsubseteq;": '\U00002291', + "sqsup;": '\U00002290', + "sqsupe;": '\U00002292', + "sqsupset;": '\U00002290', + "sqsupseteq;": '\U00002292', + "squ;": '\U000025A1', + "square;": '\U000025A1', + "squarf;": '\U000025AA', + "squf;": '\U000025AA', + "srarr;": '\U00002192', + "sscr;": '\U0001D4C8', + "ssetmn;": '\U00002216', + "ssmile;": '\U00002323', + "sstarf;": '\U000022C6', + "star;": '\U00002606', + "starf;": '\U00002605', + "straightepsilon;": '\U000003F5', + "straightphi;": '\U000003D5', + "strns;": '\U000000AF', + "sub;": '\U00002282', + "subE;": '\U00002AC5', + "subdot;": '\U00002ABD', + "sube;": '\U00002286', + "subedot;": '\U00002AC3', + "submult;": '\U00002AC1', + "subnE;": '\U00002ACB', + "subne;": '\U0000228A', + "subplus;": '\U00002ABF', + "subrarr;": '\U00002979', + "subset;": '\U00002282', + "subseteq;": '\U00002286', + "subseteqq;": '\U00002AC5', + "subsetneq;": '\U0000228A', + "subsetneqq;": '\U00002ACB', + "subsim;": '\U00002AC7', + "subsub;": '\U00002AD5', + "subsup;": '\U00002AD3', + "succ;": '\U0000227B', + "succapprox;": '\U00002AB8', + "succcurlyeq;": '\U0000227D', + "succeq;": '\U00002AB0', + "succnapprox;": '\U00002ABA', + "succneqq;": '\U00002AB6', + "succnsim;": '\U000022E9', + "succsim;": '\U0000227F', + "sum;": '\U00002211', + "sung;": '\U0000266A', + "sup;": '\U00002283', + "sup1;": '\U000000B9', + "sup2;": '\U000000B2', + "sup3;": '\U000000B3', + "supE;": '\U00002AC6', + "supdot;": '\U00002ABE', + "supdsub;": '\U00002AD8', + "supe;": '\U00002287', + "supedot;": '\U00002AC4', + "suphsol;": '\U000027C9', + "suphsub;": '\U00002AD7', + "suplarr;": '\U0000297B', + "supmult;": '\U00002AC2', + "supnE;": '\U00002ACC', + "supne;": '\U0000228B', + "supplus;": '\U00002AC0', + "supset;": '\U00002283', + "supseteq;": '\U00002287', + "supseteqq;": '\U00002AC6', + "supsetneq;": '\U0000228B', + "supsetneqq;": '\U00002ACC', + "supsim;": '\U00002AC8', + "supsub;": '\U00002AD4', + "supsup;": '\U00002AD6', + "swArr;": '\U000021D9', + "swarhk;": '\U00002926', + "swarr;": '\U00002199', + "swarrow;": '\U00002199', + "swnwar;": '\U0000292A', + "szlig;": '\U000000DF', + "target;": '\U00002316', + "tau;": '\U000003C4', + "tbrk;": '\U000023B4', + "tcaron;": '\U00000165', + "tcedil;": '\U00000163', + "tcy;": '\U00000442', + "tdot;": '\U000020DB', + "telrec;": '\U00002315', + "tfr;": '\U0001D531', + "there4;": '\U00002234', + "therefore;": '\U00002234', + "theta;": '\U000003B8', + "thetasym;": '\U000003D1', + "thetav;": '\U000003D1', + "thickapprox;": '\U00002248', + "thicksim;": '\U0000223C', + "thinsp;": '\U00002009', + "thkap;": '\U00002248', + "thksim;": '\U0000223C', + "thorn;": '\U000000FE', + "tilde;": '\U000002DC', + "times;": '\U000000D7', + "timesb;": '\U000022A0', + "timesbar;": '\U00002A31', + "timesd;": '\U00002A30', + "tint;": '\U0000222D', + "toea;": '\U00002928', + "top;": '\U000022A4', + "topbot;": '\U00002336', + "topcir;": '\U00002AF1', + "topf;": '\U0001D565', + "topfork;": '\U00002ADA', + "tosa;": '\U00002929', + "tprime;": '\U00002034', + "trade;": '\U00002122', + "triangle;": '\U000025B5', + "triangledown;": '\U000025BF', + "triangleleft;": '\U000025C3', + "trianglelefteq;": '\U000022B4', + "triangleq;": '\U0000225C', + "triangleright;": '\U000025B9', + "trianglerighteq;": '\U000022B5', + "tridot;": '\U000025EC', + "trie;": '\U0000225C', + "triminus;": '\U00002A3A', + "triplus;": '\U00002A39', + "trisb;": '\U000029CD', + "tritime;": '\U00002A3B', + "trpezium;": '\U000023E2', + "tscr;": '\U0001D4C9', + "tscy;": '\U00000446', + "tshcy;": '\U0000045B', + "tstrok;": '\U00000167', + "twixt;": '\U0000226C', + "twoheadleftarrow;": '\U0000219E', + "twoheadrightarrow;": '\U000021A0', + "uArr;": '\U000021D1', + "uHar;": '\U00002963', + "uacute;": '\U000000FA', + "uarr;": '\U00002191', + "ubrcy;": '\U0000045E', + "ubreve;": '\U0000016D', + "ucirc;": '\U000000FB', + "ucy;": '\U00000443', + "udarr;": '\U000021C5', + "udblac;": '\U00000171', + "udhar;": '\U0000296E', + "ufisht;": '\U0000297E', + "ufr;": '\U0001D532', + "ugrave;": '\U000000F9', + "uharl;": '\U000021BF', + "uharr;": '\U000021BE', + "uhblk;": '\U00002580', + "ulcorn;": '\U0000231C', + "ulcorner;": '\U0000231C', + "ulcrop;": '\U0000230F', + "ultri;": '\U000025F8', + "umacr;": '\U0000016B', + "uml;": '\U000000A8', + "uogon;": '\U00000173', + "uopf;": '\U0001D566', + "uparrow;": '\U00002191', + "updownarrow;": '\U00002195', + "upharpoonleft;": '\U000021BF', + "upharpoonright;": '\U000021BE', + "uplus;": '\U0000228E', + "upsi;": '\U000003C5', + "upsih;": '\U000003D2', + "upsilon;": '\U000003C5', + "upuparrows;": '\U000021C8', + "urcorn;": '\U0000231D', + "urcorner;": '\U0000231D', + "urcrop;": '\U0000230E', + "uring;": '\U0000016F', + "urtri;": '\U000025F9', + "uscr;": '\U0001D4CA', + "utdot;": '\U000022F0', + "utilde;": '\U00000169', + "utri;": '\U000025B5', + "utrif;": '\U000025B4', + "uuarr;": '\U000021C8', + "uuml;": '\U000000FC', + "uwangle;": '\U000029A7', + "vArr;": '\U000021D5', + "vBar;": '\U00002AE8', + "vBarv;": '\U00002AE9', + "vDash;": '\U000022A8', + "vangrt;": '\U0000299C', + "varepsilon;": '\U000003F5', + "varkappa;": '\U000003F0', + "varnothing;": '\U00002205', + "varphi;": '\U000003D5', + "varpi;": '\U000003D6', + "varpropto;": '\U0000221D', + "varr;": '\U00002195', + "varrho;": '\U000003F1', + "varsigma;": '\U000003C2', + "vartheta;": '\U000003D1', + "vartriangleleft;": '\U000022B2', + "vartriangleright;": '\U000022B3', + "vcy;": '\U00000432', + "vdash;": '\U000022A2', + "vee;": '\U00002228', + "veebar;": '\U000022BB', + "veeeq;": '\U0000225A', + "vellip;": '\U000022EE', + "verbar;": '\U0000007C', + "vert;": '\U0000007C', + "vfr;": '\U0001D533', + "vltri;": '\U000022B2', + "vopf;": '\U0001D567', + "vprop;": '\U0000221D', + "vrtri;": '\U000022B3', + "vscr;": '\U0001D4CB', + "vzigzag;": '\U0000299A', + "wcirc;": '\U00000175', + "wedbar;": '\U00002A5F', + "wedge;": '\U00002227', + "wedgeq;": '\U00002259', + "weierp;": '\U00002118', + "wfr;": '\U0001D534', + "wopf;": '\U0001D568', + "wp;": '\U00002118', + "wr;": '\U00002240', + "wreath;": '\U00002240', + "wscr;": '\U0001D4CC', + "xcap;": '\U000022C2', + "xcirc;": '\U000025EF', + "xcup;": '\U000022C3', + "xdtri;": '\U000025BD', + "xfr;": '\U0001D535', + "xhArr;": '\U000027FA', + "xharr;": '\U000027F7', + "xi;": '\U000003BE', + "xlArr;": '\U000027F8', + "xlarr;": '\U000027F5', + "xmap;": '\U000027FC', + "xnis;": '\U000022FB', + "xodot;": '\U00002A00', + "xopf;": '\U0001D569', + "xoplus;": '\U00002A01', + "xotime;": '\U00002A02', + "xrArr;": '\U000027F9', + "xrarr;": '\U000027F6', + "xscr;": '\U0001D4CD', + "xsqcup;": '\U00002A06', + "xuplus;": '\U00002A04', + "xutri;": '\U000025B3', + "xvee;": '\U000022C1', + "xwedge;": '\U000022C0', + "yacute;": '\U000000FD', + "yacy;": '\U0000044F', + "ycirc;": '\U00000177', + "ycy;": '\U0000044B', + "yen;": '\U000000A5', + "yfr;": '\U0001D536', + "yicy;": '\U00000457', + "yopf;": '\U0001D56A', + "yscr;": '\U0001D4CE', + "yucy;": '\U0000044E', + "yuml;": '\U000000FF', + "zacute;": '\U0000017A', + "zcaron;": '\U0000017E', + "zcy;": '\U00000437', + "zdot;": '\U0000017C', + "zeetrf;": '\U00002128', + "zeta;": '\U000003B6', + "zfr;": '\U0001D537', + "zhcy;": '\U00000436', + "zigrarr;": '\U000021DD', + "zopf;": '\U0001D56B', + "zscr;": '\U0001D4CF', + "zwj;": '\U0000200D', + "zwnj;": '\U0000200C', + "AElig": '\U000000C6', + "AMP": '\U00000026', + "Aacute": '\U000000C1', + "Acirc": '\U000000C2', + "Agrave": '\U000000C0', + "Aring": '\U000000C5', + "Atilde": '\U000000C3', + "Auml": '\U000000C4', + "COPY": '\U000000A9', + "Ccedil": '\U000000C7', + "ETH": '\U000000D0', + "Eacute": '\U000000C9', + "Ecirc": '\U000000CA', + "Egrave": '\U000000C8', + "Euml": '\U000000CB', + "GT": '\U0000003E', + "Iacute": '\U000000CD', + "Icirc": '\U000000CE', + "Igrave": '\U000000CC', + "Iuml": '\U000000CF', + "LT": '\U0000003C', + "Ntilde": '\U000000D1', + "Oacute": '\U000000D3', + "Ocirc": '\U000000D4', + "Ograve": '\U000000D2', + "Oslash": '\U000000D8', + "Otilde": '\U000000D5', + "Ouml": '\U000000D6', + "QUOT": '\U00000022', + "REG": '\U000000AE', + "THORN": '\U000000DE', + "Uacute": '\U000000DA', + "Ucirc": '\U000000DB', + "Ugrave": '\U000000D9', + "Uuml": '\U000000DC', + "Yacute": '\U000000DD', + "aacute": '\U000000E1', + "acirc": '\U000000E2', + "acute": '\U000000B4', + "aelig": '\U000000E6', + "agrave": '\U000000E0', + "amp": '\U00000026', + "aring": '\U000000E5', + "atilde": '\U000000E3', + "auml": '\U000000E4', + "brvbar": '\U000000A6', + "ccedil": '\U000000E7', + "cedil": '\U000000B8', + "cent": '\U000000A2', + "copy": '\U000000A9', + "curren": '\U000000A4', + "deg": '\U000000B0', + "divide": '\U000000F7', + "eacute": '\U000000E9', + "ecirc": '\U000000EA', + "egrave": '\U000000E8', + "eth": '\U000000F0', + "euml": '\U000000EB', + "frac12": '\U000000BD', + "frac14": '\U000000BC', + "frac34": '\U000000BE', + "gt": '\U0000003E', + "iacute": '\U000000ED', + "icirc": '\U000000EE', + "iexcl": '\U000000A1', + "igrave": '\U000000EC', + "iquest": '\U000000BF', + "iuml": '\U000000EF', + "laquo": '\U000000AB', + "lt": '\U0000003C', + "macr": '\U000000AF', + "micro": '\U000000B5', + "middot": '\U000000B7', + "nbsp": '\U000000A0', + "not": '\U000000AC', + "ntilde": '\U000000F1', + "oacute": '\U000000F3', + "ocirc": '\U000000F4', + "ograve": '\U000000F2', + "ordf": '\U000000AA', + "ordm": '\U000000BA', + "oslash": '\U000000F8', + "otilde": '\U000000F5', + "ouml": '\U000000F6', + "para": '\U000000B6', + "plusmn": '\U000000B1', + "pound": '\U000000A3', + "quot": '\U00000022', + "raquo": '\U000000BB', + "reg": '\U000000AE', + "sect": '\U000000A7', + "shy": '\U000000AD', + "sup1": '\U000000B9', + "sup2": '\U000000B2', + "sup3": '\U000000B3', + "szlig": '\U000000DF', + "thorn": '\U000000FE', + "times": '\U000000D7', + "uacute": '\U000000FA', + "ucirc": '\U000000FB', + "ugrave": '\U000000F9', + "uml": '\U000000A8', + "uuml": '\U000000FC', + "yacute": '\U000000FD', + "yen": '\U000000A5', + "yuml": '\U000000FF', +} + +// HTML entities that are two unicode codepoints. +var entity2 = map[string][2]rune{ + // TODO(nigeltao): Handle replacements that are wider than their names. + // "nLt;": {'\u226A', '\u20D2'}, + // "nGt;": {'\u226B', '\u20D2'}, + "NotEqualTilde;": {'\u2242', '\u0338'}, + "NotGreaterFullEqual;": {'\u2267', '\u0338'}, + "NotGreaterGreater;": {'\u226B', '\u0338'}, + "NotGreaterSlantEqual;": {'\u2A7E', '\u0338'}, + "NotHumpDownHump;": {'\u224E', '\u0338'}, + "NotHumpEqual;": {'\u224F', '\u0338'}, + "NotLeftTriangleBar;": {'\u29CF', '\u0338'}, + "NotLessLess;": {'\u226A', '\u0338'}, + "NotLessSlantEqual;": {'\u2A7D', '\u0338'}, + "NotNestedGreaterGreater;": {'\u2AA2', '\u0338'}, + "NotNestedLessLess;": {'\u2AA1', '\u0338'}, + "NotPrecedesEqual;": {'\u2AAF', '\u0338'}, + "NotRightTriangleBar;": {'\u29D0', '\u0338'}, + "NotSquareSubset;": {'\u228F', '\u0338'}, + "NotSquareSuperset;": {'\u2290', '\u0338'}, + "NotSubset;": {'\u2282', '\u20D2'}, + "NotSucceedsEqual;": {'\u2AB0', '\u0338'}, + "NotSucceedsTilde;": {'\u227F', '\u0338'}, + "NotSuperset;": {'\u2283', '\u20D2'}, + "ThickSpace;": {'\u205F', '\u200A'}, + "acE;": {'\u223E', '\u0333'}, + "bne;": {'\u003D', '\u20E5'}, + "bnequiv;": {'\u2261', '\u20E5'}, + "caps;": {'\u2229', '\uFE00'}, + "cups;": {'\u222A', '\uFE00'}, + "fjlig;": {'\u0066', '\u006A'}, + "gesl;": {'\u22DB', '\uFE00'}, + "gvertneqq;": {'\u2269', '\uFE00'}, + "gvnE;": {'\u2269', '\uFE00'}, + "lates;": {'\u2AAD', '\uFE00'}, + "lesg;": {'\u22DA', '\uFE00'}, + "lvertneqq;": {'\u2268', '\uFE00'}, + "lvnE;": {'\u2268', '\uFE00'}, + "nGg;": {'\u22D9', '\u0338'}, + "nGtv;": {'\u226B', '\u0338'}, + "nLl;": {'\u22D8', '\u0338'}, + "nLtv;": {'\u226A', '\u0338'}, + "nang;": {'\u2220', '\u20D2'}, + "napE;": {'\u2A70', '\u0338'}, + "napid;": {'\u224B', '\u0338'}, + "nbump;": {'\u224E', '\u0338'}, + "nbumpe;": {'\u224F', '\u0338'}, + "ncongdot;": {'\u2A6D', '\u0338'}, + "nedot;": {'\u2250', '\u0338'}, + "nesim;": {'\u2242', '\u0338'}, + "ngE;": {'\u2267', '\u0338'}, + "ngeqq;": {'\u2267', '\u0338'}, + "ngeqslant;": {'\u2A7E', '\u0338'}, + "nges;": {'\u2A7E', '\u0338'}, + "nlE;": {'\u2266', '\u0338'}, + "nleqq;": {'\u2266', '\u0338'}, + "nleqslant;": {'\u2A7D', '\u0338'}, + "nles;": {'\u2A7D', '\u0338'}, + "notinE;": {'\u22F9', '\u0338'}, + "notindot;": {'\u22F5', '\u0338'}, + "nparsl;": {'\u2AFD', '\u20E5'}, + "npart;": {'\u2202', '\u0338'}, + "npre;": {'\u2AAF', '\u0338'}, + "npreceq;": {'\u2AAF', '\u0338'}, + "nrarrc;": {'\u2933', '\u0338'}, + "nrarrw;": {'\u219D', '\u0338'}, + "nsce;": {'\u2AB0', '\u0338'}, + "nsubE;": {'\u2AC5', '\u0338'}, + "nsubset;": {'\u2282', '\u20D2'}, + "nsubseteqq;": {'\u2AC5', '\u0338'}, + "nsucceq;": {'\u2AB0', '\u0338'}, + "nsupE;": {'\u2AC6', '\u0338'}, + "nsupset;": {'\u2283', '\u20D2'}, + "nsupseteqq;": {'\u2AC6', '\u0338'}, + "nvap;": {'\u224D', '\u20D2'}, + "nvge;": {'\u2265', '\u20D2'}, + "nvgt;": {'\u003E', '\u20D2'}, + "nvle;": {'\u2264', '\u20D2'}, + "nvlt;": {'\u003C', '\u20D2'}, + "nvltrie;": {'\u22B4', '\u20D2'}, + "nvrtrie;": {'\u22B5', '\u20D2'}, + "nvsim;": {'\u223C', '\u20D2'}, + "race;": {'\u223D', '\u0331'}, + "smtes;": {'\u2AAC', '\uFE00'}, + "sqcaps;": {'\u2293', '\uFE00'}, + "sqcups;": {'\u2294', '\uFE00'}, + "varsubsetneq;": {'\u228A', '\uFE00'}, + "varsubsetneqq;": {'\u2ACB', '\uFE00'}, + "varsupsetneq;": {'\u228B', '\uFE00'}, + "varsupsetneqq;": {'\u2ACC', '\uFE00'}, + "vnsub;": {'\u2282', '\u20D2'}, + "vnsup;": {'\u2283', '\u20D2'}, + "vsubnE;": {'\u2ACB', '\uFE00'}, + "vsubne;": {'\u228A', '\uFE00'}, + "vsupnE;": {'\u2ACC', '\uFE00'}, + "vsupne;": {'\u228B', '\uFE00'}, +} diff --git a/src/golang.org/x/net/html/entity_test.go b/src/golang.org/x/net/html/entity_test.go new file mode 100644 index 0000000000..b53f866fa2 --- /dev/null +++ b/src/golang.org/x/net/html/entity_test.go @@ -0,0 +1,29 @@ +// Copyright 2010 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. + +package html + +import ( + "testing" + "unicode/utf8" +) + +func TestEntityLength(t *testing.T) { + // We verify that the length of UTF-8 encoding of each value is <= 1 + len(key). + // The +1 comes from the leading "&". This property implies that the length of + // unescaped text is <= the length of escaped text. + for k, v := range entity { + if 1+len(k) < utf8.RuneLen(v) { + t.Error("escaped entity &" + k + " is shorter than its UTF-8 encoding " + string(v)) + } + if len(k) > longestEntityWithoutSemicolon && k[len(k)-1] != ';' { + t.Errorf("entity name %s is %d characters, but longestEntityWithoutSemicolon=%d", k, len(k), longestEntityWithoutSemicolon) + } + } + for k, v := range entity2 { + if 1+len(k) < utf8.RuneLen(v[0])+utf8.RuneLen(v[1]) { + t.Error("escaped entity &" + k + " is shorter than its UTF-8 encoding " + string(v[0]) + string(v[1])) + } + } +} diff --git a/src/golang.org/x/net/html/escape.go b/src/golang.org/x/net/html/escape.go new file mode 100644 index 0000000000..d856139620 --- /dev/null +++ b/src/golang.org/x/net/html/escape.go @@ -0,0 +1,258 @@ +// Copyright 2010 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. + +package html + +import ( + "bytes" + "strings" + "unicode/utf8" +) + +// These replacements permit compatibility with old numeric entities that +// assumed Windows-1252 encoding. +// https://html.spec.whatwg.org/multipage/syntax.html#consume-a-character-reference +var replacementTable = [...]rune{ + '\u20AC', // First entry is what 0x80 should be replaced with. + '\u0081', + '\u201A', + '\u0192', + '\u201E', + '\u2026', + '\u2020', + '\u2021', + '\u02C6', + '\u2030', + '\u0160', + '\u2039', + '\u0152', + '\u008D', + '\u017D', + '\u008F', + '\u0090', + '\u2018', + '\u2019', + '\u201C', + '\u201D', + '\u2022', + '\u2013', + '\u2014', + '\u02DC', + '\u2122', + '\u0161', + '\u203A', + '\u0153', + '\u009D', + '\u017E', + '\u0178', // Last entry is 0x9F. + // 0x00->'\uFFFD' is handled programmatically. + // 0x0D->'\u000D' is a no-op. +} + +// unescapeEntity reads an entity like "<" from b[src:] and writes the +// corresponding "<" to b[dst:], returning the incremented dst and src cursors. +// Precondition: b[src] == '&' && dst <= src. +// attribute should be true if parsing an attribute value. +func unescapeEntity(b []byte, dst, src int, attribute bool) (dst1, src1 int) { + // https://html.spec.whatwg.org/multipage/syntax.html#consume-a-character-reference + + // i starts at 1 because we already know that s[0] == '&'. + i, s := 1, b[src:] + + if len(s) <= 1 { + b[dst] = b[src] + return dst + 1, src + 1 + } + + if s[i] == '#' { + if len(s) <= 3 { // We need to have at least "&#.". + b[dst] = b[src] + return dst + 1, src + 1 + } + i++ + c := s[i] + hex := false + if c == 'x' || c == 'X' { + hex = true + i++ + } + + x := '\x00' + for i < len(s) { + c = s[i] + i++ + if hex { + if '0' <= c && c <= '9' { + x = 16*x + rune(c) - '0' + continue + } else if 'a' <= c && c <= 'f' { + x = 16*x + rune(c) - 'a' + 10 + continue + } else if 'A' <= c && c <= 'F' { + x = 16*x + rune(c) - 'A' + 10 + continue + } + } else if '0' <= c && c <= '9' { + x = 10*x + rune(c) - '0' + continue + } + if c != ';' { + i-- + } + break + } + + if i <= 3 { // No characters matched. + b[dst] = b[src] + return dst + 1, src + 1 + } + + if 0x80 <= x && x <= 0x9F { + // Replace characters from Windows-1252 with UTF-8 equivalents. + x = replacementTable[x-0x80] + } else if x == 0 || (0xD800 <= x && x <= 0xDFFF) || x > 0x10FFFF { + // Replace invalid characters with the replacement character. + x = '\uFFFD' + } + + return dst + utf8.EncodeRune(b[dst:], x), src + i + } + + // Consume the maximum number of characters possible, with the + // consumed characters matching one of the named references. + + for i < len(s) { + c := s[i] + i++ + // Lower-cased characters are more common in entities, so we check for them first. + if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' { + continue + } + if c != ';' { + i-- + } + break + } + + entityName := string(s[1:i]) + if entityName == "" { + // No-op. + } else if attribute && entityName[len(entityName)-1] != ';' && len(s) > i && s[i] == '=' { + // No-op. + } else if x := entity[entityName]; x != 0 { + return dst + utf8.EncodeRune(b[dst:], x), src + i + } else if x := entity2[entityName]; x[0] != 0 { + dst1 := dst + utf8.EncodeRune(b[dst:], x[0]) + return dst1 + utf8.EncodeRune(b[dst1:], x[1]), src + i + } else if !attribute { + maxLen := len(entityName) - 1 + if maxLen > longestEntityWithoutSemicolon { + maxLen = longestEntityWithoutSemicolon + } + for j := maxLen; j > 1; j-- { + if x := entity[entityName[:j]]; x != 0 { + return dst + utf8.EncodeRune(b[dst:], x), src + j + 1 + } + } + } + + dst1, src1 = dst+i, src+i + copy(b[dst:dst1], b[src:src1]) + return dst1, src1 +} + +// unescape unescapes b's entities in-place, so that "a<b" becomes "a': + esc = ">" + case '"': + // """ is shorter than """. + esc = """ + case '\r': + esc = " " + default: + panic("unrecognized escape character") + } + s = s[i+1:] + if _, err := w.WriteString(esc); err != nil { + return err + } + i = strings.IndexAny(s, escapedChars) + } + _, err := w.WriteString(s) + return err +} + +// EscapeString escapes special characters like "<" to become "<". It +// escapes only five such characters: <, >, &, ' and ". +// UnescapeString(EscapeString(s)) == s always holds, but the converse isn't +// always true. +func EscapeString(s string) string { + if strings.IndexAny(s, escapedChars) == -1 { + return s + } + var buf bytes.Buffer + escape(&buf, s) + return buf.String() +} + +// UnescapeString unescapes entities like "<" to become "<". It unescapes a +// larger range of entities than EscapeString escapes. For example, "á" +// unescapes to "á", as does "á" and "&xE1;". +// UnescapeString(EscapeString(s)) == s always holds, but the converse isn't +// always true. +func UnescapeString(s string) string { + for _, c := range s { + if c == '&' { + return string(unescape([]byte(s), false)) + } + } + return s +} diff --git a/src/golang.org/x/net/html/escape_test.go b/src/golang.org/x/net/html/escape_test.go new file mode 100644 index 0000000000..b405d4b4a7 --- /dev/null +++ b/src/golang.org/x/net/html/escape_test.go @@ -0,0 +1,97 @@ +// 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. + +package html + +import "testing" + +type unescapeTest struct { + // A short description of the test case. + desc string + // The HTML text. + html string + // The unescaped text. + unescaped string +} + +var unescapeTests = []unescapeTest{ + // Handle no entities. + { + "copy", + "A\ttext\nstring", + "A\ttext\nstring", + }, + // Handle simple named entities. + { + "simple", + "& > <", + "& > <", + }, + // Handle hitting the end of the string. + { + "stringEnd", + "& &", + "& &", + }, + // Handle entities with two codepoints. + { + "multiCodepoint", + "text ⋛︀ blah", + "text \u22db\ufe00 blah", + }, + // Handle decimal numeric entities. + { + "decimalEntity", + "Delta = Δ ", + "Delta = Δ ", + }, + // Handle hexadecimal numeric entities. + { + "hexadecimalEntity", + "Lambda = λ = λ ", + "Lambda = λ = λ ", + }, + // Handle numeric early termination. + { + "numericEnds", + "&# &#x €43 © = ©f = ©", + "&# &#x €43 © = ©f = ©", + }, + // Handle numeric ISO-8859-1 entity replacements. + { + "numericReplacements", + "Footnote‡", + "Footnote‡", + }, +} + +func TestUnescape(t *testing.T) { + for _, tt := range unescapeTests { + unescaped := UnescapeString(tt.html) + if unescaped != tt.unescaped { + t.Errorf("TestUnescape %s: want %q, got %q", tt.desc, tt.unescaped, unescaped) + } + } +} + +func TestUnescapeEscape(t *testing.T) { + ss := []string{ + ``, + `abc def`, + `a & b`, + `a&b`, + `a & b`, + `"`, + `"`, + `"<&>"`, + `"<&>"`, + `3&5==1 && 0<1, "0<1", a+acute=á`, + `The special characters are: <, >, &, ' and "`, + } + for _, s := range ss { + if got := UnescapeString(EscapeString(s)); got != s { + t.Errorf("got %q want %q", got, s) + } + } +} diff --git a/src/golang.org/x/net/html/example_test.go b/src/golang.org/x/net/html/example_test.go new file mode 100644 index 0000000000..0b06ed7730 --- /dev/null +++ b/src/golang.org/x/net/html/example_test.go @@ -0,0 +1,40 @@ +// Copyright 2012 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. + +// This example demonstrates parsing HTML data and walking the resulting tree. +package html_test + +import ( + "fmt" + "log" + "strings" + + "golang.org/x/net/html" +) + +func ExampleParse() { + s := `

Links:

` + doc, err := html.Parse(strings.NewReader(s)) + if err != nil { + log.Fatal(err) + } + var f func(*html.Node) + f = func(n *html.Node) { + if n.Type == html.ElementNode && n.Data == "a" { + for _, a := range n.Attr { + if a.Key == "href" { + fmt.Println(a.Val) + break + } + } + } + for c := n.FirstChild; c != nil; c = c.NextSibling { + f(c) + } + } + f(doc) + // Output: + // foo + // /bar/baz +} diff --git a/src/golang.org/x/net/html/foreign.go b/src/golang.org/x/net/html/foreign.go new file mode 100644 index 0000000000..d3b3844099 --- /dev/null +++ b/src/golang.org/x/net/html/foreign.go @@ -0,0 +1,226 @@ +// 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. + +package html + +import ( + "strings" +) + +func adjustAttributeNames(aa []Attribute, nameMap map[string]string) { + for i := range aa { + if newName, ok := nameMap[aa[i].Key]; ok { + aa[i].Key = newName + } + } +} + +func adjustForeignAttributes(aa []Attribute) { + for i, a := range aa { + if a.Key == "" || a.Key[0] != 'x' { + continue + } + switch a.Key { + case "xlink:actuate", "xlink:arcrole", "xlink:href", "xlink:role", "xlink:show", + "xlink:title", "xlink:type", "xml:base", "xml:lang", "xml:space", "xmlns:xlink": + j := strings.Index(a.Key, ":") + aa[i].Namespace = a.Key[:j] + aa[i].Key = a.Key[j+1:] + } + } +} + +func htmlIntegrationPoint(n *Node) bool { + if n.Type != ElementNode { + return false + } + switch n.Namespace { + case "math": + if n.Data == "annotation-xml" { + for _, a := range n.Attr { + if a.Key == "encoding" { + val := strings.ToLower(a.Val) + if val == "text/html" || val == "application/xhtml+xml" { + return true + } + } + } + } + case "svg": + switch n.Data { + case "desc", "foreignObject", "title": + return true + } + } + return false +} + +func mathMLTextIntegrationPoint(n *Node) bool { + if n.Namespace != "math" { + return false + } + switch n.Data { + case "mi", "mo", "mn", "ms", "mtext": + return true + } + return false +} + +// Section 12.2.5.5. +var breakout = map[string]bool{ + "b": true, + "big": true, + "blockquote": true, + "body": true, + "br": true, + "center": true, + "code": true, + "dd": true, + "div": true, + "dl": true, + "dt": true, + "em": true, + "embed": true, + "h1": true, + "h2": true, + "h3": true, + "h4": true, + "h5": true, + "h6": true, + "head": true, + "hr": true, + "i": true, + "img": true, + "li": true, + "listing": true, + "menu": true, + "meta": true, + "nobr": true, + "ol": true, + "p": true, + "pre": true, + "ruby": true, + "s": true, + "small": true, + "span": true, + "strong": true, + "strike": true, + "sub": true, + "sup": true, + "table": true, + "tt": true, + "u": true, + "ul": true, + "var": true, +} + +// Section 12.2.5.5. +var svgTagNameAdjustments = map[string]string{ + "altglyph": "altGlyph", + "altglyphdef": "altGlyphDef", + "altglyphitem": "altGlyphItem", + "animatecolor": "animateColor", + "animatemotion": "animateMotion", + "animatetransform": "animateTransform", + "clippath": "clipPath", + "feblend": "feBlend", + "fecolormatrix": "feColorMatrix", + "fecomponenttransfer": "feComponentTransfer", + "fecomposite": "feComposite", + "feconvolvematrix": "feConvolveMatrix", + "fediffuselighting": "feDiffuseLighting", + "fedisplacementmap": "feDisplacementMap", + "fedistantlight": "feDistantLight", + "feflood": "feFlood", + "fefunca": "feFuncA", + "fefuncb": "feFuncB", + "fefuncg": "feFuncG", + "fefuncr": "feFuncR", + "fegaussianblur": "feGaussianBlur", + "feimage": "feImage", + "femerge": "feMerge", + "femergenode": "feMergeNode", + "femorphology": "feMorphology", + "feoffset": "feOffset", + "fepointlight": "fePointLight", + "fespecularlighting": "feSpecularLighting", + "fespotlight": "feSpotLight", + "fetile": "feTile", + "feturbulence": "feTurbulence", + "foreignobject": "foreignObject", + "glyphref": "glyphRef", + "lineargradient": "linearGradient", + "radialgradient": "radialGradient", + "textpath": "textPath", +} + +// Section 12.2.5.1 +var mathMLAttributeAdjustments = map[string]string{ + "definitionurl": "definitionURL", +} + +var svgAttributeAdjustments = map[string]string{ + "attributename": "attributeName", + "attributetype": "attributeType", + "basefrequency": "baseFrequency", + "baseprofile": "baseProfile", + "calcmode": "calcMode", + "clippathunits": "clipPathUnits", + "contentscripttype": "contentScriptType", + "contentstyletype": "contentStyleType", + "diffuseconstant": "diffuseConstant", + "edgemode": "edgeMode", + "externalresourcesrequired": "externalResourcesRequired", + "filterres": "filterRes", + "filterunits": "filterUnits", + "glyphref": "glyphRef", + "gradienttransform": "gradientTransform", + "gradientunits": "gradientUnits", + "kernelmatrix": "kernelMatrix", + "kernelunitlength": "kernelUnitLength", + "keypoints": "keyPoints", + "keysplines": "keySplines", + "keytimes": "keyTimes", + "lengthadjust": "lengthAdjust", + "limitingconeangle": "limitingConeAngle", + "markerheight": "markerHeight", + "markerunits": "markerUnits", + "markerwidth": "markerWidth", + "maskcontentunits": "maskContentUnits", + "maskunits": "maskUnits", + "numoctaves": "numOctaves", + "pathlength": "pathLength", + "patterncontentunits": "patternContentUnits", + "patterntransform": "patternTransform", + "patternunits": "patternUnits", + "pointsatx": "pointsAtX", + "pointsaty": "pointsAtY", + "pointsatz": "pointsAtZ", + "preservealpha": "preserveAlpha", + "preserveaspectratio": "preserveAspectRatio", + "primitiveunits": "primitiveUnits", + "refx": "refX", + "refy": "refY", + "repeatcount": "repeatCount", + "repeatdur": "repeatDur", + "requiredextensions": "requiredExtensions", + "requiredfeatures": "requiredFeatures", + "specularconstant": "specularConstant", + "specularexponent": "specularExponent", + "spreadmethod": "spreadMethod", + "startoffset": "startOffset", + "stddeviation": "stdDeviation", + "stitchtiles": "stitchTiles", + "surfacescale": "surfaceScale", + "systemlanguage": "systemLanguage", + "tablevalues": "tableValues", + "targetx": "targetX", + "targety": "targetY", + "textlength": "textLength", + "viewbox": "viewBox", + "viewtarget": "viewTarget", + "xchannelselector": "xChannelSelector", + "ychannelselector": "yChannelSelector", + "zoomandpan": "zoomAndPan", +} diff --git a/src/golang.org/x/net/html/node.go b/src/golang.org/x/net/html/node.go new file mode 100644 index 0000000000..26b657aec8 --- /dev/null +++ b/src/golang.org/x/net/html/node.go @@ -0,0 +1,193 @@ +// 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. + +package html + +import ( + "golang.org/x/net/html/atom" +) + +// A NodeType is the type of a Node. +type NodeType uint32 + +const ( + ErrorNode NodeType = iota + TextNode + DocumentNode + ElementNode + CommentNode + DoctypeNode + scopeMarkerNode +) + +// Section 12.2.3.3 says "scope markers are inserted when entering applet +// elements, buttons, object elements, marquees, table cells, and table +// captions, and are used to prevent formatting from 'leaking'". +var scopeMarker = Node{Type: scopeMarkerNode} + +// A Node consists of a NodeType and some Data (tag name for element nodes, +// content for text) and are part of a tree of Nodes. Element nodes may also +// have a Namespace and contain a slice of Attributes. Data is unescaped, so +// that it looks like "a 0 { + return (*s)[i-1] + } + return nil +} + +// index returns the index of the top-most occurrence of n in the stack, or -1 +// if n is not present. +func (s *nodeStack) index(n *Node) int { + for i := len(*s) - 1; i >= 0; i-- { + if (*s)[i] == n { + return i + } + } + return -1 +} + +// insert inserts a node at the given index. +func (s *nodeStack) insert(i int, n *Node) { + (*s) = append(*s, nil) + copy((*s)[i+1:], (*s)[i:]) + (*s)[i] = n +} + +// remove removes a node from the stack. It is a no-op if n is not present. +func (s *nodeStack) remove(n *Node) { + i := s.index(n) + if i == -1 { + return + } + copy((*s)[i:], (*s)[i+1:]) + j := len(*s) - 1 + (*s)[j] = nil + *s = (*s)[:j] +} diff --git a/src/golang.org/x/net/html/node_test.go b/src/golang.org/x/net/html/node_test.go new file mode 100644 index 0000000000..471102f3a2 --- /dev/null +++ b/src/golang.org/x/net/html/node_test.go @@ -0,0 +1,146 @@ +// Copyright 2010 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. + +package html + +import ( + "fmt" +) + +// checkTreeConsistency checks that a node and its descendants are all +// consistent in their parent/child/sibling relationships. +func checkTreeConsistency(n *Node) error { + return checkTreeConsistency1(n, 0) +} + +func checkTreeConsistency1(n *Node, depth int) error { + if depth == 1e4 { + return fmt.Errorf("html: tree looks like it contains a cycle") + } + if err := checkNodeConsistency(n); err != nil { + return err + } + for c := n.FirstChild; c != nil; c = c.NextSibling { + if err := checkTreeConsistency1(c, depth+1); err != nil { + return err + } + } + return nil +} + +// checkNodeConsistency checks that a node's parent/child/sibling relationships +// are consistent. +func checkNodeConsistency(n *Node) error { + if n == nil { + return nil + } + + nParent := 0 + for p := n.Parent; p != nil; p = p.Parent { + nParent++ + if nParent == 1e4 { + return fmt.Errorf("html: parent list looks like an infinite loop") + } + } + + nForward := 0 + for c := n.FirstChild; c != nil; c = c.NextSibling { + nForward++ + if nForward == 1e6 { + return fmt.Errorf("html: forward list of children looks like an infinite loop") + } + if c.Parent != n { + return fmt.Errorf("html: inconsistent child/parent relationship") + } + } + + nBackward := 0 + for c := n.LastChild; c != nil; c = c.PrevSibling { + nBackward++ + if nBackward == 1e6 { + return fmt.Errorf("html: backward list of children looks like an infinite loop") + } + if c.Parent != n { + return fmt.Errorf("html: inconsistent child/parent relationship") + } + } + + if n.Parent != nil { + if n.Parent == n { + return fmt.Errorf("html: inconsistent parent relationship") + } + if n.Parent == n.FirstChild { + return fmt.Errorf("html: inconsistent parent/first relationship") + } + if n.Parent == n.LastChild { + return fmt.Errorf("html: inconsistent parent/last relationship") + } + if n.Parent == n.PrevSibling { + return fmt.Errorf("html: inconsistent parent/prev relationship") + } + if n.Parent == n.NextSibling { + return fmt.Errorf("html: inconsistent parent/next relationship") + } + + parentHasNAsAChild := false + for c := n.Parent.FirstChild; c != nil; c = c.NextSibling { + if c == n { + parentHasNAsAChild = true + break + } + } + if !parentHasNAsAChild { + return fmt.Errorf("html: inconsistent parent/child relationship") + } + } + + if n.PrevSibling != nil && n.PrevSibling.NextSibling != n { + return fmt.Errorf("html: inconsistent prev/next relationship") + } + if n.NextSibling != nil && n.NextSibling.PrevSibling != n { + return fmt.Errorf("html: inconsistent next/prev relationship") + } + + if (n.FirstChild == nil) != (n.LastChild == nil) { + return fmt.Errorf("html: inconsistent first/last relationship") + } + if n.FirstChild != nil && n.FirstChild == n.LastChild { + // We have a sole child. + if n.FirstChild.PrevSibling != nil || n.FirstChild.NextSibling != nil { + return fmt.Errorf("html: inconsistent sole child's sibling relationship") + } + } + + seen := map[*Node]bool{} + + var last *Node + for c := n.FirstChild; c != nil; c = c.NextSibling { + if seen[c] { + return fmt.Errorf("html: inconsistent repeated child") + } + seen[c] = true + last = c + } + if last != n.LastChild { + return fmt.Errorf("html: inconsistent last relationship") + } + + var first *Node + for c := n.LastChild; c != nil; c = c.PrevSibling { + if !seen[c] { + return fmt.Errorf("html: inconsistent missing child") + } + delete(seen, c) + first = c + } + if first != n.FirstChild { + return fmt.Errorf("html: inconsistent first relationship") + } + + if len(seen) != 0 { + return fmt.Errorf("html: inconsistent forwards/backwards child list") + } + + return nil +} diff --git a/src/golang.org/x/net/html/parse.go b/src/golang.org/x/net/html/parse.go new file mode 100644 index 0000000000..be4b2bf5aa --- /dev/null +++ b/src/golang.org/x/net/html/parse.go @@ -0,0 +1,2094 @@ +// Copyright 2010 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. + +package html + +import ( + "errors" + "fmt" + "io" + "strings" + + a "golang.org/x/net/html/atom" +) + +// A parser implements the HTML5 parsing algorithm: +// https://html.spec.whatwg.org/multipage/syntax.html#tree-construction +type parser struct { + // tokenizer provides the tokens for the parser. + tokenizer *Tokenizer + // tok is the most recently read token. + tok Token + // Self-closing tags like
are treated as start tags, except that + // hasSelfClosingToken is set while they are being processed. + hasSelfClosingToken bool + // doc is the document root element. + doc *Node + // The stack of open elements (section 12.2.3.2) and active formatting + // elements (section 12.2.3.3). + oe, afe nodeStack + // Element pointers (section 12.2.3.4). + head, form *Node + // Other parsing state flags (section 12.2.3.5). + scripting, framesetOK bool + // im is the current insertion mode. + im insertionMode + // originalIM is the insertion mode to go back to after completing a text + // or inTableText insertion mode. + originalIM insertionMode + // fosterParenting is whether new elements should be inserted according to + // the foster parenting rules (section 12.2.5.3). + fosterParenting bool + // quirks is whether the parser is operating in "quirks mode." + quirks bool + // fragment is whether the parser is parsing an HTML fragment. + fragment bool + // context is the context element when parsing an HTML fragment + // (section 12.4). + context *Node +} + +func (p *parser) top() *Node { + if n := p.oe.top(); n != nil { + return n + } + return p.doc +} + +// Stop tags for use in popUntil. These come from section 12.2.3.2. +var ( + defaultScopeStopTags = map[string][]a.Atom{ + "": {a.Applet, a.Caption, a.Html, a.Table, a.Td, a.Th, a.Marquee, a.Object, a.Template}, + "math": {a.AnnotationXml, a.Mi, a.Mn, a.Mo, a.Ms, a.Mtext}, + "svg": {a.Desc, a.ForeignObject, a.Title}, + } +) + +type scope int + +const ( + defaultScope scope = iota + listItemScope + buttonScope + tableScope + tableRowScope + tableBodyScope + selectScope +) + +// popUntil pops the stack of open elements at the highest element whose tag +// is in matchTags, provided there is no higher element in the scope's stop +// tags (as defined in section 12.2.3.2). It returns whether or not there was +// such an element. If there was not, popUntil leaves the stack unchanged. +// +// For example, the set of stop tags for table scope is: "html", "table". If +// the stack was: +// ["html", "body", "font", "table", "b", "i", "u"] +// then popUntil(tableScope, "font") would return false, but +// popUntil(tableScope, "i") would return true and the stack would become: +// ["html", "body", "font", "table", "b"] +// +// If an element's tag is in both the stop tags and matchTags, then the stack +// will be popped and the function returns true (provided, of course, there was +// no higher element in the stack that was also in the stop tags). For example, +// popUntil(tableScope, "table") returns true and leaves: +// ["html", "body", "font"] +func (p *parser) popUntil(s scope, matchTags ...a.Atom) bool { + if i := p.indexOfElementInScope(s, matchTags...); i != -1 { + p.oe = p.oe[:i] + return true + } + return false +} + +// indexOfElementInScope returns the index in p.oe of the highest element whose +// tag is in matchTags that is in scope. If no matching element is in scope, it +// returns -1. +func (p *parser) indexOfElementInScope(s scope, matchTags ...a.Atom) int { + for i := len(p.oe) - 1; i >= 0; i-- { + tagAtom := p.oe[i].DataAtom + if p.oe[i].Namespace == "" { + for _, t := range matchTags { + if t == tagAtom { + return i + } + } + switch s { + case defaultScope: + // No-op. + case listItemScope: + if tagAtom == a.Ol || tagAtom == a.Ul { + return -1 + } + case buttonScope: + if tagAtom == a.Button { + return -1 + } + case tableScope: + if tagAtom == a.Html || tagAtom == a.Table { + return -1 + } + case selectScope: + if tagAtom != a.Optgroup && tagAtom != a.Option { + return -1 + } + default: + panic("unreachable") + } + } + switch s { + case defaultScope, listItemScope, buttonScope: + for _, t := range defaultScopeStopTags[p.oe[i].Namespace] { + if t == tagAtom { + return -1 + } + } + } + } + return -1 +} + +// elementInScope is like popUntil, except that it doesn't modify the stack of +// open elements. +func (p *parser) elementInScope(s scope, matchTags ...a.Atom) bool { + return p.indexOfElementInScope(s, matchTags...) != -1 +} + +// clearStackToContext pops elements off the stack of open elements until a +// scope-defined element is found. +func (p *parser) clearStackToContext(s scope) { + for i := len(p.oe) - 1; i >= 0; i-- { + tagAtom := p.oe[i].DataAtom + switch s { + case tableScope: + if tagAtom == a.Html || tagAtom == a.Table { + p.oe = p.oe[:i+1] + return + } + case tableRowScope: + if tagAtom == a.Html || tagAtom == a.Tr { + p.oe = p.oe[:i+1] + return + } + case tableBodyScope: + if tagAtom == a.Html || tagAtom == a.Tbody || tagAtom == a.Tfoot || tagAtom == a.Thead { + p.oe = p.oe[:i+1] + return + } + default: + panic("unreachable") + } + } +} + +// generateImpliedEndTags pops nodes off the stack of open elements as long as +// the top node has a tag name of dd, dt, li, option, optgroup, p, rp, or rt. +// If exceptions are specified, nodes with that name will not be popped off. +func (p *parser) generateImpliedEndTags(exceptions ...string) { + var i int +loop: + for i = len(p.oe) - 1; i >= 0; i-- { + n := p.oe[i] + if n.Type == ElementNode { + switch n.DataAtom { + case a.Dd, a.Dt, a.Li, a.Option, a.Optgroup, a.P, a.Rp, a.Rt: + for _, except := range exceptions { + if n.Data == except { + break loop + } + } + continue + } + } + break + } + + p.oe = p.oe[:i+1] +} + +// addChild adds a child node n to the top element, and pushes n onto the stack +// of open elements if it is an element node. +func (p *parser) addChild(n *Node) { + if p.shouldFosterParent() { + p.fosterParent(n) + } else { + p.top().AppendChild(n) + } + + if n.Type == ElementNode { + p.oe = append(p.oe, n) + } +} + +// shouldFosterParent returns whether the next node to be added should be +// foster parented. +func (p *parser) shouldFosterParent() bool { + if p.fosterParenting { + switch p.top().DataAtom { + case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr: + return true + } + } + return false +} + +// fosterParent adds a child node according to the foster parenting rules. +// Section 12.2.5.3, "foster parenting". +func (p *parser) fosterParent(n *Node) { + var table, parent, prev *Node + var i int + for i = len(p.oe) - 1; i >= 0; i-- { + if p.oe[i].DataAtom == a.Table { + table = p.oe[i] + break + } + } + + if table == nil { + // The foster parent is the html element. + parent = p.oe[0] + } else { + parent = table.Parent + } + if parent == nil { + parent = p.oe[i-1] + } + + if table != nil { + prev = table.PrevSibling + } else { + prev = parent.LastChild + } + if prev != nil && prev.Type == TextNode && n.Type == TextNode { + prev.Data += n.Data + return + } + + parent.InsertBefore(n, table) +} + +// addText adds text to the preceding node if it is a text node, or else it +// calls addChild with a new text node. +func (p *parser) addText(text string) { + if text == "" { + return + } + + if p.shouldFosterParent() { + p.fosterParent(&Node{ + Type: TextNode, + Data: text, + }) + return + } + + t := p.top() + if n := t.LastChild; n != nil && n.Type == TextNode { + n.Data += text + return + } + p.addChild(&Node{ + Type: TextNode, + Data: text, + }) +} + +// addElement adds a child element based on the current token. +func (p *parser) addElement() { + p.addChild(&Node{ + Type: ElementNode, + DataAtom: p.tok.DataAtom, + Data: p.tok.Data, + Attr: p.tok.Attr, + }) +} + +// Section 12.2.3.3. +func (p *parser) addFormattingElement() { + tagAtom, attr := p.tok.DataAtom, p.tok.Attr + p.addElement() + + // Implement the Noah's Ark clause, but with three per family instead of two. + identicalElements := 0 +findIdenticalElements: + for i := len(p.afe) - 1; i >= 0; i-- { + n := p.afe[i] + if n.Type == scopeMarkerNode { + break + } + if n.Type != ElementNode { + continue + } + if n.Namespace != "" { + continue + } + if n.DataAtom != tagAtom { + continue + } + if len(n.Attr) != len(attr) { + continue + } + compareAttributes: + for _, t0 := range n.Attr { + for _, t1 := range attr { + if t0.Key == t1.Key && t0.Namespace == t1.Namespace && t0.Val == t1.Val { + // Found a match for this attribute, continue with the next attribute. + continue compareAttributes + } + } + // If we get here, there is no attribute that matches a. + // Therefore the element is not identical to the new one. + continue findIdenticalElements + } + + identicalElements++ + if identicalElements >= 3 { + p.afe.remove(n) + } + } + + p.afe = append(p.afe, p.top()) +} + +// Section 12.2.3.3. +func (p *parser) clearActiveFormattingElements() { + for { + n := p.afe.pop() + if len(p.afe) == 0 || n.Type == scopeMarkerNode { + return + } + } +} + +// Section 12.2.3.3. +func (p *parser) reconstructActiveFormattingElements() { + n := p.afe.top() + if n == nil { + return + } + if n.Type == scopeMarkerNode || p.oe.index(n) != -1 { + return + } + i := len(p.afe) - 1 + for n.Type != scopeMarkerNode && p.oe.index(n) == -1 { + if i == 0 { + i = -1 + break + } + i-- + n = p.afe[i] + } + for { + i++ + clone := p.afe[i].clone() + p.addChild(clone) + p.afe[i] = clone + if i == len(p.afe)-1 { + break + } + } +} + +// Section 12.2.4. +func (p *parser) acknowledgeSelfClosingTag() { + p.hasSelfClosingToken = false +} + +// An insertion mode (section 12.2.3.1) is the state transition function from +// a particular state in the HTML5 parser's state machine. It updates the +// parser's fields depending on parser.tok (where ErrorToken means EOF). +// It returns whether the token was consumed. +type insertionMode func(*parser) bool + +// setOriginalIM sets the insertion mode to return to after completing a text or +// inTableText insertion mode. +// Section 12.2.3.1, "using the rules for". +func (p *parser) setOriginalIM() { + if p.originalIM != nil { + panic("html: bad parser state: originalIM was set twice") + } + p.originalIM = p.im +} + +// Section 12.2.3.1, "reset the insertion mode". +func (p *parser) resetInsertionMode() { + for i := len(p.oe) - 1; i >= 0; i-- { + n := p.oe[i] + if i == 0 && p.context != nil { + n = p.context + } + + switch n.DataAtom { + case a.Select: + p.im = inSelectIM + case a.Td, a.Th: + p.im = inCellIM + case a.Tr: + p.im = inRowIM + case a.Tbody, a.Thead, a.Tfoot: + p.im = inTableBodyIM + case a.Caption: + p.im = inCaptionIM + case a.Colgroup: + p.im = inColumnGroupIM + case a.Table: + p.im = inTableIM + case a.Head: + p.im = inBodyIM + case a.Body: + p.im = inBodyIM + case a.Frameset: + p.im = inFramesetIM + case a.Html: + p.im = beforeHeadIM + default: + continue + } + return + } + p.im = inBodyIM +} + +const whitespace = " \t\r\n\f" + +// Section 12.2.5.4.1. +func initialIM(p *parser) bool { + switch p.tok.Type { + case TextToken: + p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace) + if len(p.tok.Data) == 0 { + // It was all whitespace, so ignore it. + return true + } + case CommentToken: + p.doc.AppendChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + }) + return true + case DoctypeToken: + n, quirks := parseDoctype(p.tok.Data) + p.doc.AppendChild(n) + p.quirks = quirks + p.im = beforeHTMLIM + return true + } + p.quirks = true + p.im = beforeHTMLIM + return false +} + +// Section 12.2.5.4.2. +func beforeHTMLIM(p *parser) bool { + switch p.tok.Type { + case DoctypeToken: + // Ignore the token. + return true + case TextToken: + p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace) + if len(p.tok.Data) == 0 { + // It was all whitespace, so ignore it. + return true + } + case StartTagToken: + if p.tok.DataAtom == a.Html { + p.addElement() + p.im = beforeHeadIM + return true + } + case EndTagToken: + switch p.tok.DataAtom { + case a.Head, a.Body, a.Html, a.Br: + p.parseImpliedToken(StartTagToken, a.Html, a.Html.String()) + return false + default: + // Ignore the token. + return true + } + case CommentToken: + p.doc.AppendChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + }) + return true + } + p.parseImpliedToken(StartTagToken, a.Html, a.Html.String()) + return false +} + +// Section 12.2.5.4.3. +func beforeHeadIM(p *parser) bool { + switch p.tok.Type { + case TextToken: + p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace) + if len(p.tok.Data) == 0 { + // It was all whitespace, so ignore it. + return true + } + case StartTagToken: + switch p.tok.DataAtom { + case a.Head: + p.addElement() + p.head = p.top() + p.im = inHeadIM + return true + case a.Html: + return inBodyIM(p) + } + case EndTagToken: + switch p.tok.DataAtom { + case a.Head, a.Body, a.Html, a.Br: + p.parseImpliedToken(StartTagToken, a.Head, a.Head.String()) + return false + default: + // Ignore the token. + return true + } + case CommentToken: + p.addChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + }) + return true + case DoctypeToken: + // Ignore the token. + return true + } + + p.parseImpliedToken(StartTagToken, a.Head, a.Head.String()) + return false +} + +// Section 12.2.5.4.4. +func inHeadIM(p *parser) bool { + switch p.tok.Type { + case TextToken: + s := strings.TrimLeft(p.tok.Data, whitespace) + if len(s) < len(p.tok.Data) { + // Add the initial whitespace to the current node. + p.addText(p.tok.Data[:len(p.tok.Data)-len(s)]) + if s == "" { + return true + } + p.tok.Data = s + } + case StartTagToken: + switch p.tok.DataAtom { + case a.Html: + return inBodyIM(p) + case a.Base, a.Basefont, a.Bgsound, a.Command, a.Link, a.Meta: + p.addElement() + p.oe.pop() + p.acknowledgeSelfClosingTag() + return true + case a.Script, a.Title, a.Noscript, a.Noframes, a.Style: + p.addElement() + p.setOriginalIM() + p.im = textIM + return true + case a.Head: + // Ignore the token. + return true + } + case EndTagToken: + switch p.tok.DataAtom { + case a.Head: + n := p.oe.pop() + if n.DataAtom != a.Head { + panic("html: bad parser state: element not found, in the in-head insertion mode") + } + p.im = afterHeadIM + return true + case a.Body, a.Html, a.Br: + p.parseImpliedToken(EndTagToken, a.Head, a.Head.String()) + return false + default: + // Ignore the token. + return true + } + case CommentToken: + p.addChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + }) + return true + case DoctypeToken: + // Ignore the token. + return true + } + + p.parseImpliedToken(EndTagToken, a.Head, a.Head.String()) + return false +} + +// Section 12.2.5.4.6. +func afterHeadIM(p *parser) bool { + switch p.tok.Type { + case TextToken: + s := strings.TrimLeft(p.tok.Data, whitespace) + if len(s) < len(p.tok.Data) { + // Add the initial whitespace to the current node. + p.addText(p.tok.Data[:len(p.tok.Data)-len(s)]) + if s == "" { + return true + } + p.tok.Data = s + } + case StartTagToken: + switch p.tok.DataAtom { + case a.Html: + return inBodyIM(p) + case a.Body: + p.addElement() + p.framesetOK = false + p.im = inBodyIM + return true + case a.Frameset: + p.addElement() + p.im = inFramesetIM + return true + case a.Base, a.Basefont, a.Bgsound, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Title: + p.oe = append(p.oe, p.head) + defer p.oe.remove(p.head) + return inHeadIM(p) + case a.Head: + // Ignore the token. + return true + } + case EndTagToken: + switch p.tok.DataAtom { + case a.Body, a.Html, a.Br: + // Drop down to creating an implied tag. + default: + // Ignore the token. + return true + } + case CommentToken: + p.addChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + }) + return true + case DoctypeToken: + // Ignore the token. + return true + } + + p.parseImpliedToken(StartTagToken, a.Body, a.Body.String()) + p.framesetOK = true + return false +} + +// copyAttributes copies attributes of src not found on dst to dst. +func copyAttributes(dst *Node, src Token) { + if len(src.Attr) == 0 { + return + } + attr := map[string]string{} + for _, t := range dst.Attr { + attr[t.Key] = t.Val + } + for _, t := range src.Attr { + if _, ok := attr[t.Key]; !ok { + dst.Attr = append(dst.Attr, t) + attr[t.Key] = t.Val + } + } +} + +// Section 12.2.5.4.7. +func inBodyIM(p *parser) bool { + switch p.tok.Type { + case TextToken: + d := p.tok.Data + switch n := p.oe.top(); n.DataAtom { + case a.Pre, a.Listing: + if n.FirstChild == nil { + // Ignore a newline at the start of a
 block.
+				if d != "" && d[0] == '\r' {
+					d = d[1:]
+				}
+				if d != "" && d[0] == '\n' {
+					d = d[1:]
+				}
+			}
+		}
+		d = strings.Replace(d, "\x00", "", -1)
+		if d == "" {
+			return true
+		}
+		p.reconstructActiveFormattingElements()
+		p.addText(d)
+		if p.framesetOK && strings.TrimLeft(d, whitespace) != "" {
+			// There were non-whitespace characters inserted.
+			p.framesetOK = false
+		}
+	case StartTagToken:
+		switch p.tok.DataAtom {
+		case a.Html:
+			copyAttributes(p.oe[0], p.tok)
+		case a.Base, a.Basefont, a.Bgsound, a.Command, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Title:
+			return inHeadIM(p)
+		case a.Body:
+			if len(p.oe) >= 2 {
+				body := p.oe[1]
+				if body.Type == ElementNode && body.DataAtom == a.Body {
+					p.framesetOK = false
+					copyAttributes(body, p.tok)
+				}
+			}
+		case a.Frameset:
+			if !p.framesetOK || len(p.oe) < 2 || p.oe[1].DataAtom != a.Body {
+				// Ignore the token.
+				return true
+			}
+			body := p.oe[1]
+			if body.Parent != nil {
+				body.Parent.RemoveChild(body)
+			}
+			p.oe = p.oe[:1]
+			p.addElement()
+			p.im = inFramesetIM
+			return true
+		case a.Address, a.Article, a.Aside, a.Blockquote, a.Center, a.Details, a.Dir, a.Div, a.Dl, a.Fieldset, a.Figcaption, a.Figure, a.Footer, a.Header, a.Hgroup, a.Menu, a.Nav, a.Ol, a.P, a.Section, a.Summary, a.Ul:
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+		case a.H1, a.H2, a.H3, a.H4, a.H5, a.H6:
+			p.popUntil(buttonScope, a.P)
+			switch n := p.top(); n.DataAtom {
+			case a.H1, a.H2, a.H3, a.H4, a.H5, a.H6:
+				p.oe.pop()
+			}
+			p.addElement()
+		case a.Pre, a.Listing:
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+			// The newline, if any, will be dealt with by the TextToken case.
+			p.framesetOK = false
+		case a.Form:
+			if p.form == nil {
+				p.popUntil(buttonScope, a.P)
+				p.addElement()
+				p.form = p.top()
+			}
+		case a.Li:
+			p.framesetOK = false
+			for i := len(p.oe) - 1; i >= 0; i-- {
+				node := p.oe[i]
+				switch node.DataAtom {
+				case a.Li:
+					p.oe = p.oe[:i]
+				case a.Address, a.Div, a.P:
+					continue
+				default:
+					if !isSpecialElement(node) {
+						continue
+					}
+				}
+				break
+			}
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+		case a.Dd, a.Dt:
+			p.framesetOK = false
+			for i := len(p.oe) - 1; i >= 0; i-- {
+				node := p.oe[i]
+				switch node.DataAtom {
+				case a.Dd, a.Dt:
+					p.oe = p.oe[:i]
+				case a.Address, a.Div, a.P:
+					continue
+				default:
+					if !isSpecialElement(node) {
+						continue
+					}
+				}
+				break
+			}
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+		case a.Plaintext:
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+		case a.Button:
+			p.popUntil(defaultScope, a.Button)
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+			p.framesetOK = false
+		case a.A:
+			for i := len(p.afe) - 1; i >= 0 && p.afe[i].Type != scopeMarkerNode; i-- {
+				if n := p.afe[i]; n.Type == ElementNode && n.DataAtom == a.A {
+					p.inBodyEndTagFormatting(a.A)
+					p.oe.remove(n)
+					p.afe.remove(n)
+					break
+				}
+			}
+			p.reconstructActiveFormattingElements()
+			p.addFormattingElement()
+		case a.B, a.Big, a.Code, a.Em, a.Font, a.I, a.S, a.Small, a.Strike, a.Strong, a.Tt, a.U:
+			p.reconstructActiveFormattingElements()
+			p.addFormattingElement()
+		case a.Nobr:
+			p.reconstructActiveFormattingElements()
+			if p.elementInScope(defaultScope, a.Nobr) {
+				p.inBodyEndTagFormatting(a.Nobr)
+				p.reconstructActiveFormattingElements()
+			}
+			p.addFormattingElement()
+		case a.Applet, a.Marquee, a.Object:
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+			p.afe = append(p.afe, &scopeMarker)
+			p.framesetOK = false
+		case a.Table:
+			if !p.quirks {
+				p.popUntil(buttonScope, a.P)
+			}
+			p.addElement()
+			p.framesetOK = false
+			p.im = inTableIM
+			return true
+		case a.Area, a.Br, a.Embed, a.Img, a.Input, a.Keygen, a.Wbr:
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+			p.oe.pop()
+			p.acknowledgeSelfClosingTag()
+			if p.tok.DataAtom == a.Input {
+				for _, t := range p.tok.Attr {
+					if t.Key == "type" {
+						if strings.ToLower(t.Val) == "hidden" {
+							// Skip setting framesetOK = false
+							return true
+						}
+					}
+				}
+			}
+			p.framesetOK = false
+		case a.Param, a.Source, a.Track:
+			p.addElement()
+			p.oe.pop()
+			p.acknowledgeSelfClosingTag()
+		case a.Hr:
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+			p.oe.pop()
+			p.acknowledgeSelfClosingTag()
+			p.framesetOK = false
+		case a.Image:
+			p.tok.DataAtom = a.Img
+			p.tok.Data = a.Img.String()
+			return false
+		case a.Isindex:
+			if p.form != nil {
+				// Ignore the token.
+				return true
+			}
+			action := ""
+			prompt := "This is a searchable index. Enter search keywords: "
+			attr := []Attribute{{Key: "name", Val: "isindex"}}
+			for _, t := range p.tok.Attr {
+				switch t.Key {
+				case "action":
+					action = t.Val
+				case "name":
+					// Ignore the attribute.
+				case "prompt":
+					prompt = t.Val
+				default:
+					attr = append(attr, t)
+				}
+			}
+			p.acknowledgeSelfClosingTag()
+			p.popUntil(buttonScope, a.P)
+			p.parseImpliedToken(StartTagToken, a.Form, a.Form.String())
+			if action != "" {
+				p.form.Attr = []Attribute{{Key: "action", Val: action}}
+			}
+			p.parseImpliedToken(StartTagToken, a.Hr, a.Hr.String())
+			p.parseImpliedToken(StartTagToken, a.Label, a.Label.String())
+			p.addText(prompt)
+			p.addChild(&Node{
+				Type:     ElementNode,
+				DataAtom: a.Input,
+				Data:     a.Input.String(),
+				Attr:     attr,
+			})
+			p.oe.pop()
+			p.parseImpliedToken(EndTagToken, a.Label, a.Label.String())
+			p.parseImpliedToken(StartTagToken, a.Hr, a.Hr.String())
+			p.parseImpliedToken(EndTagToken, a.Form, a.Form.String())
+		case a.Textarea:
+			p.addElement()
+			p.setOriginalIM()
+			p.framesetOK = false
+			p.im = textIM
+		case a.Xmp:
+			p.popUntil(buttonScope, a.P)
+			p.reconstructActiveFormattingElements()
+			p.framesetOK = false
+			p.addElement()
+			p.setOriginalIM()
+			p.im = textIM
+		case a.Iframe:
+			p.framesetOK = false
+			p.addElement()
+			p.setOriginalIM()
+			p.im = textIM
+		case a.Noembed, a.Noscript:
+			p.addElement()
+			p.setOriginalIM()
+			p.im = textIM
+		case a.Select:
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+			p.framesetOK = false
+			p.im = inSelectIM
+			return true
+		case a.Optgroup, a.Option:
+			if p.top().DataAtom == a.Option {
+				p.oe.pop()
+			}
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+		case a.Rp, a.Rt:
+			if p.elementInScope(defaultScope, a.Ruby) {
+				p.generateImpliedEndTags()
+			}
+			p.addElement()
+		case a.Math, a.Svg:
+			p.reconstructActiveFormattingElements()
+			if p.tok.DataAtom == a.Math {
+				adjustAttributeNames(p.tok.Attr, mathMLAttributeAdjustments)
+			} else {
+				adjustAttributeNames(p.tok.Attr, svgAttributeAdjustments)
+			}
+			adjustForeignAttributes(p.tok.Attr)
+			p.addElement()
+			p.top().Namespace = p.tok.Data
+			if p.hasSelfClosingToken {
+				p.oe.pop()
+				p.acknowledgeSelfClosingTag()
+			}
+			return true
+		case a.Caption, a.Col, a.Colgroup, a.Frame, a.Head, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr:
+			// Ignore the token.
+		default:
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+		}
+	case EndTagToken:
+		switch p.tok.DataAtom {
+		case a.Body:
+			if p.elementInScope(defaultScope, a.Body) {
+				p.im = afterBodyIM
+			}
+		case a.Html:
+			if p.elementInScope(defaultScope, a.Body) {
+				p.parseImpliedToken(EndTagToken, a.Body, a.Body.String())
+				return false
+			}
+			return true
+		case a.Address, a.Article, a.Aside, a.Blockquote, a.Button, a.Center, a.Details, a.Dir, a.Div, a.Dl, a.Fieldset, a.Figcaption, a.Figure, a.Footer, a.Header, a.Hgroup, a.Listing, a.Menu, a.Nav, a.Ol, a.Pre, a.Section, a.Summary, a.Ul:
+			p.popUntil(defaultScope, p.tok.DataAtom)
+		case a.Form:
+			node := p.form
+			p.form = nil
+			i := p.indexOfElementInScope(defaultScope, a.Form)
+			if node == nil || i == -1 || p.oe[i] != node {
+				// Ignore the token.
+				return true
+			}
+			p.generateImpliedEndTags()
+			p.oe.remove(node)
+		case a.P:
+			if !p.elementInScope(buttonScope, a.P) {
+				p.parseImpliedToken(StartTagToken, a.P, a.P.String())
+			}
+			p.popUntil(buttonScope, a.P)
+		case a.Li:
+			p.popUntil(listItemScope, a.Li)
+		case a.Dd, a.Dt:
+			p.popUntil(defaultScope, p.tok.DataAtom)
+		case a.H1, a.H2, a.H3, a.H4, a.H5, a.H6:
+			p.popUntil(defaultScope, a.H1, a.H2, a.H3, a.H4, a.H5, a.H6)
+		case a.A, a.B, a.Big, a.Code, a.Em, a.Font, a.I, a.Nobr, a.S, a.Small, a.Strike, a.Strong, a.Tt, a.U:
+			p.inBodyEndTagFormatting(p.tok.DataAtom)
+		case a.Applet, a.Marquee, a.Object:
+			if p.popUntil(defaultScope, p.tok.DataAtom) {
+				p.clearActiveFormattingElements()
+			}
+		case a.Br:
+			p.tok.Type = StartTagToken
+			return false
+		default:
+			p.inBodyEndTagOther(p.tok.DataAtom)
+		}
+	case CommentToken:
+		p.addChild(&Node{
+			Type: CommentNode,
+			Data: p.tok.Data,
+		})
+	}
+
+	return true
+}
+
+func (p *parser) inBodyEndTagFormatting(tagAtom a.Atom) {
+	// This is the "adoption agency" algorithm, described at
+	// https://html.spec.whatwg.org/multipage/syntax.html#adoptionAgency
+
+	// TODO: this is a fairly literal line-by-line translation of that algorithm.
+	// Once the code successfully parses the comprehensive test suite, we should
+	// refactor this code to be more idiomatic.
+
+	// Steps 1-4. The outer loop.
+	for i := 0; i < 8; i++ {
+		// Step 5. Find the formatting element.
+		var formattingElement *Node
+		for j := len(p.afe) - 1; j >= 0; j-- {
+			if p.afe[j].Type == scopeMarkerNode {
+				break
+			}
+			if p.afe[j].DataAtom == tagAtom {
+				formattingElement = p.afe[j]
+				break
+			}
+		}
+		if formattingElement == nil {
+			p.inBodyEndTagOther(tagAtom)
+			return
+		}
+		feIndex := p.oe.index(formattingElement)
+		if feIndex == -1 {
+			p.afe.remove(formattingElement)
+			return
+		}
+		if !p.elementInScope(defaultScope, tagAtom) {
+			// Ignore the tag.
+			return
+		}
+
+		// Steps 9-10. Find the furthest block.
+		var furthestBlock *Node
+		for _, e := range p.oe[feIndex:] {
+			if isSpecialElement(e) {
+				furthestBlock = e
+				break
+			}
+		}
+		if furthestBlock == nil {
+			e := p.oe.pop()
+			for e != formattingElement {
+				e = p.oe.pop()
+			}
+			p.afe.remove(e)
+			return
+		}
+
+		// Steps 11-12. Find the common ancestor and bookmark node.
+		commonAncestor := p.oe[feIndex-1]
+		bookmark := p.afe.index(formattingElement)
+
+		// Step 13. The inner loop. Find the lastNode to reparent.
+		lastNode := furthestBlock
+		node := furthestBlock
+		x := p.oe.index(node)
+		// Steps 13.1-13.2
+		for j := 0; j < 3; j++ {
+			// Step 13.3.
+			x--
+			node = p.oe[x]
+			// Step 13.4 - 13.5.
+			if p.afe.index(node) == -1 {
+				p.oe.remove(node)
+				continue
+			}
+			// Step 13.6.
+			if node == formattingElement {
+				break
+			}
+			// Step 13.7.
+			clone := node.clone()
+			p.afe[p.afe.index(node)] = clone
+			p.oe[p.oe.index(node)] = clone
+			node = clone
+			// Step 13.8.
+			if lastNode == furthestBlock {
+				bookmark = p.afe.index(node) + 1
+			}
+			// Step 13.9.
+			if lastNode.Parent != nil {
+				lastNode.Parent.RemoveChild(lastNode)
+			}
+			node.AppendChild(lastNode)
+			// Step 13.10.
+			lastNode = node
+		}
+
+		// Step 14. Reparent lastNode to the common ancestor,
+		// or for misnested table nodes, to the foster parent.
+		if lastNode.Parent != nil {
+			lastNode.Parent.RemoveChild(lastNode)
+		}
+		switch commonAncestor.DataAtom {
+		case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr:
+			p.fosterParent(lastNode)
+		default:
+			commonAncestor.AppendChild(lastNode)
+		}
+
+		// Steps 15-17. Reparent nodes from the furthest block's children
+		// to a clone of the formatting element.
+		clone := formattingElement.clone()
+		reparentChildren(clone, furthestBlock)
+		furthestBlock.AppendChild(clone)
+
+		// Step 18. Fix up the list of active formatting elements.
+		if oldLoc := p.afe.index(formattingElement); oldLoc != -1 && oldLoc < bookmark {
+			// Move the bookmark with the rest of the list.
+			bookmark--
+		}
+		p.afe.remove(formattingElement)
+		p.afe.insert(bookmark, clone)
+
+		// Step 19. Fix up the stack of open elements.
+		p.oe.remove(formattingElement)
+		p.oe.insert(p.oe.index(furthestBlock)+1, clone)
+	}
+}
+
+// inBodyEndTagOther performs the "any other end tag" algorithm for inBodyIM.
+// "Any other end tag" handling from 12.2.5.5 The rules for parsing tokens in foreign content
+// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inforeign
+func (p *parser) inBodyEndTagOther(tagAtom a.Atom) {
+	for i := len(p.oe) - 1; i >= 0; i-- {
+		if p.oe[i].DataAtom == tagAtom {
+			p.oe = p.oe[:i]
+			break
+		}
+		if isSpecialElement(p.oe[i]) {
+			break
+		}
+	}
+}
+
+// Section 12.2.5.4.8.
+func textIM(p *parser) bool {
+	switch p.tok.Type {
+	case ErrorToken:
+		p.oe.pop()
+	case TextToken:
+		d := p.tok.Data
+		if n := p.oe.top(); n.DataAtom == a.Textarea && n.FirstChild == nil {
+			// Ignore a newline at the start of a -->
+#errors
+#document
+| 
+|   
+|   
+|     -->
+#errors
+#document
+| 
+|   
+|   
+|     
+#errors
+Line: 1 Col: 10 Unexpected start tag (textarea). Expected DOCTYPE.
+#document
+| 
+|   
+|   
+|     
+#errors
+Line: 1 Col: 9 Unexpected end tag (strong). Expected DOCTYPE.
+Line: 1 Col: 9 Unexpected end tag (strong) after the (implied) root element.
+Line: 1 Col: 13 Unexpected end tag (b) after the (implied) root element.
+Line: 1 Col: 18 Unexpected end tag (em) after the (implied) root element.
+Line: 1 Col: 22 Unexpected end tag (i) after the (implied) root element.
+Line: 1 Col: 26 Unexpected end tag (u) after the (implied) root element.
+Line: 1 Col: 35 Unexpected end tag (strike) after the (implied) root element.
+Line: 1 Col: 39 Unexpected end tag (s) after the (implied) root element.
+Line: 1 Col: 47 Unexpected end tag (blink) after the (implied) root element.
+Line: 1 Col: 52 Unexpected end tag (tt) after the (implied) root element.
+Line: 1 Col: 58 Unexpected end tag (pre) after the (implied) root element.
+Line: 1 Col: 64 Unexpected end tag (big) after the (implied) root element.
+Line: 1 Col: 72 Unexpected end tag (small) after the (implied) root element.
+Line: 1 Col: 79 Unexpected end tag (font) after the (implied) root element.
+Line: 1 Col: 88 Unexpected end tag (select) after the (implied) root element.
+Line: 1 Col: 93 Unexpected end tag (h1) after the (implied) root element.
+Line: 1 Col: 98 Unexpected end tag (h2) after the (implied) root element.
+Line: 1 Col: 103 Unexpected end tag (h3) after the (implied) root element.
+Line: 1 Col: 108 Unexpected end tag (h4) after the (implied) root element.
+Line: 1 Col: 113 Unexpected end tag (h5) after the (implied) root element.
+Line: 1 Col: 118 Unexpected end tag (h6) after the (implied) root element.
+Line: 1 Col: 125 Unexpected end tag (body) after the (implied) root element.
+Line: 1 Col: 130 Unexpected end tag (br). Treated as br element.
+Line: 1 Col: 134 End tag (a) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 140 This element (img) has no end tag.
+Line: 1 Col: 148 Unexpected end tag (title). Ignored.
+Line: 1 Col: 155 Unexpected end tag (span). Ignored.
+Line: 1 Col: 163 Unexpected end tag (style). Ignored.
+Line: 1 Col: 172 Unexpected end tag (script). Ignored.
+Line: 1 Col: 180 Unexpected end tag (table). Ignored.
+Line: 1 Col: 185 Unexpected end tag (th). Ignored.
+Line: 1 Col: 190 Unexpected end tag (td). Ignored.
+Line: 1 Col: 195 Unexpected end tag (tr). Ignored.
+Line: 1 Col: 203 This element (frame) has no end tag.
+Line: 1 Col: 210 This element (area) has no end tag.
+Line: 1 Col: 217 Unexpected end tag (link). Ignored.
+Line: 1 Col: 225 This element (param) has no end tag.
+Line: 1 Col: 230 This element (hr) has no end tag.
+Line: 1 Col: 238 This element (input) has no end tag.
+Line: 1 Col: 244 Unexpected end tag (col). Ignored.
+Line: 1 Col: 251 Unexpected end tag (base). Ignored.
+Line: 1 Col: 258 Unexpected end tag (meta). Ignored.
+Line: 1 Col: 269 This element (basefont) has no end tag.
+Line: 1 Col: 279 This element (bgsound) has no end tag.
+Line: 1 Col: 287 This element (embed) has no end tag.
+Line: 1 Col: 296 This element (spacer) has no end tag.
+Line: 1 Col: 300 Unexpected end tag (p). Ignored.
+Line: 1 Col: 305 End tag (dd) seen too early. Expected other end tag.
+Line: 1 Col: 310 End tag (dt) seen too early. Expected other end tag.
+Line: 1 Col: 320 Unexpected end tag (caption). Ignored.
+Line: 1 Col: 331 Unexpected end tag (colgroup). Ignored.
+Line: 1 Col: 339 Unexpected end tag (tbody). Ignored.
+Line: 1 Col: 347 Unexpected end tag (tfoot). Ignored.
+Line: 1 Col: 355 Unexpected end tag (thead). Ignored.
+Line: 1 Col: 365 End tag (address) seen too early. Expected other end tag.
+Line: 1 Col: 378 End tag (blockquote) seen too early. Expected other end tag.
+Line: 1 Col: 387 End tag (center) seen too early. Expected other end tag.
+Line: 1 Col: 393 Unexpected end tag (dir). Ignored.
+Line: 1 Col: 399 End tag (div) seen too early. Expected other end tag.
+Line: 1 Col: 404 End tag (dl) seen too early. Expected other end tag.
+Line: 1 Col: 415 End tag (fieldset) seen too early. Expected other end tag.
+Line: 1 Col: 425 End tag (listing) seen too early. Expected other end tag.
+Line: 1 Col: 432 End tag (menu) seen too early. Expected other end tag.
+Line: 1 Col: 437 End tag (ol) seen too early. Expected other end tag.
+Line: 1 Col: 442 End tag (ul) seen too early. Expected other end tag.
+Line: 1 Col: 447 End tag (li) seen too early. Expected other end tag.
+Line: 1 Col: 454 End tag (nobr) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 460 This element (wbr) has no end tag.
+Line: 1 Col: 476 End tag (button) seen too early. Expected other end tag.
+Line: 1 Col: 486 End tag (marquee) seen too early. Expected other end tag.
+Line: 1 Col: 495 End tag (object) seen too early. Expected other end tag.
+Line: 1 Col: 513 Unexpected end tag (html). Ignored.
+Line: 1 Col: 513 Unexpected end tag (frameset). Ignored.
+Line: 1 Col: 520 Unexpected end tag (head). Ignored.
+Line: 1 Col: 529 Unexpected end tag (iframe). Ignored.
+Line: 1 Col: 537 This element (image) has no end tag.
+Line: 1 Col: 547 This element (isindex) has no end tag.
+Line: 1 Col: 557 Unexpected end tag (noembed). Ignored.
+Line: 1 Col: 568 Unexpected end tag (noframes). Ignored.
+Line: 1 Col: 579 Unexpected end tag (noscript). Ignored.
+Line: 1 Col: 590 Unexpected end tag (optgroup). Ignored.
+Line: 1 Col: 599 Unexpected end tag (option). Ignored.
+Line: 1 Col: 611 Unexpected end tag (plaintext). Ignored.
+Line: 1 Col: 622 Unexpected end tag (textarea). Ignored.
+#document
+| 
+|   
+|   
+|     
+|

+ +#data +

+#errors +Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE. +Line: 1 Col: 20 Unexpected end tag (strong) in table context caused voodoo mode. +Line: 1 Col: 20 End tag (strong) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 24 Unexpected end tag (b) in table context caused voodoo mode. +Line: 1 Col: 24 End tag (b) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 29 Unexpected end tag (em) in table context caused voodoo mode. +Line: 1 Col: 29 End tag (em) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 33 Unexpected end tag (i) in table context caused voodoo mode. +Line: 1 Col: 33 End tag (i) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 37 Unexpected end tag (u) in table context caused voodoo mode. +Line: 1 Col: 37 End tag (u) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 46 Unexpected end tag (strike) in table context caused voodoo mode. +Line: 1 Col: 46 End tag (strike) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 50 Unexpected end tag (s) in table context caused voodoo mode. +Line: 1 Col: 50 End tag (s) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 58 Unexpected end tag (blink) in table context caused voodoo mode. +Line: 1 Col: 58 Unexpected end tag (blink). Ignored. +Line: 1 Col: 63 Unexpected end tag (tt) in table context caused voodoo mode. +Line: 1 Col: 63 End tag (tt) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 69 Unexpected end tag (pre) in table context caused voodoo mode. +Line: 1 Col: 69 End tag (pre) seen too early. Expected other end tag. +Line: 1 Col: 75 Unexpected end tag (big) in table context caused voodoo mode. +Line: 1 Col: 75 End tag (big) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 83 Unexpected end tag (small) in table context caused voodoo mode. +Line: 1 Col: 83 End tag (small) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 90 Unexpected end tag (font) in table context caused voodoo mode. +Line: 1 Col: 90 End tag (font) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 99 Unexpected end tag (select) in table context caused voodoo mode. +Line: 1 Col: 99 Unexpected end tag (select). Ignored. +Line: 1 Col: 104 Unexpected end tag (h1) in table context caused voodoo mode. +Line: 1 Col: 104 End tag (h1) seen too early. Expected other end tag. +Line: 1 Col: 109 Unexpected end tag (h2) in table context caused voodoo mode. +Line: 1 Col: 109 End tag (h2) seen too early. Expected other end tag. +Line: 1 Col: 114 Unexpected end tag (h3) in table context caused voodoo mode. +Line: 1 Col: 114 End tag (h3) seen too early. Expected other end tag. +Line: 1 Col: 119 Unexpected end tag (h4) in table context caused voodoo mode. +Line: 1 Col: 119 End tag (h4) seen too early. Expected other end tag. +Line: 1 Col: 124 Unexpected end tag (h5) in table context caused voodoo mode. +Line: 1 Col: 124 End tag (h5) seen too early. Expected other end tag. +Line: 1 Col: 129 Unexpected end tag (h6) in table context caused voodoo mode. +Line: 1 Col: 129 End tag (h6) seen too early. Expected other end tag. +Line: 1 Col: 136 Unexpected end tag (body) in the table row phase. Ignored. +Line: 1 Col: 141 Unexpected end tag (br) in table context caused voodoo mode. +Line: 1 Col: 141 Unexpected end tag (br). Treated as br element. +Line: 1 Col: 145 Unexpected end tag (a) in table context caused voodoo mode. +Line: 1 Col: 145 End tag (a) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 151 Unexpected end tag (img) in table context caused voodoo mode. +Line: 1 Col: 151 This element (img) has no end tag. +Line: 1 Col: 159 Unexpected end tag (title) in table context caused voodoo mode. +Line: 1 Col: 159 Unexpected end tag (title). Ignored. +Line: 1 Col: 166 Unexpected end tag (span) in table context caused voodoo mode. +Line: 1 Col: 166 Unexpected end tag (span). Ignored. +Line: 1 Col: 174 Unexpected end tag (style) in table context caused voodoo mode. +Line: 1 Col: 174 Unexpected end tag (style). Ignored. +Line: 1 Col: 183 Unexpected end tag (script) in table context caused voodoo mode. +Line: 1 Col: 183 Unexpected end tag (script). Ignored. +Line: 1 Col: 196 Unexpected end tag (th). Ignored. +Line: 1 Col: 201 Unexpected end tag (td). Ignored. +Line: 1 Col: 206 Unexpected end tag (tr). Ignored. +Line: 1 Col: 214 This element (frame) has no end tag. +Line: 1 Col: 221 This element (area) has no end tag. +Line: 1 Col: 228 Unexpected end tag (link). Ignored. +Line: 1 Col: 236 This element (param) has no end tag. +Line: 1 Col: 241 This element (hr) has no end tag. +Line: 1 Col: 249 This element (input) has no end tag. +Line: 1 Col: 255 Unexpected end tag (col). Ignored. +Line: 1 Col: 262 Unexpected end tag (base). Ignored. +Line: 1 Col: 269 Unexpected end tag (meta). Ignored. +Line: 1 Col: 280 This element (basefont) has no end tag. +Line: 1 Col: 290 This element (bgsound) has no end tag. +Line: 1 Col: 298 This element (embed) has no end tag. +Line: 1 Col: 307 This element (spacer) has no end tag. +Line: 1 Col: 311 Unexpected end tag (p). Ignored. +Line: 1 Col: 316 End tag (dd) seen too early. Expected other end tag. +Line: 1 Col: 321 End tag (dt) seen too early. Expected other end tag. +Line: 1 Col: 331 Unexpected end tag (caption). Ignored. +Line: 1 Col: 342 Unexpected end tag (colgroup). Ignored. +Line: 1 Col: 350 Unexpected end tag (tbody). Ignored. +Line: 1 Col: 358 Unexpected end tag (tfoot). Ignored. +Line: 1 Col: 366 Unexpected end tag (thead). Ignored. +Line: 1 Col: 376 End tag (address) seen too early. Expected other end tag. +Line: 1 Col: 389 End tag (blockquote) seen too early. Expected other end tag. +Line: 1 Col: 398 End tag (center) seen too early. Expected other end tag. +Line: 1 Col: 404 Unexpected end tag (dir). Ignored. +Line: 1 Col: 410 End tag (div) seen too early. Expected other end tag. +Line: 1 Col: 415 End tag (dl) seen too early. Expected other end tag. +Line: 1 Col: 426 End tag (fieldset) seen too early. Expected other end tag. +Line: 1 Col: 436 End tag (listing) seen too early. Expected other end tag. +Line: 1 Col: 443 End tag (menu) seen too early. Expected other end tag. +Line: 1 Col: 448 End tag (ol) seen too early. Expected other end tag. +Line: 1 Col: 453 End tag (ul) seen too early. Expected other end tag. +Line: 1 Col: 458 End tag (li) seen too early. Expected other end tag. +Line: 1 Col: 465 End tag (nobr) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 471 This element (wbr) has no end tag. +Line: 1 Col: 487 End tag (button) seen too early. Expected other end tag. +Line: 1 Col: 497 End tag (marquee) seen too early. Expected other end tag. +Line: 1 Col: 506 End tag (object) seen too early. Expected other end tag. +Line: 1 Col: 524 Unexpected end tag (html). Ignored. +Line: 1 Col: 524 Unexpected end tag (frameset). Ignored. +Line: 1 Col: 531 Unexpected end tag (head). Ignored. +Line: 1 Col: 540 Unexpected end tag (iframe). Ignored. +Line: 1 Col: 548 This element (image) has no end tag. +Line: 1 Col: 558 This element (isindex) has no end tag. +Line: 1 Col: 568 Unexpected end tag (noembed). Ignored. +Line: 1 Col: 579 Unexpected end tag (noframes). Ignored. +Line: 1 Col: 590 Unexpected end tag (noscript). Ignored. +Line: 1 Col: 601 Unexpected end tag (optgroup). Ignored. +Line: 1 Col: 610 Unexpected end tag (option). Ignored. +Line: 1 Col: 622 Unexpected end tag (plaintext). Ignored. +Line: 1 Col: 633 Unexpected end tag (textarea). Ignored. +#document +| +| +| +|
+| +| +| +|

+ +#data + +#errors +Line: 1 Col: 10 Unexpected start tag (frameset). Expected DOCTYPE. +Line: 1 Col: 10 Expected closing tag. Unexpected end of file. +#document +| +| +| diff --git a/src/golang.org/x/net/html/testdata/webkit/tests10.dat b/src/golang.org/x/net/html/testdata/webkit/tests10.dat new file mode 100644 index 0000000000..4f8df86f20 --- /dev/null +++ b/src/golang.org/x/net/html/testdata/webkit/tests10.dat @@ -0,0 +1,799 @@ +#data + +#errors +#document +| +| +| +| +| + +#data +a +#errors +29: Bogus comment +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| + +#data + +#errors +35: Stray “svg” start tag. +42: Stray end tag “svg” +#document +| +| +| +| +| +#errors +43: Stray “svg” start tag. +50: Stray end tag “svg” +#document +| +| +| +| +|

+#errors +34: Start tag “svg” seen in “table”. +41: Stray end tag “svg”. +#document +| +| +| +| +| +| + +#data +
foo
+#errors +34: Start tag “svg” seen in “table”. +46: Stray end tag “g”. +53: Stray end tag “svg”. +#document +| +| +| +| +| +| +| "foo" +| + +#data +
foobar
+#errors +34: Start tag “svg” seen in “table”. +46: Stray end tag “g”. +58: Stray end tag “g”. +65: Stray end tag “svg”. +#document +| +| +| +| +| +| +| "foo" +| +| "bar" +| + +#data +
foobar
+#errors +41: Start tag “svg” seen in “table”. +53: Stray end tag “g”. +65: Stray end tag “g”. +72: Stray end tag “svg”. +#document +| +| +| +| +| +| +| "foo" +| +| "bar" +| +| + +#data +
foobar
+#errors +45: Start tag “svg” seen in “table”. +57: Stray end tag “g”. +69: Stray end tag “g”. +76: Stray end tag “svg”. +#document +| +| +| +| +| +| +| "foo" +| +| "bar" +| +| +| + +#data +
foobar
+#errors +#document +| +| +| +| +| +| +| +|
+| +| +| "foo" +| +| "bar" + +#data +
foobar

baz

+#errors +#document +| +| +| +| +| +| +| +|
+| +| +| "foo" +| +| "bar" +|

+| "baz" + +#data +
foobar

baz

+#errors +#document +| +| +| +| +| +|
+| +| +| "foo" +| +| "bar" +|

+| "baz" + +#data +
foobar

baz

quux +#errors +70: HTML start tag “p” in a foreign namespace context. +81: “table” closed but “caption” was still open. +#document +| +| +| +| +| +|
+| +| +| "foo" +| +| "bar" +|

+| "baz" +|

+| "quux" + +#data +
foobarbaz

quux +#errors +78: “table” closed but “caption” was still open. +78: Unclosed elements on stack. +#document +| +| +| +| +| +|
+| +| +| "foo" +| +| "bar" +| "baz" +|

+| "quux" + +#data +foobar

baz

quux +#errors +44: Start tag “svg” seen in “table”. +56: Stray end tag “g”. +68: Stray end tag “g”. +71: HTML start tag “p” in a foreign namespace context. +71: Start tag “p” seen in “table”. +#document +| +| +| +| +| +| +| "foo" +| +| "bar" +|

+| "baz" +| +| +|

+| "quux" + +#data +

quux +#errors +50: Stray “svg” start tag. +54: Stray “g” start tag. +62: Stray end tag “g” +66: Stray “g” start tag. +74: Stray end tag “g” +77: Stray “p” start tag. +88: “table” end tag with “select” open. +#document +| +| +| +| +| +| +| +|
+|

quux +#errors +36: Start tag “select” seen in “table”. +42: Stray “svg” start tag. +46: Stray “g” start tag. +54: Stray end tag “g” +58: Stray “g” start tag. +66: Stray end tag “g” +69: Stray “p” start tag. +80: “table” end tag with “select” open. +#document +| +| +| +| +| +|

+| "quux" + +#data +foobar

baz +#errors +41: Stray “svg” start tag. +68: HTML start tag “p” in a foreign namespace context. +#document +| +| +| +| +| +| +| "foo" +| +| "bar" +|

+| "baz" + +#data +foobar

baz +#errors +34: Stray “svg” start tag. +61: HTML start tag “p” in a foreign namespace context. +#document +| +| +| +| +| +| +| "foo" +| +| "bar" +|

+| "baz" + +#data +

+#errors +31: Stray “svg” start tag. +35: Stray “g” start tag. +40: Stray end tag “g” +44: Stray “g” start tag. +49: Stray end tag “g” +52: Stray “p” start tag. +58: Stray “span” start tag. +58: End of file seen and there were open elements. +#document +| +| +| +| + +#data +

+#errors +42: Stray “svg” start tag. +46: Stray “g” start tag. +51: Stray end tag “g” +55: Stray “g” start tag. +60: Stray end tag “g” +63: Stray “p” start tag. +69: Stray “span” start tag. +#document +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| xlink:href="foo" +| +| xlink href="foo" + +#data + +#errors +#document +| +| +| +| +| xlink:href="foo" +| xml:lang="en" +| +| +| xlink href="foo" +| xml lang="en" + +#data + +#errors +#document +| +| +| +| +| xlink:href="foo" +| xml:lang="en" +| +| +| xlink href="foo" +| xml lang="en" + +#data +bar +#errors +#document +| +| +| +| +| xlink:href="foo" +| xml:lang="en" +| +| +| xlink href="foo" +| xml lang="en" +| "bar" + +#data + +#errors +#document +| +| +| +| + +#data +

a +#errors +#document +| +| +| +|
+| +| "a" + +#data +
a +#errors +#document +| +| +| +|
+| +| +| "a" + +#data +
+#errors +#document +| +| +| +|
+| +| +| + +#data +
a +#errors +#document +| +| +| +|
+| +| +| +| +| "a" + +#data +

a +#errors +#document +| +| +| +|

+| +| +| +|

+| "a" + +#data +
    a +#errors +40: HTML start tag “ul” in a foreign namespace context. +41: End of file in a foreign namespace context. +#document +| +| +| +| +| +| +|
    +| +|
      +| "a" + +#data +
        a +#errors +35: HTML start tag “ul” in a foreign namespace context. +36: End of file in a foreign namespace context. +#document +| +| +| +| +| +| +| +|
          +| "a" + +#data +

          +#errors +#document +| +| +| +| +|

          +| +| +|

          + +#data +

          +#errors +#document +| +| +| +| +|

          +| +| +|

          + +#data +

          +#errors +#document +| +| +| +|

          +| +| +| +|

          +|

          + +#data +
          +#errors +#document +| +| +| +| +| +|
          +| +|
          +| +| + +#data +
          +#errors +#document +| +| +| +| +| +| +| +|
          +|
          +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data +

+#errors +#document +| +| +| +| +|
+| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| +| + +#data +
+#errors +#document +| +| +| +| +| +| +| +|
+| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| +| +| +| +| +| +| +| +| diff --git a/src/golang.org/x/net/html/testdata/webkit/tests11.dat b/src/golang.org/x/net/html/testdata/webkit/tests11.dat new file mode 100644 index 0000000000..638cde479f --- /dev/null +++ b/src/golang.org/x/net/html/testdata/webkit/tests11.dat @@ -0,0 +1,482 @@ +#data + +#errors +#document +| +| +| +| +| +| attributeName="" +| attributeType="" +| baseFrequency="" +| baseProfile="" +| calcMode="" +| clipPathUnits="" +| contentScriptType="" +| contentStyleType="" +| diffuseConstant="" +| edgeMode="" +| externalResourcesRequired="" +| filterRes="" +| filterUnits="" +| glyphRef="" +| gradientTransform="" +| gradientUnits="" +| kernelMatrix="" +| kernelUnitLength="" +| keyPoints="" +| keySplines="" +| keyTimes="" +| lengthAdjust="" +| limitingConeAngle="" +| markerHeight="" +| markerUnits="" +| markerWidth="" +| maskContentUnits="" +| maskUnits="" +| numOctaves="" +| pathLength="" +| patternContentUnits="" +| patternTransform="" +| patternUnits="" +| pointsAtX="" +| pointsAtY="" +| pointsAtZ="" +| preserveAlpha="" +| preserveAspectRatio="" +| primitiveUnits="" +| refX="" +| refY="" +| repeatCount="" +| repeatDur="" +| requiredExtensions="" +| requiredFeatures="" +| specularConstant="" +| specularExponent="" +| spreadMethod="" +| startOffset="" +| stdDeviation="" +| stitchTiles="" +| surfaceScale="" +| systemLanguage="" +| tableValues="" +| targetX="" +| targetY="" +| textLength="" +| viewBox="" +| viewTarget="" +| xChannelSelector="" +| yChannelSelector="" +| zoomAndPan="" + +#data + +#errors +#document +| +| +| +| +| +| attributeName="" +| attributeType="" +| baseFrequency="" +| baseProfile="" +| calcMode="" +| clipPathUnits="" +| contentScriptType="" +| contentStyleType="" +| diffuseConstant="" +| edgeMode="" +| externalResourcesRequired="" +| filterRes="" +| filterUnits="" +| glyphRef="" +| gradientTransform="" +| gradientUnits="" +| kernelMatrix="" +| kernelUnitLength="" +| keyPoints="" +| keySplines="" +| keyTimes="" +| lengthAdjust="" +| limitingConeAngle="" +| markerHeight="" +| markerUnits="" +| markerWidth="" +| maskContentUnits="" +| maskUnits="" +| numOctaves="" +| pathLength="" +| patternContentUnits="" +| patternTransform="" +| patternUnits="" +| pointsAtX="" +| pointsAtY="" +| pointsAtZ="" +| preserveAlpha="" +| preserveAspectRatio="" +| primitiveUnits="" +| refX="" +| refY="" +| repeatCount="" +| repeatDur="" +| requiredExtensions="" +| requiredFeatures="" +| specularConstant="" +| specularExponent="" +| spreadMethod="" +| startOffset="" +| stdDeviation="" +| stitchTiles="" +| surfaceScale="" +| systemLanguage="" +| tableValues="" +| targetX="" +| targetY="" +| textLength="" +| viewBox="" +| viewTarget="" +| xChannelSelector="" +| yChannelSelector="" +| zoomAndPan="" + +#data + +#errors +#document +| +| +| +| +| +| attributeName="" +| attributeType="" +| baseFrequency="" +| baseProfile="" +| calcMode="" +| clipPathUnits="" +| contentScriptType="" +| contentStyleType="" +| diffuseConstant="" +| edgeMode="" +| externalResourcesRequired="" +| filterRes="" +| filterUnits="" +| glyphRef="" +| gradientTransform="" +| gradientUnits="" +| kernelMatrix="" +| kernelUnitLength="" +| keyPoints="" +| keySplines="" +| keyTimes="" +| lengthAdjust="" +| limitingConeAngle="" +| markerHeight="" +| markerUnits="" +| markerWidth="" +| maskContentUnits="" +| maskUnits="" +| numOctaves="" +| pathLength="" +| patternContentUnits="" +| patternTransform="" +| patternUnits="" +| pointsAtX="" +| pointsAtY="" +| pointsAtZ="" +| preserveAlpha="" +| preserveAspectRatio="" +| primitiveUnits="" +| refX="" +| refY="" +| repeatCount="" +| repeatDur="" +| requiredExtensions="" +| requiredFeatures="" +| specularConstant="" +| specularExponent="" +| spreadMethod="" +| startOffset="" +| stdDeviation="" +| stitchTiles="" +| surfaceScale="" +| systemLanguage="" +| tableValues="" +| targetX="" +| targetY="" +| textLength="" +| viewBox="" +| viewTarget="" +| xChannelSelector="" +| yChannelSelector="" +| zoomAndPan="" + +#data + +#errors +#document +| +| +| +| +| +| attributename="" +| attributetype="" +| basefrequency="" +| baseprofile="" +| calcmode="" +| clippathunits="" +| contentscripttype="" +| contentstyletype="" +| diffuseconstant="" +| edgemode="" +| externalresourcesrequired="" +| filterres="" +| filterunits="" +| glyphref="" +| gradienttransform="" +| gradientunits="" +| kernelmatrix="" +| kernelunitlength="" +| keypoints="" +| keysplines="" +| keytimes="" +| lengthadjust="" +| limitingconeangle="" +| markerheight="" +| markerunits="" +| markerwidth="" +| maskcontentunits="" +| maskunits="" +| numoctaves="" +| pathlength="" +| patterncontentunits="" +| patterntransform="" +| patternunits="" +| pointsatx="" +| pointsaty="" +| pointsatz="" +| preservealpha="" +| preserveaspectratio="" +| primitiveunits="" +| refx="" +| refy="" +| repeatcount="" +| repeatdur="" +| requiredextensions="" +| requiredfeatures="" +| specularconstant="" +| specularexponent="" +| spreadmethod="" +| startoffset="" +| stddeviation="" +| stitchtiles="" +| surfacescale="" +| systemlanguage="" +| tablevalues="" +| targetx="" +| targety="" +| textlength="" +| viewbox="" +| viewtarget="" +| xchannelselector="" +| ychannelselector="" +| zoomandpan="" + +#data + +#errors +#document +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| diff --git a/src/golang.org/x/net/html/testdata/webkit/tests12.dat b/src/golang.org/x/net/html/testdata/webkit/tests12.dat new file mode 100644 index 0000000000..63107d277b --- /dev/null +++ b/src/golang.org/x/net/html/testdata/webkit/tests12.dat @@ -0,0 +1,62 @@ +#data +

foobazeggs

spam

quuxbar +#errors +#document +| +| +| +| +|

+| "foo" +| +| +| +| "baz" +| +| +| +| +| "eggs" +| +| +|

+| "spam" +| +| +| +|
+| +| +| "quux" +| "bar" + +#data +foobazeggs

spam
quuxbar +#errors +#document +| +| +| +| +| "foo" +| +| +| +| "baz" +| +| +| +| +| "eggs" +| +| +|

+| "spam" +| +| +| +|
+| +| +| "quux" +| "bar" diff --git a/src/golang.org/x/net/html/testdata/webkit/tests14.dat b/src/golang.org/x/net/html/testdata/webkit/tests14.dat new file mode 100644 index 0000000000..b8713f8858 --- /dev/null +++ b/src/golang.org/x/net/html/testdata/webkit/tests14.dat @@ -0,0 +1,74 @@ +#data + +#errors +#document +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +15: Unexpected start tag html +#document +| +| +| abc:def="gh" +| +| +| + +#data + +#errors +15: Unexpected start tag html +#document +| +| +| xml:lang="bar" +| +| + +#data + +#errors +#document +| +| +| 123="456" +| +| + +#data + +#errors +#document +| +| +| 123="456" +| 789="012" +| +| + +#data + +#errors +#document +| +| +| +| +| 789="012" diff --git a/src/golang.org/x/net/html/testdata/webkit/tests15.dat b/src/golang.org/x/net/html/testdata/webkit/tests15.dat new file mode 100644 index 0000000000..6ce1c0d166 --- /dev/null +++ b/src/golang.org/x/net/html/testdata/webkit/tests15.dat @@ -0,0 +1,208 @@ +#data +

X +#errors +Line: 1 Col: 31 Unexpected end tag (p). Ignored. +Line: 1 Col: 36 Expected closing tag. Unexpected end of file. +#document +| +| +| +| +|

+| +| +| +| +| +| +| " " +|

+| "X" + +#data +

+

X +#errors +Line: 1 Col: 3 Unexpected start tag (p). Expected DOCTYPE. +Line: 1 Col: 16 Unexpected end tag (p). Ignored. +Line: 2 Col: 4 Expected closing tag. Unexpected end of file. +#document +| +| +| +|

+| +| +| +| +| +| +| " +" +|

+| "X" + +#data + +#errors +Line: 1 Col: 22 Unexpected end tag (html) after the (implied) root element. +#document +| +| +| +| +| " " + +#data + +#errors +Line: 1 Col: 22 Unexpected end tag (body) after the (implied) root element. +#document +| +| +| +| +| + +#data + +#errors +Line: 1 Col: 6 Unexpected start tag (html). Expected DOCTYPE. +Line: 1 Col: 13 Unexpected end tag (html) after the (implied) root element. +#document +| +| +| +| + +#data +X +#errors +Line: 1 Col: 22 Unexpected end tag (body) after the (implied) root element. +#document +| +| +| +| +| +| "X" + +#data +<!doctype html><table> X<meta></table> +#errors +Line: 1 Col: 24 Unexpected non-space characters in table context caused voodoo mode. +Line: 1 Col: 30 Unexpected start tag (meta) in table context caused voodoo mode. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| " X" +| <meta> +| <table> + +#data +<!doctype html><table> x</table> +#errors +Line: 1 Col: 24 Unexpected non-space characters in table context caused voodoo mode. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| " x" +| <table> + +#data +<!doctype html><table> x </table> +#errors +Line: 1 Col: 25 Unexpected non-space characters in table context caused voodoo mode. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| " x " +| <table> + +#data +<!doctype html><table><tr> x</table> +#errors +Line: 1 Col: 28 Unexpected non-space characters in table context caused voodoo mode. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| " x" +| <table> +| <tbody> +| <tr> + +#data +<!doctype html><table>X<style> <tr>x </style> </table> +#errors +Line: 1 Col: 23 Unexpected non-space characters in table context caused voodoo mode. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "X" +| <table> +| <style> +| " <tr>x " +| " " + +#data +<!doctype html><div><table><a>foo</a> <tr><td>bar</td> </tr></table></div> +#errors +Line: 1 Col: 30 Unexpected start tag (a) in table context caused voodoo mode. +Line: 1 Col: 37 Unexpected end tag (a) in table context caused voodoo mode. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <div> +| <a> +| "foo" +| <table> +| " " +| <tbody> +| <tr> +| <td> +| "bar" +| " " + +#data +<frame></frame></frame><frameset><frame><frameset><frame></frameset><noframes></frameset><noframes> +#errors +6: Start tag seen without seeing a doctype first. Expected “<!DOCTYPE html>”. +13: Stray start tag “frame”. +21: Stray end tag “frame”. +29: Stray end tag “frame”. +39: “frameset” start tag after “body” already open. +105: End of file seen inside an [R]CDATA element. +105: End of file seen and there were open elements. +XXX: These errors are wrong, please fix me! +#document +| <html> +| <head> +| <frameset> +| <frame> +| <frameset> +| <frame> +| <noframes> +| "</frameset><noframes>" + +#data +<!DOCTYPE html><object></html> +#errors +1: Expected closing tag. Unexpected end of file +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <object> diff --git a/src/golang.org/x/net/html/testdata/webkit/tests16.dat b/src/golang.org/x/net/html/testdata/webkit/tests16.dat new file mode 100644 index 0000000000..c8ef66f0e6 --- /dev/null +++ b/src/golang.org/x/net/html/testdata/webkit/tests16.dat @@ -0,0 +1,2299 @@ +#data +<!doctype html><script> +#errors +Line: 1 Col: 23 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| <body> + +#data +<!doctype html><script>a +#errors +Line: 1 Col: 24 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "a" +| <body> + +#data +<!doctype html><script>< +#errors +Line: 1 Col: 24 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<" +| <body> + +#data +<!doctype html><script></ +#errors +Line: 1 Col: 25 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</" +| <body> + +#data +<!doctype html><script></S +#errors +Line: 1 Col: 26 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</S" +| <body> + +#data +<!doctype html><script></SC +#errors +Line: 1 Col: 27 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</SC" +| <body> + +#data +<!doctype html><script></SCR +#errors +Line: 1 Col: 28 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</SCR" +| <body> + +#data +<!doctype html><script></SCRI +#errors +Line: 1 Col: 29 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</SCRI" +| <body> + +#data +<!doctype html><script></SCRIP +#errors +Line: 1 Col: 30 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</SCRIP" +| <body> + +#data +<!doctype html><script></SCRIPT +#errors +Line: 1 Col: 31 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</SCRIPT" +| <body> + +#data +<!doctype html><script></SCRIPT +#errors +Line: 1 Col: 32 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| <body> + +#data +<!doctype html><script></s +#errors +Line: 1 Col: 26 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</s" +| <body> + +#data +<!doctype html><script></sc +#errors +Line: 1 Col: 27 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</sc" +| <body> + +#data +<!doctype html><script></scr +#errors +Line: 1 Col: 28 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</scr" +| <body> + +#data +<!doctype html><script></scri +#errors +Line: 1 Col: 29 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</scri" +| <body> + +#data +<!doctype html><script></scrip +#errors +Line: 1 Col: 30 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</scrip" +| <body> + +#data +<!doctype html><script></script +#errors +Line: 1 Col: 31 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</script" +| <body> + +#data +<!doctype html><script></script +#errors +Line: 1 Col: 32 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| <body> + +#data +<!doctype html><script><! +#errors +Line: 1 Col: 25 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!" +| <body> + +#data +<!doctype html><script><!a +#errors +Line: 1 Col: 26 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!a" +| <body> + +#data +<!doctype html><script><!- +#errors +Line: 1 Col: 26 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!-" +| <body> + +#data +<!doctype html><script><!-a +#errors +Line: 1 Col: 27 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!-a" +| <body> + +#data +<!doctype html><script><!-- +#errors +Line: 1 Col: 27 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--" +| <body> + +#data +<!doctype html><script><!--a +#errors +Line: 1 Col: 28 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--a" +| <body> + +#data +<!doctype html><script><!--< +#errors +Line: 1 Col: 28 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<" +| <body> + +#data +<!doctype html><script><!--<a +#errors +Line: 1 Col: 29 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<a" +| <body> + +#data +<!doctype html><script><!--</ +#errors +Line: 1 Col: 27 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--</" +| <body> + +#data +<!doctype html><script><!--</script +#errors +Line: 1 Col: 35 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--</script" +| <body> + +#data +<!doctype html><script><!--</script +#errors +Line: 1 Col: 36 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--" +| <body> + +#data +<!doctype html><script><!--<s +#errors +Line: 1 Col: 29 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<s" +| <body> + +#data +<!doctype html><script><!--<script +#errors +Line: 1 Col: 34 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script" +| <body> + +#data +<!doctype html><script><!--<script +#errors +Line: 1 Col: 35 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script " +| <body> + +#data +<!doctype html><script><!--<script < +#errors +Line: 1 Col: 36 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script <" +| <body> + +#data +<!doctype html><script><!--<script <a +#errors +Line: 1 Col: 37 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script <a" +| <body> + +#data +<!doctype html><script><!--<script </ +#errors +Line: 1 Col: 37 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </" +| <body> + +#data +<!doctype html><script><!--<script </s +#errors +Line: 1 Col: 38 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </s" +| <body> + +#data +<!doctype html><script><!--<script </script +#errors +Line: 1 Col: 43 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script" +| <body> + +#data +<!doctype html><script><!--<script </scripta +#errors +Line: 1 Col: 44 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </scripta" +| <body> + +#data +<!doctype html><script><!--<script </script +#errors +Line: 1 Col: 44 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script " +| <body> + +#data +<!doctype html><script><!--<script </script> +#errors +Line: 1 Col: 44 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script>" +| <body> + +#data +<!doctype html><script><!--<script </script/ +#errors +Line: 1 Col: 44 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script/" +| <body> + +#data +<!doctype html><script><!--<script </script < +#errors +Line: 1 Col: 45 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script <" +| <body> + +#data +<!doctype html><script><!--<script </script <a +#errors +Line: 1 Col: 46 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script <a" +| <body> + +#data +<!doctype html><script><!--<script </script </ +#errors +Line: 1 Col: 46 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script </" +| <body> + +#data +<!doctype html><script><!--<script </script </script +#errors +Line: 1 Col: 52 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script </script" +| <body> + +#data +<!doctype html><script><!--<script </script </script +#errors +Line: 1 Col: 53 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script " +| <body> + +#data +<!doctype html><script><!--<script </script </script/ +#errors +Line: 1 Col: 53 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script " +| <body> + +#data +<!doctype html><script><!--<script </script </script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script " +| <body> + +#data +<!doctype html><script><!--<script - +#errors +Line: 1 Col: 36 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script -" +| <body> + +#data +<!doctype html><script><!--<script -a +#errors +Line: 1 Col: 37 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script -a" +| <body> + +#data +<!doctype html><script><!--<script -< +#errors +Line: 1 Col: 37 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script -<" +| <body> + +#data +<!doctype html><script><!--<script -- +#errors +Line: 1 Col: 37 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script --" +| <body> + +#data +<!doctype html><script><!--<script --a +#errors +Line: 1 Col: 38 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script --a" +| <body> + +#data +<!doctype html><script><!--<script --< +#errors +Line: 1 Col: 38 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script --<" +| <body> + +#data +<!doctype html><script><!--<script --> +#errors +Line: 1 Col: 38 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script -->" +| <body> + +#data +<!doctype html><script><!--<script -->< +#errors +Line: 1 Col: 39 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script --><" +| <body> + +#data +<!doctype html><script><!--<script --></ +#errors +Line: 1 Col: 40 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script --></" +| <body> + +#data +<!doctype html><script><!--<script --></script +#errors +Line: 1 Col: 46 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script --></script" +| <body> + +#data +<!doctype html><script><!--<script --></script +#errors +Line: 1 Col: 47 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script -->" +| <body> + +#data +<!doctype html><script><!--<script --></script/ +#errors +Line: 1 Col: 47 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script -->" +| <body> + +#data +<!doctype html><script><!--<script --></script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script -->" +| <body> + +#data +<!doctype html><script><!--<script><\/script>--></script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script><\/script>-->" +| <body> + +#data +<!doctype html><script><!--<script></scr'+'ipt>--></script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script></scr'+'ipt>-->" +| <body> + +#data +<!doctype html><script><!--<script></script><script></script></script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>" +| <body> + +#data +<!doctype html><script><!--<script></script><script></script>--><!--</script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>--><!--" +| <body> + +#data +<!doctype html><script><!--<script></script><script></script>-- ></script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>-- >" +| <body> + +#data +<!doctype html><script><!--<script></script><script></script>- -></script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>- ->" +| <body> + +#data +<!doctype html><script><!--<script></script><script></script>- - ></script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>- - >" +| <body> + +#data +<!doctype html><script><!--<script></script><script></script>-></script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>->" +| <body> + +#data +<!doctype html><script><!--<script>--!></script>X +#errors +Line: 1 Col: 49 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script>--!></script>X" +| <body> + +#data +<!doctype html><script><!--<scr'+'ipt></script>--></script> +#errors +Line: 1 Col: 59 Unexpected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<scr'+'ipt>" +| <body> +| "-->" + +#data +<!doctype html><script><!--<script></scr'+'ipt></script>X +#errors +Line: 1 Col: 57 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script></scr'+'ipt></script>X" +| <body> + +#data +<!doctype html><style><!--<style></style>--></style> +#errors +Line: 1 Col: 52 Unexpected end tag (style). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <style> +| "<!--<style>" +| <body> +| "-->" + +#data +<!doctype html><style><!--</style>X +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <style> +| "<!--" +| <body> +| "X" + +#data +<!doctype html><style><!--...</style>...--></style> +#errors +Line: 1 Col: 51 Unexpected end tag (style). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <style> +| "<!--..." +| <body> +| "...-->" + +#data +<!doctype html><style><!--<br><html xmlns:v="urn:schemas-microsoft-com:vml"><!--[if !mso]><style></style>X +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <style> +| "<!--<br><html xmlns:v="urn:schemas-microsoft-com:vml"><!--[if !mso]><style>" +| <body> +| "X" + +#data +<!doctype html><style><!--...<style><!--...--!></style>--></style> +#errors +Line: 1 Col: 66 Unexpected end tag (style). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <style> +| "<!--...<style><!--...--!>" +| <body> +| "-->" + +#data +<!doctype html><style><!--...</style><!-- --><style>@import ...</style> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <style> +| "<!--..." +| <!-- --> +| <style> +| "@import ..." +| <body> + +#data +<!doctype html><style>...<style><!--...</style><!-- --></style> +#errors +Line: 1 Col: 63 Unexpected end tag (style). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <style> +| "...<style><!--..." +| <!-- --> +| <body> + +#data +<!doctype html><style>...<!--[if IE]><style>...</style>X +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <style> +| "...<!--[if IE]><style>..." +| <body> +| "X" + +#data +<!doctype html><title><!--<title>--> +#errors +Line: 1 Col: 52 Unexpected end tag (title). +#document +| +| +| +| +| "<!--<title>" +| <body> +| "-->" + +#data +<!doctype html><title></title> +#errors +#document +| +| +| +| +| "" +| + +#data +foo/title><link></head><body>X +#errors +Line: 1 Col: 52 Unexpected end of file. Expected end tag (title). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <title> +| "foo/title><link></head><body>X" +| <body> + +#data +<!doctype html><noscript><!--<noscript></noscript>--></noscript> +#errors +Line: 1 Col: 64 Unexpected end tag (noscript). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <noscript> +| "<!--<noscript>" +| <body> +| "-->" + +#data +<!doctype html><noscript><!--</noscript>X<noscript>--></noscript> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <noscript> +| "<!--" +| <body> +| "X" +| <noscript> +| "-->" + +#data +<!doctype html><noscript><iframe></noscript>X +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <noscript> +| "<iframe>" +| <body> +| "X" + +#data +<!doctype html><noframes><!--<noframes></noframes>--></noframes> +#errors +Line: 1 Col: 64 Unexpected end tag (noframes). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <noframes> +| "<!--<noframes>" +| <body> +| "-->" + +#data +<!doctype html><noframes><body><script><!--...</script></body></noframes></html> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <noframes> +| "<body><script><!--...</script></body>" +| <body> + +#data +<!doctype html><textarea><!--<textarea></textarea>--></textarea> +#errors +Line: 1 Col: 64 Unexpected end tag (textarea). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <textarea> +| "<!--<textarea>" +| "-->" + +#data +<!doctype html><textarea></textarea></textarea> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <textarea> +| "</textarea>" + +#data +<!doctype html><textarea><</textarea> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <textarea> +| "<" + +#data +<!doctype html><textarea>a<b</textarea> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <textarea> +| "a<b" + +#data +<!doctype html><iframe><!--<iframe></iframe>--></iframe> +#errors +Line: 1 Col: 56 Unexpected end tag (iframe). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <iframe> +| "<!--<iframe>" +| "-->" + +#data +<!doctype html><iframe>...<!--X->...<!--/X->...</iframe> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <iframe> +| "...<!--X->...<!--/X->..." + +#data +<!doctype html><xmp><!--<xmp></xmp>--></xmp> +#errors +Line: 1 Col: 44 Unexpected end tag (xmp). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <xmp> +| "<!--<xmp>" +| "-->" + +#data +<!doctype html><noembed><!--<noembed></noembed>--></noembed> +#errors +Line: 1 Col: 60 Unexpected end tag (noembed). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <noembed> +| "<!--<noembed>" +| "-->" + +#data +<script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 8 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| <body> + +#data +<script>a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 9 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "a" +| <body> + +#data +<script>< +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 9 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<" +| <body> + +#data +<script></ +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 10 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</" +| <body> + +#data +<script></S +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 11 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</S" +| <body> + +#data +<script></SC +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 12 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</SC" +| <body> + +#data +<script></SCR +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 13 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</SCR" +| <body> + +#data +<script></SCRI +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 14 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</SCRI" +| <body> + +#data +<script></SCRIP +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 15 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</SCRIP" +| <body> + +#data +<script></SCRIPT +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 16 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</SCRIPT" +| <body> + +#data +<script></SCRIPT +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 17 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| <body> + +#data +<script></s +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 11 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</s" +| <body> + +#data +<script></sc +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 12 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</sc" +| <body> + +#data +<script></scr +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 13 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</scr" +| <body> + +#data +<script></scri +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 14 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</scri" +| <body> + +#data +<script></scrip +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 15 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</scrip" +| <body> + +#data +<script></script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 16 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</script" +| <body> + +#data +<script></script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 17 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| <body> + +#data +<script><! +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 10 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!" +| <body> + +#data +<script><!a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 11 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!a" +| <body> + +#data +<script><!- +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 11 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!-" +| <body> + +#data +<script><!-a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 12 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!-a" +| <body> + +#data +<script><!-- +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 12 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--" +| <body> + +#data +<script><!--a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 13 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--a" +| <body> + +#data +<script><!--< +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 13 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<" +| <body> + +#data +<script><!--<a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 14 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<a" +| <body> + +#data +<script><!--</ +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 14 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--</" +| <body> + +#data +<script><!--</script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 20 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--</script" +| <body> + +#data +<script><!--</script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 21 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--" +| <body> + +#data +<script><!--<s +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 14 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<s" +| <body> + +#data +<script><!--<script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 19 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script" +| <body> + +#data +<script><!--<script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 20 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script " +| <body> + +#data +<script><!--<script < +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 21 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script <" +| <body> + +#data +<script><!--<script <a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 22 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script <a" +| <body> + +#data +<script><!--<script </ +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 22 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </" +| <body> + +#data +<script><!--<script </s +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 23 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </s" +| <body> + +#data +<script><!--<script </script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 28 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script" +| <body> + +#data +<script><!--<script </scripta +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 29 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </scripta" +| <body> + +#data +<script><!--<script </script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 29 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script " +| <body> + +#data +<script><!--<script </script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 29 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script>" +| <body> + +#data +<script><!--<script </script/ +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 29 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script/" +| <body> + +#data +<script><!--<script </script < +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 30 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script <" +| <body> + +#data +<script><!--<script </script <a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 31 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script <a" +| <body> + +#data +<script><!--<script </script </ +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 31 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script </" +| <body> + +#data +<script><!--<script </script </script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 38 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script </script" +| <body> + +#data +<script><!--<script </script </script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 38 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script " +| <body> + +#data +<script><!--<script </script </script/ +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 38 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script " +| <body> + +#data +<script><!--<script </script </script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script </script " +| <body> + +#data +<script><!--<script - +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 21 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script -" +| <body> + +#data +<script><!--<script -a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 22 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script -a" +| <body> + +#data +<script><!--<script -- +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 22 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script --" +| <body> + +#data +<script><!--<script --a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 23 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script --a" +| <body> + +#data +<script><!--<script --> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 23 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script -->" +| <body> + +#data +<script><!--<script -->< +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 24 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script --><" +| <body> + +#data +<script><!--<script --></ +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 25 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script --></" +| <body> + +#data +<script><!--<script --></script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 31 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script --></script" +| <body> + +#data +<script><!--<script --></script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 32 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script -->" +| <body> + +#data +<script><!--<script --></script/ +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 32 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script -->" +| <body> + +#data +<script><!--<script --></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script -->" +| <body> + +#data +<script><!--<script><\/script>--></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script><\/script>-->" +| <body> + +#data +<script><!--<script></scr'+'ipt>--></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script></scr'+'ipt>-->" +| <body> + +#data +<script><!--<script></script><script></script></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>" +| <body> + +#data +<script><!--<script></script><script></script>--><!--</script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>--><!--" +| <body> + +#data +<script><!--<script></script><script></script>-- ></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>-- >" +| <body> + +#data +<script><!--<script></script><script></script>- -></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>- ->" +| <body> + +#data +<script><!--<script></script><script></script>- - ></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>- - >" +| <body> + +#data +<script><!--<script></script><script></script>-></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>->" +| <body> + +#data +<script><!--<script>--!></script>X +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 34 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script>--!></script>X" +| <body> + +#data +<script><!--<scr'+'ipt></script>--></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 44 Unexpected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<scr'+'ipt>" +| <body> +| "-->" + +#data +<script><!--<script></scr'+'ipt></script>X +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 42 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script></scr'+'ipt></script>X" +| <body> + +#data +<style><!--<style></style>--></style> +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +Line: 1 Col: 37 Unexpected end tag (style). +#document +| <html> +| <head> +| <style> +| "<!--<style>" +| <body> +| "-->" + +#data +<style><!--</style>X +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +#document +| <html> +| <head> +| <style> +| "<!--" +| <body> +| "X" + +#data +<style><!--...</style>...--></style> +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +Line: 1 Col: 36 Unexpected end tag (style). +#document +| <html> +| <head> +| <style> +| "<!--..." +| <body> +| "...-->" + +#data +<style><!--<br><html xmlns:v="urn:schemas-microsoft-com:vml"><!--[if !mso]><style></style>X +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +#document +| <html> +| <head> +| <style> +| "<!--<br><html xmlns:v="urn:schemas-microsoft-com:vml"><!--[if !mso]><style>" +| <body> +| "X" + +#data +<style><!--...<style><!--...--!></style>--></style> +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +Line: 1 Col: 51 Unexpected end tag (style). +#document +| <html> +| <head> +| <style> +| "<!--...<style><!--...--!>" +| <body> +| "-->" + +#data +<style><!--...</style><!-- --><style>@import ...</style> +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +#document +| <html> +| <head> +| <style> +| "<!--..." +| <!-- --> +| <style> +| "@import ..." +| <body> + +#data +<style>...<style><!--...</style><!-- --></style> +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +Line: 1 Col: 48 Unexpected end tag (style). +#document +| <html> +| <head> +| <style> +| "...<style><!--..." +| <!-- --> +| <body> + +#data +<style>...<!--[if IE]><style>...</style>X +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +#document +| <html> +| <head> +| <style> +| "...<!--[if IE]><style>..." +| <body> +| "X" + +#data +<title><!--<title>--> +#errors +Line: 1 Col: 7 Unexpected start tag (title). Expected DOCTYPE. +Line: 1 Col: 37 Unexpected end tag (title). +#document +| +| +| +| "<!--<title>" +| <body> +| "-->" + +#data +<title></title> +#errors +Line: 1 Col: 7 Unexpected start tag (title). Expected DOCTYPE. +#document +| +| +| +| "" +| + +#data +foo/title><link></head><body>X +#errors +Line: 1 Col: 7 Unexpected start tag (title). Expected DOCTYPE. +Line: 1 Col: 37 Unexpected end of file. Expected end tag (title). +#document +| <html> +| <head> +| <title> +| "foo/title><link></head><body>X" +| <body> + +#data +<noscript><!--<noscript></noscript>--></noscript> +#errors +Line: 1 Col: 10 Unexpected start tag (noscript). Expected DOCTYPE. +Line: 1 Col: 49 Unexpected end tag (noscript). +#document +| <html> +| <head> +| <noscript> +| "<!--<noscript>" +| <body> +| "-->" + +#data +<noscript><!--</noscript>X<noscript>--></noscript> +#errors +Line: 1 Col: 10 Unexpected start tag (noscript). Expected DOCTYPE. +#document +| <html> +| <head> +| <noscript> +| "<!--" +| <body> +| "X" +| <noscript> +| "-->" + +#data +<noscript><iframe></noscript>X +#errors +Line: 1 Col: 10 Unexpected start tag (noscript). Expected DOCTYPE. +#document +| <html> +| <head> +| <noscript> +| "<iframe>" +| <body> +| "X" + +#data +<noframes><!--<noframes></noframes>--></noframes> +#errors +Line: 1 Col: 10 Unexpected start tag (noframes). Expected DOCTYPE. +Line: 1 Col: 49 Unexpected end tag (noframes). +#document +| <html> +| <head> +| <noframes> +| "<!--<noframes>" +| <body> +| "-->" + +#data +<noframes><body><script><!--...</script></body></noframes></html> +#errors +Line: 1 Col: 10 Unexpected start tag (noframes). Expected DOCTYPE. +#document +| <html> +| <head> +| <noframes> +| "<body><script><!--...</script></body>" +| <body> + +#data +<textarea><!--<textarea></textarea>--></textarea> +#errors +Line: 1 Col: 10 Unexpected start tag (textarea). Expected DOCTYPE. +Line: 1 Col: 49 Unexpected end tag (textarea). +#document +| <html> +| <head> +| <body> +| <textarea> +| "<!--<textarea>" +| "-->" + +#data +<textarea></textarea></textarea> +#errors +Line: 1 Col: 10 Unexpected start tag (textarea). Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| <textarea> +| "</textarea>" + +#data +<iframe><!--<iframe></iframe>--></iframe> +#errors +Line: 1 Col: 8 Unexpected start tag (iframe). Expected DOCTYPE. +Line: 1 Col: 41 Unexpected end tag (iframe). +#document +| <html> +| <head> +| <body> +| <iframe> +| "<!--<iframe>" +| "-->" + +#data +<iframe>...<!--X->...<!--/X->...</iframe> +#errors +Line: 1 Col: 8 Unexpected start tag (iframe). Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| <iframe> +| "...<!--X->...<!--/X->..." + +#data +<xmp><!--<xmp></xmp>--></xmp> +#errors +Line: 1 Col: 5 Unexpected start tag (xmp). Expected DOCTYPE. +Line: 1 Col: 29 Unexpected end tag (xmp). +#document +| <html> +| <head> +| <body> +| <xmp> +| "<!--<xmp>" +| "-->" + +#data +<noembed><!--<noembed></noembed>--></noembed> +#errors +Line: 1 Col: 9 Unexpected start tag (noembed). Expected DOCTYPE. +Line: 1 Col: 45 Unexpected end tag (noembed). +#document +| <html> +| <head> +| <body> +| <noembed> +| "<!--<noembed>" +| "-->" + +#data +<!doctype html><table> + +#errors +Line 2 Col 0 Unexpected end of file. Expected table content. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| " +" + +#data +<!doctype html><table><td><span><font></span><span> +#errors +Line 1 Col 26 Unexpected table cell start tag (td) in the table body phase. +Line 1 Col 45 Unexpected end tag (span). +Line 1 Col 51 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <td> +| <span> +| <font> +| <font> +| <span> + +#data +<!doctype html><form><table></form><form></table></form> +#errors +35: Stray end tag “form”. +41: Start tag “form” seen in “table”. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <form> +| <table> +| <form> diff --git a/src/golang.org/x/net/html/testdata/webkit/tests17.dat b/src/golang.org/x/net/html/testdata/webkit/tests17.dat new file mode 100644 index 0000000000..7b555f888d --- /dev/null +++ b/src/golang.org/x/net/html/testdata/webkit/tests17.dat @@ -0,0 +1,153 @@ +#data +<!doctype html><table><tbody><select><tr> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <table> +| <tbody> +| <tr> + +#data +<!doctype html><table><tr><select><td> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <table> +| <tbody> +| <tr> +| <td> + +#data +<!doctype html><table><tr><td><select><td> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <td> +| <select> +| <td> + +#data +<!doctype html><table><tr><th><select><td> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <th> +| <select> +| <td> + +#data +<!doctype html><table><caption><select><tr> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <caption> +| <select> +| <tbody> +| <tr> + +#data +<!doctype html><select><tr> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> + +#data +<!doctype html><select><td> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> + +#data +<!doctype html><select><th> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> + +#data +<!doctype html><select><tbody> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> + +#data +<!doctype html><select><thead> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> + +#data +<!doctype html><select><tfoot> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> + +#data +<!doctype html><select><caption> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> + +#data +<!doctype html><table><tr></table>a +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| "a" diff --git a/src/golang.org/x/net/html/testdata/webkit/tests18.dat b/src/golang.org/x/net/html/testdata/webkit/tests18.dat new file mode 100644 index 0000000000..680e1f068a --- /dev/null +++ b/src/golang.org/x/net/html/testdata/webkit/tests18.dat @@ -0,0 +1,269 @@ +#data +<!doctype html><plaintext></plaintext> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <plaintext> +| "</plaintext>" + +#data +<!doctype html><table><plaintext></plaintext> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <plaintext> +| "</plaintext>" +| <table> + +#data +<!doctype html><table><tbody><plaintext></plaintext> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <plaintext> +| "</plaintext>" +| <table> +| <tbody> + +#data +<!doctype html><table><tbody><tr><plaintext></plaintext> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <plaintext> +| "</plaintext>" +| <table> +| <tbody> +| <tr> + +#data +<!doctype html><table><tbody><tr><plaintext></plaintext> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <plaintext> +| "</plaintext>" +| <table> +| <tbody> +| <tr> + +#data +<!doctype html><table><td><plaintext></plaintext> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <td> +| <plaintext> +| "</plaintext>" + +#data +<!doctype html><table><caption><plaintext></plaintext> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <caption> +| <plaintext> +| "</plaintext>" + +#data +<!doctype html><table><tr><style></script></style>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "abc" +| <table> +| <tbody> +| <tr> +| <style> +| "</script>" + +#data +<!doctype html><table><tr><script></style></script>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "abc" +| <table> +| <tbody> +| <tr> +| <script> +| "</style>" + +#data +<!doctype html><table><caption><style></script></style>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <caption> +| <style> +| "</script>" +| "abc" + +#data +<!doctype html><table><td><style></script></style>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <td> +| <style> +| "</script>" +| "abc" + +#data +<!doctype html><select><script></style></script>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <script> +| "</style>" +| "abc" + +#data +<!doctype html><table><select><script></style></script>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <script> +| "</style>" +| "abc" +| <table> + +#data +<!doctype html><table><tr><select><script></style></script>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <script> +| "</style>" +| "abc" +| <table> +| <tbody> +| <tr> + +#data +<!doctype html><frameset></frameset><noframes>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <noframes> +| "abc" + +#data +<!doctype html><frameset></frameset><noframes>abc</noframes><!--abc--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <noframes> +| "abc" +| <!-- abc --> + +#data +<!doctype html><frameset></frameset></html><noframes>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <noframes> +| "abc" + +#data +<!doctype html><frameset></frameset></html><noframes>abc</noframes><!--abc--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <noframes> +| "abc" +| <!-- abc --> + +#data +<!doctype html><table><tr></tbody><tfoot> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <tfoot> + +#data +<!doctype html><table><td><svg></svg>abc<td> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <td> +| <svg svg> +| "abc" +| <td> diff --git a/src/golang.org/x/net/html/testdata/webkit/tests19.dat b/src/golang.org/x/net/html/testdata/webkit/tests19.dat new file mode 100644 index 0000000000..0d62f5a5b0 --- /dev/null +++ b/src/golang.org/x/net/html/testdata/webkit/tests19.dat @@ -0,0 +1,1237 @@ +#data +<!doctype html><math><mn DefinitionUrl="foo"> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <math math> +| <math mn> +| definitionURL="foo" + +#data +<!doctype html><html></p><!--foo--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <!-- foo --> +| <head> +| <body> + +#data +<!doctype html><head></head></p><!--foo--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <!-- foo --> +| <body> + +#data +<!doctype html><body><p><pre> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <pre> + +#data +<!doctype html><body><p><listing> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <listing> + +#data +<!doctype html><p><plaintext> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <plaintext> + +#data +<!doctype html><p><h1> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <h1> + +#data +<!doctype html><form><isindex> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <form> + +#data +<!doctype html><isindex action="POST"> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <form> +| action="POST" +| <hr> +| <label> +| "This is a searchable index. Enter search keywords: " +| <input> +| name="isindex" +| <hr> + +#data +<!doctype html><isindex prompt="this is isindex"> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <form> +| <hr> +| <label> +| "this is isindex" +| <input> +| name="isindex" +| <hr> + +#data +<!doctype html><isindex type="hidden"> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <form> +| <hr> +| <label> +| "This is a searchable index. Enter search keywords: " +| <input> +| name="isindex" +| type="hidden" +| <hr> + +#data +<!doctype html><isindex name="foo"> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <form> +| <hr> +| <label> +| "This is a searchable index. Enter search keywords: " +| <input> +| name="isindex" +| <hr> + +#data +<!doctype html><ruby><p><rp> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <ruby> +| <p> +| <rp> + +#data +<!doctype html><ruby><div><span><rp> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <ruby> +| <div> +| <span> +| <rp> + +#data +<!doctype html><ruby><div><p><rp> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <ruby> +| <div> +| <p> +| <rp> + +#data +<!doctype html><ruby><p><rt> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <ruby> +| <p> +| <rt> + +#data +<!doctype html><ruby><div><span><rt> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <ruby> +| <div> +| <span> +| <rt> + +#data +<!doctype html><ruby><div><p><rt> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <ruby> +| <div> +| <p> +| <rt> + +#data +<!doctype html><math/><foo> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <math math> +| <foo> + +#data +<!doctype html><svg/><foo> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <svg svg> +| <foo> + +#data +<!doctype html><div></body><!--foo--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <div> +| <!-- foo --> + +#data +<!doctype html><h1><div><h3><span></h1>foo +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <h1> +| <div> +| <h3> +| <span> +| "foo" + +#data +<!doctype html><p></h3>foo +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| "foo" + +#data +<!doctype html><h3><li>abc</h2>foo +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <h3> +| <li> +| "abc" +| "foo" + +#data +<!doctype html><table>abc<!--foo--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "abc" +| <table> +| <!-- foo --> + +#data +<!doctype html><table> <!--foo--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| " " +| <!-- foo --> + +#data +<!doctype html><table> b <!--foo--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| " b " +| <table> +| <!-- foo --> + +#data +<!doctype html><select><option><option> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <option> +| <option> + +#data +<!doctype html><select><option></optgroup> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <option> + +#data +<!doctype html><select><option></optgroup> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <option> + +#data +<!doctype html><p><math><mi><p><h1> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <math math> +| <math mi> +| <p> +| <h1> + +#data +<!doctype html><p><math><mo><p><h1> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <math math> +| <math mo> +| <p> +| <h1> + +#data +<!doctype html><p><math><mn><p><h1> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <math math> +| <math mn> +| <p> +| <h1> + +#data +<!doctype html><p><math><ms><p><h1> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <math math> +| <math ms> +| <p> +| <h1> + +#data +<!doctype html><p><math><mtext><p><h1> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <math math> +| <math mtext> +| <p> +| <h1> + +#data +<!doctype html><frameset></noframes> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> + +#data +<!doctype html><html c=d><body></html><html a=b> +#errors +#document +| <!DOCTYPE html> +| <html> +| a="b" +| c="d" +| <head> +| <body> + +#data +<!doctype html><html c=d><frameset></frameset></html><html a=b> +#errors +#document +| <!DOCTYPE html> +| <html> +| a="b" +| c="d" +| <head> +| <frameset> + +#data +<!doctype html><html><frameset></frameset></html><!--foo--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <!-- foo --> + +#data +<!doctype html><html><frameset></frameset></html> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| " " + +#data +<!doctype html><html><frameset></frameset></html>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> + +#data +<!doctype html><html><frameset></frameset></html><p> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> + +#data +<!doctype html><html><frameset></frameset></html></p> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> + +#data +<html><frameset></frameset></html><!doctype html> +#errors +#document +| <html> +| <head> +| <frameset> + +#data +<!doctype html><body><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> + +#data +<!doctype html><p><frameset><frame> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <frame> + +#data +<!doctype html><p>a<frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| "a" + +#data +<!doctype html><p> <frameset><frame> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <frame> + +#data +<!doctype html><pre><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <pre> + +#data +<!doctype html><listing><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <listing> + +#data +<!doctype html><li><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <li> + +#data +<!doctype html><dd><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <dd> + +#data +<!doctype html><dt><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <dt> + +#data +<!doctype html><button><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <button> + +#data +<!doctype html><applet><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <applet> + +#data +<!doctype html><marquee><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <marquee> + +#data +<!doctype html><object><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <object> + +#data +<!doctype html><table><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> + +#data +<!doctype html><area><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <area> + +#data +<!doctype html><basefont><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <basefont> +| <frameset> + +#data +<!doctype html><bgsound><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <bgsound> +| <frameset> + +#data +<!doctype html><br><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <br> + +#data +<!doctype html><embed><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <embed> + +#data +<!doctype html><img><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <img> + +#data +<!doctype html><input><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <input> + +#data +<!doctype html><keygen><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <keygen> + +#data +<!doctype html><wbr><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <wbr> + +#data +<!doctype html><hr><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <hr> + +#data +<!doctype html><textarea></textarea><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <textarea> + +#data +<!doctype html><xmp></xmp><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <xmp> + +#data +<!doctype html><iframe></iframe><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <iframe> + +#data +<!doctype html><select></select><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> + +#data +<!doctype html><svg></svg><frameset><frame> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <frame> + +#data +<!doctype html><math></math><frameset><frame> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <frame> + +#data +<!doctype html><svg><foreignObject><div> <frameset><frame> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <frame> + +#data +<!doctype html><svg>a</svg><frameset><frame> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <svg svg> +| "a" + +#data +<!doctype html><svg> </svg><frameset><frame> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <frame> + +#data +<html>aaa<frameset></frameset> +#errors +#document +| <html> +| <head> +| <body> +| "aaa" + +#data +<html> a <frameset></frameset> +#errors +#document +| <html> +| <head> +| <body> +| "a " + +#data +<!doctype html><div><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> + +#data +<!doctype html><div><body><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <div> + +#data +<!doctype html><p><math></p>a +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <math math> +| "a" + +#data +<!doctype html><p><math><mn><span></p>a +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <math math> +| <math mn> +| <span> +| <p> +| "a" + +#data +<!doctype html><math></html> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <math math> + +#data +<!doctype html><meta charset="ascii"> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <meta> +| charset="ascii" +| <body> + +#data +<!doctype html><meta http-equiv="content-type" content="text/html;charset=ascii"> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <meta> +| content="text/html;charset=ascii" +| http-equiv="content-type" +| <body> + +#data +<!doctype html><head><!--aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa--><meta charset="utf8"> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <!-- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --> +| <meta> +| charset="utf8" +| <body> + +#data +<!doctype html><html a=b><head></head><html c=d> +#errors +#document +| <!DOCTYPE html> +| <html> +| a="b" +| c="d" +| <head> +| <body> + +#data +<!doctype html><image/> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <img> + +#data +<!doctype html>a<i>b<table>c<b>d</i>e</b>f +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "a" +| <i> +| "bc" +| <b> +| "de" +| "f" +| <table> + +#data +<!doctype html><table><i>a<b>b<div>c<a>d</i>e</b>f +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <i> +| "a" +| <b> +| "b" +| <b> +| <div> +| <b> +| <i> +| "c" +| <a> +| "d" +| <a> +| "e" +| <a> +| "f" +| <table> + +#data +<!doctype html><i>a<b>b<div>c<a>d</i>e</b>f +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <i> +| "a" +| <b> +| "b" +| <b> +| <div> +| <b> +| <i> +| "c" +| <a> +| "d" +| <a> +| "e" +| <a> +| "f" + +#data +<!doctype html><table><i>a<b>b<div>c</i> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <i> +| "a" +| <b> +| "b" +| <b> +| <div> +| <i> +| "c" +| <table> + +#data +<!doctype html><table><i>a<b>b<div>c<a>d</i>e</b>f +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <i> +| "a" +| <b> +| "b" +| <b> +| <div> +| <b> +| <i> +| "c" +| <a> +| "d" +| <a> +| "e" +| <a> +| "f" +| <table> + +#data +<!doctype html><table><i>a<div>b<tr>c<b>d</i>e +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <i> +| "a" +| <div> +| "b" +| <i> +| "c" +| <b> +| "d" +| <b> +| "e" +| <table> +| <tbody> +| <tr> + +#data +<!doctype html><table><td><table><i>a<div>b<b>c</i>d +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <td> +| <i> +| "a" +| <div> +| <i> +| "b" +| <b> +| "c" +| <b> +| "d" +| <table> + +#data +<!doctype html><body><bgsound> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <bgsound> + +#data +<!doctype html><body><basefont> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <basefont> + +#data +<!doctype html><a><b></a><basefont> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <a> +| <b> +| <basefont> + +#data +<!doctype html><a><b></a><bgsound> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <a> +| <b> +| <bgsound> + +#data +<!doctype html><figcaption><article></figcaption>a +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <figcaption> +| <article> +| "a" + +#data +<!doctype html><summary><article></summary>a +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <summary> +| <article> +| "a" + +#data +<!doctype html><p><a><plaintext>b +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <a> +| <plaintext> +| <a> +| "b" + +#data +<!DOCTYPE html><div>a<a></div>b<p>c</p>d +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <div> +| "a" +| <a> +| <a> +| "b" +| <p> +| "c" +| "d" diff --git a/src/golang.org/x/net/html/testdata/webkit/tests2.dat b/src/golang.org/x/net/html/testdata/webkit/tests2.dat new file mode 100644 index 0000000000..60d8592216 --- /dev/null +++ b/src/golang.org/x/net/html/testdata/webkit/tests2.dat @@ -0,0 +1,763 @@ +#data +<!DOCTYPE html>Test +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "Test" + +#data +<textarea>test</div>test +#errors +Line: 1 Col: 10 Unexpected start tag (textarea). Expected DOCTYPE. +Line: 1 Col: 24 Expected closing tag. Unexpected end of file. +#document +| <html> +| <head> +| <body> +| <textarea> +| "test</div>test" + +#data +<table><td> +#errors +Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE. +Line: 1 Col: 11 Unexpected table cell start tag (td) in the table body phase. +Line: 1 Col: 11 Expected closing tag. Unexpected end of file. +#document +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <td> + +#data +<table><td>test</tbody></table> +#errors +Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE. +Line: 1 Col: 11 Unexpected table cell start tag (td) in the table body phase. +#document +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <td> +| "test" + +#data +<frame>test +#errors +Line: 1 Col: 7 Unexpected start tag (frame). Expected DOCTYPE. +Line: 1 Col: 7 Unexpected start tag frame. Ignored. +#document +| <html> +| <head> +| <body> +| "test" + +#data +<!DOCTYPE html><frameset>test +#errors +Line: 1 Col: 29 Unepxected characters in the frameset phase. Characters ignored. +Line: 1 Col: 29 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> + +#data +<!DOCTYPE html><frameset><!DOCTYPE html> +#errors +Line: 1 Col: 40 Unexpected DOCTYPE. Ignored. +Line: 1 Col: 40 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> + +#data +<!DOCTYPE html><font><p><b>test</font> +#errors +Line: 1 Col: 38 End tag (font) violates step 1, paragraph 3 of the adoption agency algorithm. +Line: 1 Col: 38 End tag (font) violates step 1, paragraph 3 of the adoption agency algorithm. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <font> +| <p> +| <font> +| <b> +| "test" + +#data +<!DOCTYPE html><dt><div><dd> +#errors +Line: 1 Col: 28 Missing end tag (div, dt). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <dt> +| <div> +| <dd> + +#data +<script></x +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 11 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</x" +| <body> + +#data +<table><plaintext><td> +#errors +Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE. +Line: 1 Col: 18 Unexpected start tag (plaintext) in table context caused voodoo mode. +Line: 1 Col: 22 Unexpected end of file. Expected table content. +#document +| <html> +| <head> +| <body> +| <plaintext> +| "<td>" +| <table> + +#data +<plaintext></plaintext> +#errors +Line: 1 Col: 11 Unexpected start tag (plaintext). Expected DOCTYPE. +Line: 1 Col: 23 Expected closing tag. Unexpected end of file. +#document +| <html> +| <head> +| <body> +| <plaintext> +| "</plaintext>" + +#data +<!DOCTYPE html><table><tr>TEST +#errors +Line: 1 Col: 30 Unexpected non-space characters in table context caused voodoo mode. +Line: 1 Col: 30 Unexpected end of file. Expected table content. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "TEST" +| <table> +| <tbody> +| <tr> + +#data +<!DOCTYPE html><body t1=1><body t2=2><body t3=3 t4=4> +#errors +Line: 1 Col: 37 Unexpected start tag (body). +Line: 1 Col: 53 Unexpected start tag (body). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| t1="1" +| t2="2" +| t3="3" +| t4="4" + +#data +</b test +#errors +Line: 1 Col: 8 Unexpected end of file in attribute name. +Line: 1 Col: 8 End tag contains unexpected attributes. +Line: 1 Col: 8 Unexpected end tag (b). Expected DOCTYPE. +Line: 1 Col: 8 Unexpected end tag (b) after the (implied) root element. +#document +| <html> +| <head> +| <body> + +#data +<!DOCTYPE html></b test<b &=&>X +#errors +Line: 1 Col: 32 Named entity didn't end with ';'. +Line: 1 Col: 33 End tag contains unexpected attributes. +Line: 1 Col: 33 Unexpected end tag (b) after the (implied) root element. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "X" + +#data +<!doctypehtml><scrIPt type=text/x-foobar;baz>X</SCRipt +#errors +Line: 1 Col: 9 No space after literal string 'DOCTYPE'. +Line: 1 Col: 54 Unexpected end of file in the tag name. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| type="text/x-foobar;baz" +| "X</SCRipt" +| <body> + +#data +& +#errors +Line: 1 Col: 1 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "&" + +#data +&# +#errors +Line: 1 Col: 1 Numeric entity expected. Got end of file instead. +Line: 1 Col: 1 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "&#" + +#data +&#X +#errors +Line: 1 Col: 3 Numeric entity expected but none found. +Line: 1 Col: 3 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "&#X" + +#data +&#x +#errors +Line: 1 Col: 3 Numeric entity expected but none found. +Line: 1 Col: 3 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "&#x" + +#data +- +#errors +Line: 1 Col: 4 Numeric entity didn't end with ';'. +Line: 1 Col: 4 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "-" + +#data +&x-test +#errors +Line: 1 Col: 1 Named entity expected. Got none. +Line: 1 Col: 1 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "&x-test" + +#data +<!doctypehtml><p><li> +#errors +Line: 1 Col: 9 No space after literal string 'DOCTYPE'. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <li> + +#data +<!doctypehtml><p><dt> +#errors +Line: 1 Col: 9 No space after literal string 'DOCTYPE'. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <dt> + +#data +<!doctypehtml><p><dd> +#errors +Line: 1 Col: 9 No space after literal string 'DOCTYPE'. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <dd> + +#data +<!doctypehtml><p><form> +#errors +Line: 1 Col: 9 No space after literal string 'DOCTYPE'. +Line: 1 Col: 23 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <form> + +#data +<!DOCTYPE html><p></P>X +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| "X" + +#data +& +#errors +Line: 1 Col: 4 Named entity didn't end with ';'. +Line: 1 Col: 4 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "&" + +#data +&AMp; +#errors +Line: 1 Col: 1 Named entity expected. Got none. +Line: 1 Col: 1 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "&AMp;" + +#data +<!DOCTYPE html><html><head></head><body><thisISasillyTESTelementNameToMakeSureCrazyTagNamesArePARSEDcorrectLY> +#errors +Line: 1 Col: 110 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <thisisasillytestelementnametomakesurecrazytagnamesareparsedcorrectly> + +#data +<!DOCTYPE html>X</body>X +#errors +Line: 1 Col: 24 Unexpected non-space characters in the after body phase. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "XX" + +#data +<!DOCTYPE html><!-- X +#errors +Line: 1 Col: 21 Unexpected end of file in comment. +#document +| <!DOCTYPE html> +| <!-- X --> +| <html> +| <head> +| <body> + +#data +<!DOCTYPE html><table><caption>test TEST</caption><td>test +#errors +Line: 1 Col: 54 Unexpected table cell start tag (td) in the table body phase. +Line: 1 Col: 58 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <caption> +| "test TEST" +| <tbody> +| <tr> +| <td> +| "test" + +#data +<!DOCTYPE html><select><option><optgroup> +#errors +Line: 1 Col: 41 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <option> +| <optgroup> + +#data +<!DOCTYPE html><select><optgroup><option></optgroup><option><select><option> +#errors +Line: 1 Col: 68 Unexpected select start tag in the select phase treated as select end tag. +Line: 1 Col: 76 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <optgroup> +| <option> +| <option> +| <option> + +#data +<!DOCTYPE html><select><optgroup><option><optgroup> +#errors +Line: 1 Col: 51 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <optgroup> +| <option> +| <optgroup> + +#data +<!DOCTYPE html><datalist><option>foo</datalist>bar +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <datalist> +| <option> +| "foo" +| "bar" + +#data +<!DOCTYPE html><font><input><input></font> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <font> +| <input> +| <input> + +#data +<!DOCTYPE html><!-- XXX - XXX --> +#errors +#document +| <!DOCTYPE html> +| <!-- XXX - XXX --> +| <html> +| <head> +| <body> + +#data +<!DOCTYPE html><!-- XXX - XXX +#errors +Line: 1 Col: 29 Unexpected end of file in comment (-) +#document +| <!DOCTYPE html> +| <!-- XXX - XXX --> +| <html> +| <head> +| <body> + +#data +<!DOCTYPE html><!-- XXX - XXX - XXX --> +#errors +#document +| <!DOCTYPE html> +| <!-- XXX - XXX - XXX --> +| <html> +| <head> +| <body> + +#data +<isindex test=x name=x> +#errors +Line: 1 Col: 23 Unexpected start tag (isindex). Expected DOCTYPE. +Line: 1 Col: 23 Unexpected start tag isindex. Don't use it! +#document +| <html> +| <head> +| <body> +| <form> +| <hr> +| <label> +| "This is a searchable index. Enter search keywords: " +| <input> +| name="isindex" +| test="x" +| <hr> + +#data +test +test +#errors +Line: 2 Col: 4 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "test +test" + +#data +<!DOCTYPE html><body><title>test</body> +#errors +#document +| +| +| +| +| +| "test</body>" + +#data +<!DOCTYPE html><body><title>X +#errors +#document +| +| +| +| +| +| "X" +| <meta> +| name="z" +| <link> +| rel="foo" +| <style> +| " +x { content:"</style" } " + +#data +<!DOCTYPE html><select><optgroup></optgroup></select> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <optgroup> + +#data + + +#errors +Line: 2 Col: 1 Unexpected End of file. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> + +#data +<!DOCTYPE html> <html> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> + +#data +<!DOCTYPE html><script> +</script> <title>x +#errors +#document +| +| +| +| +#errors +Line: 1 Col: 6 Unexpected start tag (head). Expected DOCTYPE. +Line: 1 Col: 21 Unexpected start tag (script) that can be in head. Moved. +#document +| +| +| +#errors +Line: 1 Col: 6 Unexpected start tag (head). Expected DOCTYPE. +Line: 1 Col: 28 Unexpected start tag (style) that can be in head. Moved. +#document +| +| +| +#errors +Line: 1 Col: 6 Unexpected start tag (head). Expected DOCTYPE. +#document +| +| +| +| +| "x" +| x +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +Line: 1 Col: 22 Unexpected end of file. Expected end tag (style). +#document +| +| +| --> x +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +#document +| +| +| x +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +#document +| +| +| x +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +#document +| +| +| x +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +#document +| +| +|

+#errors +#document +| +| +| +| +| +| ddd +#errors +#document +| +| +| +#errors +#document +| +| +| +| +|
  • +| +| ", + " + +` diff --git a/src/golang.org/x/tools/cmd/cover/testdata/main.go b/src/golang.org/x/tools/cmd/cover/testdata/main.go new file mode 100644 index 0000000000..6ed39c4f23 --- /dev/null +++ b/src/golang.org/x/tools/cmd/cover/testdata/main.go @@ -0,0 +1,112 @@ +// 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. + +// Test runner for coverage test. This file is not coverage-annotated; test.go is. +// It knows the coverage counter is called "coverTest". + +package main + +import ( + "fmt" + "os" +) + +func main() { + testAll() + verify() +} + +type block struct { + count uint32 + line uint32 +} + +var counters = make(map[block]bool) + +// check records the location and expected value for a counter. +func check(line, count uint32) { + b := block{ + count, + line, + } + counters[b] = true +} + +// checkVal is a version of check that returns its extra argument, +// so it can be used in conditionals. +func checkVal(line, count uint32, val int) int { + b := block{ + count, + line, + } + counters[b] = true + return val +} + +var PASS = true + +// verify checks the expected counts against the actual. It runs after the test has completed. +func verify() { + for b := range counters { + got, index := count(b.line) + if b.count == anything && got != 0 { + got = anything + } + if got != b.count { + fmt.Fprintf(os.Stderr, "test_go:%d expected count %d got %d [counter %d]\n", b.line, b.count, got, index) + PASS = false + } + } + verifyPanic() + if !PASS { + fmt.Fprintf(os.Stderr, "FAIL\n") + os.Exit(2) + } +} + +// verifyPanic is a special check for the known counter that should be +// after the panic call in testPanic. +func verifyPanic() { + if coverTest.Count[panicIndex-1] != 1 { + // Sanity check for test before panic. + fmt.Fprintf(os.Stderr, "bad before panic") + PASS = false + } + if coverTest.Count[panicIndex] != 0 { + fmt.Fprintf(os.Stderr, "bad at panic: %d should be 0\n", coverTest.Count[panicIndex]) + PASS = false + } + if coverTest.Count[panicIndex+1] != 1 { + fmt.Fprintf(os.Stderr, "bad after panic") + PASS = false + } +} + +// count returns the count and index for the counter at the specified line. +func count(line uint32) (uint32, int) { + // Linear search is fine. Choose perfect fit over approximate. + // We can have a closing brace for a range on the same line as a condition for an "else if" + // and we don't want that brace to steal the count for the condition on the "if". + // Therefore we test for a perfect (lo==line && hi==line) match, but if we can't + // find that we take the first imperfect match. + index := -1 + indexLo := uint32(1e9) + for i := range coverTest.Count { + lo, hi := coverTest.Pos[3*i], coverTest.Pos[3*i+1] + if lo == line && line == hi { + return coverTest.Count[i], i + } + // Choose the earliest match (the counters are in unpredictable order). + if lo <= line && line <= hi && indexLo > lo { + index = i + indexLo = lo + } + } + if index == -1 { + fmt.Fprintln(os.Stderr, "cover_test: no counter for line", line) + PASS = false + return 0, 0 + } + return coverTest.Count[index], index +} diff --git a/src/golang.org/x/tools/cmd/cover/testdata/test.go b/src/golang.org/x/tools/cmd/cover/testdata/test.go new file mode 100644 index 0000000000..9013950a2b --- /dev/null +++ b/src/golang.org/x/tools/cmd/cover/testdata/test.go @@ -0,0 +1,218 @@ +// 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. + +// This program is processed by the cover command, and then testAll is called. +// The test driver in main.go can then compare the coverage statistics with expectation. + +// The word LINE is replaced by the line number in this file. When the file is executed, +// the coverage processing has changed the line numbers, so we can't use runtime.Caller. + +package main + +const anything = 1e9 // Just some unlikely value that means "we got here, don't care how often" + +func testAll() { + testSimple() + testBlockRun() + testIf() + testFor() + testRange() + testSwitch() + testTypeSwitch() + testSelect1() + testSelect2() + testPanic() + testEmptySwitches() +} + +// The indexes of the counters in testPanic are known to main.go +const panicIndex = 3 + +// This test appears first because the index of its counters is known to main.go +func testPanic() { + defer func() { + recover() + }() + check(LINE, 1) + panic("should not get next line") + check(LINE, 0) // this is GoCover.Count[panicIndex] + // The next counter is in testSimple and it will be non-zero. + // If the panic above does not trigger a counter, the test will fail + // because GoCover.Count[panicIndex] will be the one in testSimple. +} + +func testSimple() { + check(LINE, 1) +} + +func testIf() { + if true { + check(LINE, 1) + } else { + check(LINE, 0) + } + if false { + check(LINE, 0) + } else { + check(LINE, 1) + } + for i := 0; i < 3; i++ { + if checkVal(LINE, 3, i) <= 2 { + check(LINE, 3) + } + if checkVal(LINE, 3, i) <= 1 { + check(LINE, 2) + } + if checkVal(LINE, 3, i) <= 0 { + check(LINE, 1) + } + } + for i := 0; i < 3; i++ { + if checkVal(LINE, 3, i) <= 1 { + check(LINE, 2) + } else { + check(LINE, 1) + } + } + for i := 0; i < 3; i++ { + if checkVal(LINE, 3, i) <= 0 { + check(LINE, 1) + } else if checkVal(LINE, 2, i) <= 1 { + check(LINE, 1) + } else if checkVal(LINE, 1, i) <= 2 { + check(LINE, 1) + } else if checkVal(LINE, 0, i) <= 3 { + check(LINE, 0) + } + } + if func(a, b int) bool { return a < b }(3, 4) { + check(LINE, 1) + } +} + +func testFor() { + for i := 0; i < 10; func() { i++; check(LINE, 10) }() { + check(LINE, 10) + } +} + +func testRange() { + for _, f := range []func(){ + func() { check(LINE, 1) }, + } { + f() + check(LINE, 1) + } +} + +func testBlockRun() { + check(LINE, 1) + { + check(LINE, 1) + } + { + check(LINE, 1) + } + check(LINE, 1) + { + check(LINE, 1) + } + { + check(LINE, 1) + } + check(LINE, 1) +} + +func testSwitch() { + for i := 0; i < 5; func() { i++; check(LINE, 5) }() { + switch i { + case 0: + check(LINE, 1) + case 1: + check(LINE, 1) + case 2: + check(LINE, 1) + default: + check(LINE, 2) + } + } +} + +func testTypeSwitch() { + var x = []interface{}{1, 2.0, "hi"} + for _, v := range x { + switch func() { check(LINE, 3) }(); v.(type) { + case int: + check(LINE, 1) + case float64: + check(LINE, 1) + case string: + check(LINE, 1) + case complex128: + check(LINE, 0) + default: + check(LINE, 0) + } + } +} + +func testSelect1() { + c := make(chan int) + go func() { + for i := 0; i < 1000; i++ { + c <- i + } + }() + for { + select { + case <-c: + check(LINE, anything) + case <-c: + check(LINE, anything) + default: + check(LINE, 1) + return + } + } +} + +func testSelect2() { + c1 := make(chan int, 1000) + c2 := make(chan int, 1000) + for i := 0; i < 1000; i++ { + c1 <- i + c2 <- i + } + for { + select { + case <-c1: + check(LINE, 1000) + case <-c2: + check(LINE, 1000) + default: + check(LINE, 1) + return + } + } +} + +// Empty control statements created syntax errors. This function +// is here just to be sure that those are handled correctly now. +func testEmptySwitches() { + check(LINE, 1) + switch 3 { + } + check(LINE, 1) + switch i := (interface{})(3).(int); i { + } + check(LINE, 1) + c := make(chan int) + go func() { + check(LINE, 1) + c <- 1 + select {} + }() + <-c + check(LINE, 1) +} diff --git a/src/golang.org/x/tools/cmd/digraph/digraph.go b/src/golang.org/x/tools/cmd/digraph/digraph.go new file mode 100644 index 0000000000..3ad2950015 --- /dev/null +++ b/src/golang.org/x/tools/cmd/digraph/digraph.go @@ -0,0 +1,540 @@ +// The digraph command performs queries over unlabelled directed graphs +// represented in text form. It is intended to integrate nicely with +// typical UNIX command pipelines. +// +// Since directed graphs (import graphs, reference graphs, call graphs, +// etc) often arise during software tool development and debugging, this +// command is included in the go.tools repository. +// +// TODO(adonovan): +// - support input files other than stdin +// - suport alternative formats (AT&T GraphViz, CSV, etc), +// a comment syntax, etc. +// - allow queries to nest, like Blaze query language. +// +package main // import "golang.org/x/tools/cmd/digraph" + +import ( + "bufio" + "bytes" + "errors" + "flag" + "fmt" + "io" + "os" + "sort" + "strconv" + "unicode" + "unicode/utf8" +) + +const Usage = `digraph: queries over directed graphs in text form. + +Graph format: + + Each line contains zero or more words. Words are separated by + unquoted whitespace; words may contain Go-style double-quoted portions, + allowing spaces and other characters to be expressed. + + Each field declares a node, and if there are more than one, + an edge from the first to each subsequent one. + The graph is provided on the standard input. + + For instance, the following (acyclic) graph specifies a partial order + among the subtasks of getting dressed: + + % cat clothes.txt + socks shoes + "boxer shorts" pants + pants belt shoes + shirt tie sweater + sweater jacket + hat + + The line "shirt tie sweater" indicates the two edges shirt -> tie and + shirt -> sweater, not shirt -> tie -> sweater. + +Supported queries: + + nodes + the set of all nodes + degree + the in-degree and out-degree of each node. + preds
    `)
    +	template.HTMLEscape(w, data[0:mark])
    +	io.WriteString(w, "")
    +	template.HTMLEscape(w, data[mark:lo])
    +	if lo < hi {
    +		io.WriteString(w, "
    ") + template.HTMLEscape(w, data[lo:hi]) + io.WriteString(w, "
    ") + } + template.HTMLEscape(w, data[hi:]) + io.WriteString(w, "
    ") +} + +// addrToByte evaluates the given address starting at offset start in data. +// It returns the lo and hi byte offset of the matched region within data. +// See http://plan9.bell-labs.com/sys/doc/sam/sam.html Table II +// for details on the syntax. +func addrToByteRange(addr string, start int, data []byte) (lo, hi int, err error) { + var ( + dir byte + prevc byte + charOffset bool + ) + lo = start + hi = start + for addr != "" && err == nil { + c := addr[0] + switch c { + default: + err = errors.New("invalid address syntax near " + string(c)) + case ',': + if len(addr) == 1 { + hi = len(data) + } else { + _, hi, err = addrToByteRange(addr[1:], hi, data) + } + return + + case '+', '-': + if prevc == '+' || prevc == '-' { + lo, hi, err = addrNumber(data, lo, hi, prevc, 1, charOffset) + } + dir = c + + case '$': + lo = len(data) + hi = len(data) + if len(addr) > 1 { + dir = '+' + } + + case '#': + charOffset = true + + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + var i int + for i = 1; i < len(addr); i++ { + if addr[i] < '0' || addr[i] > '9' { + break + } + } + var n int + n, err = strconv.Atoi(addr[0:i]) + if err != nil { + break + } + lo, hi, err = addrNumber(data, lo, hi, dir, n, charOffset) + dir = 0 + charOffset = false + prevc = c + addr = addr[i:] + continue + + case '/': + var i, j int + Regexp: + for i = 1; i < len(addr); i++ { + switch addr[i] { + case '\\': + i++ + case '/': + j = i + 1 + break Regexp + } + } + if j == 0 { + j = i + } + pattern := addr[1:i] + lo, hi, err = addrRegexp(data, lo, hi, dir, pattern) + prevc = c + addr = addr[j:] + continue + } + prevc = c + addr = addr[1:] + } + + if err == nil && dir != 0 { + lo, hi, err = addrNumber(data, lo, hi, dir, 1, charOffset) + } + if err != nil { + return 0, 0, err + } + return lo, hi, nil +} + +// addrNumber applies the given dir, n, and charOffset to the address lo, hi. +// dir is '+' or '-', n is the count, and charOffset is true if the syntax +// used was #n. Applying +n (or +#n) means to advance n lines +// (or characters) after hi. Applying -n (or -#n) means to back up n lines +// (or characters) before lo. +// The return value is the new lo, hi. +func addrNumber(data []byte, lo, hi int, dir byte, n int, charOffset bool) (int, int, error) { + switch dir { + case 0: + lo = 0 + hi = 0 + fallthrough + + case '+': + if charOffset { + pos := hi + for ; n > 0 && pos < len(data); n-- { + _, size := utf8.DecodeRune(data[pos:]) + pos += size + } + if n == 0 { + return pos, pos, nil + } + break + } + // find next beginning of line + if hi > 0 { + for hi < len(data) && data[hi-1] != '\n' { + hi++ + } + } + lo = hi + if n == 0 { + return lo, hi, nil + } + for ; hi < len(data); hi++ { + if data[hi] != '\n' { + continue + } + switch n--; n { + case 1: + lo = hi + 1 + case 0: + return lo, hi + 1, nil + } + } + + case '-': + if charOffset { + // Scan backward for bytes that are not UTF-8 continuation bytes. + pos := lo + for ; pos > 0 && n > 0; pos-- { + if data[pos]&0xc0 != 0x80 { + n-- + } + } + if n == 0 { + return pos, pos, nil + } + break + } + // find earlier beginning of line + for lo > 0 && data[lo-1] != '\n' { + lo-- + } + hi = lo + if n == 0 { + return lo, hi, nil + } + for ; lo >= 0; lo-- { + if lo > 0 && data[lo-1] != '\n' { + continue + } + switch n--; n { + case 1: + hi = lo + case 0: + return lo, hi, nil + } + } + } + + return 0, 0, errors.New("address out of range") +} + +// addrRegexp searches for pattern in the given direction starting at lo, hi. +// The direction dir is '+' (search forward from hi) or '-' (search backward from lo). +// Backward searches are unimplemented. +func addrRegexp(data []byte, lo, hi int, dir byte, pattern string) (int, int, error) { + re, err := regexp.Compile(pattern) + if err != nil { + return 0, 0, err + } + if dir == '-' { + // Could implement reverse search using binary search + // through file, but that seems like overkill. + return 0, 0, errors.New("reverse search not implemented") + } + m := re.FindIndex(data[hi:]) + if len(m) > 0 { + m[0] += hi + m[1] += hi + } else if hi > 0 { + // No match. Wrap to beginning of data. + m = re.FindIndex(data) + } + if len(m) == 0 { + return 0, 0, errors.New("no match for " + pattern) + } + return m[0], m[1], nil +} + +// lineToByte returns the byte index of the first byte of line n. +// Line numbers begin at 1. +func lineToByte(data []byte, n int) int { + if n <= 1 { + return 0 + } + n-- + for i, c := range data { + if c == '\n' { + if n--; n == 0 { + return i + 1 + } + } + } + return len(data) +} + +// byteToLine returns the number of the line containing the byte at index i. +func byteToLine(data []byte, i int) int { + l := 1 + for j, c := range data { + if j == i { + return l + } + if c == '\n' { + l++ + } + } + return l +} diff --git a/src/golang.org/x/tools/cmd/godoc/dl.go b/src/golang.org/x/tools/cmd/godoc/dl.go new file mode 100644 index 0000000000..bd73831624 --- /dev/null +++ b/src/golang.org/x/tools/cmd/godoc/dl.go @@ -0,0 +1,14 @@ +// Copyright 2014 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. + +package main + +import "net/http" + +// Register a redirect handler for /dl/ to the golang.org download page. +// This file will not be included when deploying godoc to golang.org. + +func init() { + http.Handle("/dl/", http.RedirectHandler("http://golang.org/dl/", http.StatusFound)) +} diff --git a/src/golang.org/x/tools/cmd/godoc/doc.go b/src/golang.org/x/tools/cmd/godoc/doc.go new file mode 100644 index 0000000000..17cf23e5c8 --- /dev/null +++ b/src/golang.org/x/tools/cmd/godoc/doc.go @@ -0,0 +1,146 @@ +// Copyright 2009 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. + +/* + +Godoc extracts and generates documentation for Go programs. + +It has two modes. + +Without the -http flag, it runs in command-line mode and prints plain text +documentation to standard output and exits. If both a library package and +a command with the same name exists, using the prefix cmd/ will force +documentation on the command rather than the library package. If the -src +flag is specified, godoc prints the exported interface of a package in Go +source form, or the implementation of a specific exported language entity: + + godoc fmt # documentation for package fmt + godoc fmt Printf # documentation for fmt.Printf + godoc cmd/go # force documentation for the go command + godoc -src fmt # fmt package interface in Go source form + godoc -src fmt Printf # implementation of fmt.Printf + +In command-line mode, the -q flag enables search queries against a godoc running +as a webserver. If no explicit server address is specified with the -server flag, +godoc first tries localhost:6060 and then http://golang.org. + + godoc -q Reader + godoc -q math.Sin + godoc -server=:6060 -q sin + +With the -http flag, it runs as a web server and presents the documentation as a +web page. + + godoc -http=:6060 + +Usage: + godoc [flag] package [name ...] + +The flags are: + -v + verbose mode + -q + arguments are considered search queries: a legal query is a + single identifier (such as ToLower) or a qualified identifier + (such as math.Sin) + -src + print (exported) source in command-line mode + -tabwidth=4 + width of tabs in units of spaces + -timestamps=true + show timestamps with directory listings + -index + enable identifier and full text search index + (no search box is shown if -index is not set) + -index_files="" + glob pattern specifying index files; if not empty, + the index is read from these files in sorted order + -index_throttle=0.75 + index throttle value; a value of 0 means no time is allocated + to the indexer (the indexer will never finish), a value of 1.0 + means that index creation is running at full throttle (other + goroutines may get no time while the index is built) + -links=true: + link identifiers to their declarations + -write_index=false + write index to a file; the file name must be specified with + -index_files + -maxresults=10000 + maximum number of full text search results shown + (no full text index is built if maxresults <= 0) + -notes="BUG" + regular expression matching note markers to show + (e.g., "BUG|TODO", ".*") + -html + print HTML in command-line mode + -goroot=$GOROOT + Go root directory + -http=addr + HTTP service address (e.g., '127.0.0.1:6060' or just ':6060') + -server=addr + webserver address for command line searches + -analysis=type,pointer + comma-separated list of analyses to perform + "type": display identifier resolution, type info, method sets, + 'implements', and static callees + "pointer" display channel peers, callers and dynamic callees + (significantly slower) + See http://golang.org/lib/godoc/analysis/help.html for details. + -templates="" + directory containing alternate template files; if set, + the directory may provide alternative template files + for the files in $GOROOT/lib/godoc + -url=path + print to standard output the data that would be served by + an HTTP request for path + -zip="" + zip file providing the file system to serve; disabled if empty + +By default, godoc looks at the packages it finds via $GOROOT and $GOPATH (if set). +This behavior can be altered by providing an alternative $GOROOT with the -goroot +flag. + +When godoc runs as a web server and -index is set, a search index is maintained. +The index is created at startup. + +The index contains both identifier and full text search information (searchable +via regular expressions). The maximum number of full text search results shown +can be set with the -maxresults flag; if set to 0, no full text results are +shown, and only an identifier index but no full text search index is created. + +The presentation mode of web pages served by godoc can be controlled with the +"m" URL parameter; it accepts a comma-separated list of flag names as value: + + all show documentation for all declarations, not just the exported ones + methods show all embedded methods, not just those of unexported anonymous fields + src show the original source code rather then the extracted documentation + text present the page in textual (command-line) form rather than HTML + flat present flat (not indented) directory listings using full paths + +For instance, http://golang.org/pkg/math/big/?m=all,text shows the documentation +for all (not just the exported) declarations of package big, in textual form (as +it would appear when using godoc from the command line: "godoc -src math/big .*"). + +By default, godoc serves files from the file system of the underlying OS. +Instead, a .zip file may be provided via the -zip flag, which contains +the file system to serve. The file paths stored in the .zip file must use +slash ('/') as path separator; and they must be unrooted. $GOROOT (or -goroot) +must be set to the .zip file directory path containing the Go root directory. +For instance, for a .zip file created by the command: + + zip go.zip $HOME/go + +one may run godoc as follows: + + godoc -http=:6060 -zip=go.zip -goroot=$HOME/go + +Godoc documentation is converted to HTML or to text using the go/doc package; +see http://golang.org/pkg/go/doc/#ToHTML for the exact rules. +Godoc also shows example code that is runnable by the testing package; +see http://golang.org/pkg/testing/#hdr-Examples for the conventions. +See "Godoc: documenting Go code" for how to write good comments for godoc: +http://golang.org/doc/articles/godoc_documenting_go_code.html + +*/ +package main // import "golang.org/x/tools/cmd/godoc" diff --git a/src/golang.org/x/tools/cmd/godoc/godoc_test.go b/src/golang.org/x/tools/cmd/godoc/godoc_test.go new file mode 100644 index 0000000000..312cfc642b --- /dev/null +++ b/src/golang.org/x/tools/cmd/godoc/godoc_test.go @@ -0,0 +1,386 @@ +// 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. + +package main_test + +import ( + "bufio" + "bytes" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "os" + "os/exec" + "path/filepath" + "regexp" + "runtime" + "strings" + "testing" + "time" +) + +var godocTests = []struct { + args []string + matches []string // regular expressions + dontmatch []string // regular expressions +}{ + { + args: []string{"fmt"}, + matches: []string{ + `import "fmt"`, + `Package fmt implements formatted I/O`, + }, + }, + { + args: []string{"io", "WriteString"}, + matches: []string{ + `func WriteString\(`, + `WriteString writes the contents of the string s to w`, + }, + }, + { + args: []string{"nonexistingpkg"}, + matches: []string{ + `no such file or directory|does not exist|cannot find the file`, + }, + }, + { + args: []string{"fmt", "NonexistentSymbol"}, + matches: []string{ + `No match found\.`, + }, + }, + { + args: []string{"-src", "syscall", "Open"}, + matches: []string{ + `func Open\(`, + }, + dontmatch: []string{ + `No match found\.`, + }, + }, +} + +// buildGodoc builds the godoc executable. +// It returns its path, and a cleanup function. +// +// TODO(adonovan): opt: do this at most once, and do the cleanup +// exactly once. How though? There's no atexit. +func buildGodoc(t *testing.T) (bin string, cleanup func()) { + if runtime.GOARCH == "arm" { + t.Skip("skipping test on arm platforms; too slow") + } + tmp, err := ioutil.TempDir("", "godoc-regtest-") + if err != nil { + t.Fatal(err) + } + defer func() { + if cleanup == nil { // probably, go build failed. + os.RemoveAll(tmp) + } + }() + + bin = filepath.Join(tmp, "godoc") + if runtime.GOOS == "windows" { + bin += ".exe" + } + cmd := exec.Command("go", "build", "-o", bin) + if err := cmd.Run(); err != nil { + t.Fatalf("Building godoc: %v", err) + } + + return bin, func() { os.RemoveAll(tmp) } +} + +// Basic regression test for godoc command-line tool. +func TestCLI(t *testing.T) { + bin, cleanup := buildGodoc(t) + defer cleanup() + for _, test := range godocTests { + cmd := exec.Command(bin, test.args...) + cmd.Args[0] = "godoc" + out, err := cmd.CombinedOutput() + if err != nil { + t.Errorf("Running with args %#v: %v", test.args, err) + continue + } + for _, pat := range test.matches { + re := regexp.MustCompile(pat) + if !re.Match(out) { + t.Errorf("godoc %v =\n%s\nwanted /%v/", strings.Join(test.args, " "), out, pat) + } + } + for _, pat := range test.dontmatch { + re := regexp.MustCompile(pat) + if re.Match(out) { + t.Errorf("godoc %v =\n%s\ndid not want /%v/", strings.Join(test.args, " "), out, pat) + } + } + } +} + +func serverAddress(t *testing.T) string { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + ln, err = net.Listen("tcp6", "[::1]:0") + } + if err != nil { + t.Fatal(err) + } + defer ln.Close() + return ln.Addr().String() +} + +const ( + startTimeout = 5 * time.Minute + pollInterval = 200 * time.Millisecond +) + +var indexingMsg = []byte("Indexing in progress: result may be inaccurate") + +func waitForServer(t *testing.T, address string) { + // "health check" duplicated from x/tools/cmd/tipgodoc/tip.go + deadline := time.Now().Add(startTimeout) + for time.Now().Before(deadline) { + time.Sleep(pollInterval) + res, err := http.Get(fmt.Sprintf("http://%v/search?q=FALLTHROUGH", address)) + if err != nil { + continue + } + rbody, err := ioutil.ReadAll(res.Body) + res.Body.Close() + if err == nil && res.StatusCode == http.StatusOK && + !bytes.Contains(rbody, indexingMsg) { + return + } + } + t.Fatalf("Server %q failed to respond in %v", address, startTimeout) +} + +func killAndWait(cmd *exec.Cmd) { + cmd.Process.Kill() + cmd.Wait() +} + +// Basic integration test for godoc HTTP interface. +func TestWeb(t *testing.T) { + bin, cleanup := buildGodoc(t) + defer cleanup() + addr := serverAddress(t) + cmd := exec.Command(bin, fmt.Sprintf("-http=%s", addr), "-index", "-index_interval=-1s") + cmd.Stdout = os.Stderr + cmd.Stderr = os.Stderr + cmd.Args[0] = "godoc" + if err := cmd.Start(); err != nil { + t.Fatalf("failed to start godoc: %s", err) + } + defer killAndWait(cmd) + waitForServer(t, addr) + tests := []struct { + path string + match []string + dontmatch []string + }{ + { + path: "/", + match: []string{"Go is an open source programming language"}, + }, + { + path: "/pkg/fmt/", + match: []string{"Package fmt implements formatted I/O"}, + }, + { + path: "/src/fmt/", + match: []string{"scan_test.go"}, + }, + { + path: "/src/fmt/print.go", + match: []string{"// Println formats using"}, + }, + { + path: "/pkg", + match: []string{ + "Standard library", + "Package fmt implements formatted I/O", + }, + dontmatch: []string{ + "internal/syscall", + "cmd/gc", + }, + }, + { + path: "/pkg/?m=all", + match: []string{ + "Standard library", + "Package fmt implements formatted I/O", + "internal/syscall", + }, + dontmatch: []string{ + "cmd/gc", + }, + }, + { + path: "/search?q=notwithstanding", + match: []string{ + "/src", + }, + dontmatch: []string{ + "/pkg/bootstrap", + }, + }, + } + for _, test := range tests { + url := fmt.Sprintf("http://%s%s", addr, test.path) + resp, err := http.Get(url) + if err != nil { + t.Errorf("GET %s failed: %s", url, err) + continue + } + body, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + t.Errorf("GET %s: failed to read body: %s (response: %v)", url, err, resp) + } + isErr := false + for _, substr := range test.match { + if !bytes.Contains(body, []byte(substr)) { + t.Errorf("GET %s: wanted substring %q in body", url, substr) + isErr = true + } + } + for _, substr := range test.dontmatch { + if bytes.Contains(body, []byte(substr)) { + t.Errorf("GET %s: didn't want substring %q in body", url, substr) + isErr = true + } + } + if isErr { + t.Errorf("GET %s: got:\n%s", url, body) + } + } +} + +// Basic integration test for godoc -analysis=type (via HTTP interface). +func TestTypeAnalysis(t *testing.T) { + // Write a fake GOROOT/GOPATH. + tmpdir, err := ioutil.TempDir("", "godoc-analysis") + if err != nil { + t.Fatalf("ioutil.TempDir failed: %s", err) + } + defer os.RemoveAll(tmpdir) + for _, f := range []struct{ file, content string }{ + {"goroot/src/lib/lib.go", ` +package lib +type T struct{} +const C = 3 +var V T +func (T) F() int { return C } +`}, + {"gopath/src/app/main.go", ` +package main +import "lib" +func main() { print(lib.V) } +`}, + } { + file := filepath.Join(tmpdir, f.file) + if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil { + t.Fatalf("MkdirAll(%s) failed: %s", filepath.Dir(file), err) + } + if err := ioutil.WriteFile(file, []byte(f.content), 0644); err != nil { + t.Fatal(err) + } + } + + // Start the server. + bin, cleanup := buildGodoc(t) + defer cleanup() + addr := serverAddress(t) + cmd := exec.Command(bin, fmt.Sprintf("-http=%s", addr), "-analysis=type") + cmd.Env = append(cmd.Env, fmt.Sprintf("GOROOT=%s", filepath.Join(tmpdir, "goroot"))) + cmd.Env = append(cmd.Env, fmt.Sprintf("GOPATH=%s", filepath.Join(tmpdir, "gopath"))) + for _, e := range os.Environ() { + if strings.HasPrefix(e, "GOROOT=") || strings.HasPrefix(e, "GOPATH=") { + continue + } + cmd.Env = append(cmd.Env, e) + } + cmd.Stdout = os.Stderr + stderr, err := cmd.StderrPipe() + if err != nil { + t.Fatal(err) + } + cmd.Args[0] = "godoc" + if err := cmd.Start(); err != nil { + t.Fatalf("failed to start godoc: %s", err) + } + defer killAndWait(cmd) + waitForServer(t, addr) + + // Wait for type analysis to complete. + reader := bufio.NewReader(stderr) + for { + s, err := reader.ReadString('\n') + if err != nil { + t.Fatal(err) + } + fmt.Fprint(os.Stderr, s) + if strings.Contains(s, "Type analysis complete.") { + break + } + } + go io.Copy(os.Stderr, reader) + + t0 := time.Now() + + // Make an HTTP request and check for a regular expression match. + // The patterns are very crude checks that basic type information + // has been annotated onto the source view. +tryagain: + for _, test := range []struct{ url, pattern string }{ + {"/src/lib/lib.go", "L2.*package .*Package docs for lib.*/lib"}, + {"/src/lib/lib.go", "L3.*type .*type info for T.*struct"}, + {"/src/lib/lib.go", "L5.*var V .*type T struct"}, + {"/src/lib/lib.go", "L6.*func .*type T struct.*T.*return .*const C untyped int.*C"}, + + {"/src/app/main.go", "L2.*package .*Package docs for app"}, + {"/src/app/main.go", "L3.*import .*Package docs for lib.*lib"}, + {"/src/app/main.go", "L4.*func main.*package lib.*lib.*var lib.V lib.T.*V"}, + } { + url := fmt.Sprintf("http://%s%s", addr, test.url) + resp, err := http.Get(url) + if err != nil { + t.Errorf("GET %s failed: %s", url, err) + continue + } + body, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + t.Errorf("GET %s: failed to read body: %s (response: %v)", url, err, resp) + continue + } + + if !bytes.Contains(body, []byte("Static analysis features")) { + // Type analysis results usually become available within + // ~4ms after godoc startup (for this input on my machine). + if elapsed := time.Since(t0); elapsed > 500*time.Millisecond { + t.Fatalf("type analysis results still unavailable after %s", elapsed) + } + time.Sleep(10 * time.Millisecond) + goto tryagain + } + + match, err := regexp.Match(test.pattern, body) + if err != nil { + t.Errorf("regexp.Match(%q) failed: %s", test.pattern, err) + continue + } + if !match { + // This is a really ugly failure message. + t.Errorf("GET %s: body doesn't match %q, got:\n%s", + url, test.pattern, string(body)) + } + } +} diff --git a/src/golang.org/x/tools/cmd/godoc/handlers.go b/src/golang.org/x/tools/cmd/godoc/handlers.go new file mode 100644 index 0000000000..1f79d80d78 --- /dev/null +++ b/src/golang.org/x/tools/cmd/godoc/handlers.go @@ -0,0 +1,83 @@ +// Copyright 2010 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. + +// The /doc/codewalk/ tree is synthesized from codewalk descriptions, +// files named $GOROOT/doc/codewalk/*.xml. +// For an example and a description of the format, see +// http://golang.org/doc/codewalk/codewalk or run godoc -http=:6060 +// and see http://localhost:6060/doc/codewalk/codewalk . +// That page is itself a codewalk; the source code for it is +// $GOROOT/doc/codewalk/codewalk.xml. + +package main + +import ( + "log" + "net/http" + "text/template" + + "golang.org/x/tools/godoc" + "golang.org/x/tools/godoc/redirect" + "golang.org/x/tools/godoc/vfs" +) + +var ( + pres *godoc.Presentation + fs = vfs.NameSpace{} +) + +func registerHandlers(pres *godoc.Presentation) { + if pres == nil { + panic("nil Presentation") + } + http.HandleFunc("/doc/codewalk/", codewalk) + http.Handle("/doc/play/", pres.FileServer()) + http.Handle("/robots.txt", pres.FileServer()) + http.Handle("/", pres) + http.Handle("/pkg/C/", redirect.Handler("/cmd/cgo/")) + redirect.Register(nil) +} + +func readTemplate(name string) *template.Template { + if pres == nil { + panic("no global Presentation set yet") + } + path := "lib/godoc/" + name + + // use underlying file system fs to read the template file + // (cannot use template ParseFile functions directly) + data, err := vfs.ReadFile(fs, path) + if err != nil { + log.Fatal("readTemplate: ", err) + } + // be explicit with errors (for app engine use) + t, err := template.New(name).Funcs(pres.FuncMap()).Parse(string(data)) + if err != nil { + log.Fatal("readTemplate: ", err) + } + return t +} + +func readTemplates(p *godoc.Presentation, html bool) { + p.PackageText = readTemplate("package.txt") + p.SearchText = readTemplate("search.txt") + + if html || p.HTMLMode { + codewalkHTML = readTemplate("codewalk.html") + codewalkdirHTML = readTemplate("codewalkdir.html") + p.CallGraphHTML = readTemplate("callgraph.html") + p.DirlistHTML = readTemplate("dirlist.html") + p.ErrorHTML = readTemplate("error.html") + p.ExampleHTML = readTemplate("example.html") + p.GodocHTML = readTemplate("godoc.html") + p.ImplementsHTML = readTemplate("implements.html") + p.MethodSetHTML = readTemplate("methodset.html") + p.PackageHTML = readTemplate("package.html") + p.SearchHTML = readTemplate("search.html") + p.SearchDocHTML = readTemplate("searchdoc.html") + p.SearchCodeHTML = readTemplate("searchcode.html") + p.SearchTxtHTML = readTemplate("searchtxt.html") + p.SearchDescXML = readTemplate("opensearch.xml") + } +} diff --git a/src/golang.org/x/tools/cmd/godoc/index.go b/src/golang.org/x/tools/cmd/godoc/index.go new file mode 100644 index 0000000000..f84b29a29d --- /dev/null +++ b/src/golang.org/x/tools/cmd/godoc/index.go @@ -0,0 +1,11 @@ +// Copyright 2015 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. + +package main + +import "strings" + +func indexDirectoryDefault(dir string) bool { + return dir != "/pkg" && !strings.HasPrefix(dir, "/pkg/") +} diff --git a/src/golang.org/x/tools/cmd/godoc/main.go b/src/golang.org/x/tools/cmd/godoc/main.go new file mode 100644 index 0000000000..3496013c15 --- /dev/null +++ b/src/golang.org/x/tools/cmd/godoc/main.go @@ -0,0 +1,329 @@ +// Copyright 2009 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. + +// godoc: Go Documentation Server + +// Web server tree: +// +// http://godoc/ main landing page +// http://godoc/doc/ serve from $GOROOT/doc - spec, mem, etc. +// http://godoc/src/ serve files from $GOROOT/src; .go gets pretty-printed +// http://godoc/cmd/ serve documentation about commands +// http://godoc/pkg/ serve documentation about packages +// (idea is if you say import "compress/zlib", you go to +// http://godoc/pkg/compress/zlib) +// +// Command-line interface: +// +// godoc packagepath [name ...] +// +// godoc compress/zlib +// - prints doc for package compress/zlib +// godoc crypto/block Cipher NewCMAC +// - prints doc for Cipher and NewCMAC in package crypto/block + +// +build !appengine + +package main + +import ( + "archive/zip" + _ "expvar" // to serve /debug/vars + "flag" + "fmt" + "go/build" + "log" + "net/http" + "net/http/httptest" + _ "net/http/pprof" // to serve /debug/pprof/* + "net/url" + "os" + "path/filepath" + "regexp" + "runtime" + "strings" + + "golang.org/x/tools/godoc" + "golang.org/x/tools/godoc/analysis" + "golang.org/x/tools/godoc/static" + "golang.org/x/tools/godoc/vfs" + "golang.org/x/tools/godoc/vfs/gatefs" + "golang.org/x/tools/godoc/vfs/mapfs" + "golang.org/x/tools/godoc/vfs/zipfs" +) + +const ( + defaultAddr = ":6060" // default webserver address + toolsPath = "golang.org/x/tools/cmd/" +) + +var ( + // file system to serve + // (with e.g.: zip -r go.zip $GOROOT -i \*.go -i \*.html -i \*.css -i \*.js -i \*.txt -i \*.c -i \*.h -i \*.s -i \*.png -i \*.jpg -i \*.sh -i favicon.ico) + zipfile = flag.String("zip", "", "zip file providing the file system to serve; disabled if empty") + + // file-based index + writeIndex = flag.Bool("write_index", false, "write index to a file; the file name must be specified with -index_files") + + analysisFlag = flag.String("analysis", "", `comma-separated list of analyses to perform (supported: type, pointer). See http://golang.org/lib/godoc/analysis/help.html`) + + // network + httpAddr = flag.String("http", "", "HTTP service address (e.g., '"+defaultAddr+"')") + serverAddr = flag.String("server", "", "webserver address for command line searches") + + // layout control + html = flag.Bool("html", false, "print HTML in command-line mode") + srcMode = flag.Bool("src", false, "print (exported) source in command-line mode") + urlFlag = flag.String("url", "", "print HTML for named URL") + + // command-line searches + query = flag.Bool("q", false, "arguments are considered search queries") + + verbose = flag.Bool("v", false, "verbose mode") + + // file system roots + // TODO(gri) consider the invariant that goroot always end in '/' + goroot = flag.String("goroot", runtime.GOROOT(), "Go root directory") + + // layout control + tabWidth = flag.Int("tabwidth", 4, "tab width") + showTimestamps = flag.Bool("timestamps", false, "show timestamps with directory listings") + templateDir = flag.String("templates", "", "directory containing alternate template files") + showPlayground = flag.Bool("play", false, "enable playground in web interface") + showExamples = flag.Bool("ex", false, "show examples in command line mode") + declLinks = flag.Bool("links", true, "link identifiers to their declarations") + + // search index + indexEnabled = flag.Bool("index", false, "enable search index") + indexFiles = flag.String("index_files", "", "glob pattern specifying index files; if not empty, the index is read from these files in sorted order") + indexInterval = flag.Duration("index_interval", 0, "interval of indexing; 0 for default (5m), negative to only index once at startup") + maxResults = flag.Int("maxresults", 10000, "maximum number of full text search results shown") + indexThrottle = flag.Float64("index_throttle", 0.75, "index throttle value; 0.0 = no time allocated, 1.0 = full throttle") + + // source code notes + notesRx = flag.String("notes", "BUG", "regular expression matching note markers to show") +) + +func usage() { + fmt.Fprintf(os.Stderr, + "usage: godoc package [name ...]\n"+ + " godoc -http="+defaultAddr+"\n") + flag.PrintDefaults() + os.Exit(2) +} + +func loggingHandler(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + log.Printf("%s\t%s", req.RemoteAddr, req.URL) + h.ServeHTTP(w, req) + }) +} + +func handleURLFlag() { + // Try up to 10 fetches, following redirects. + urlstr := *urlFlag + for i := 0; i < 10; i++ { + // Prepare request. + u, err := url.Parse(urlstr) + if err != nil { + log.Fatal(err) + } + req := &http.Request{ + URL: u, + } + + // Invoke default HTTP handler to serve request + // to our buffering httpWriter. + w := httptest.NewRecorder() + http.DefaultServeMux.ServeHTTP(w, req) + + // Return data, error, or follow redirect. + switch w.Code { + case 200: // ok + os.Stdout.Write(w.Body.Bytes()) + return + case 301, 302, 303, 307: // redirect + redirect := w.HeaderMap.Get("Location") + if redirect == "" { + log.Fatalf("HTTP %d without Location header", w.Code) + } + urlstr = redirect + default: + log.Fatalf("HTTP error %d", w.Code) + } + } + log.Fatalf("too many redirects") +} + +func main() { + flag.Usage = usage + flag.Parse() + + playEnabled = *showPlayground + + // Check usage: either server and no args, command line and args, or index creation mode + if (*httpAddr != "" || *urlFlag != "") != (flag.NArg() == 0) && !*writeIndex { + usage() + } + + var fsGate chan bool + fsGate = make(chan bool, 20) + + // Determine file system to use. + if *zipfile == "" { + // use file system of underlying OS + rootfs := gatefs.New(vfs.OS(*goroot), fsGate) + fs.Bind("/", rootfs, "/", vfs.BindReplace) + } else { + // use file system specified via .zip file (path separator must be '/') + rc, err := zip.OpenReader(*zipfile) + if err != nil { + log.Fatalf("%s: %s\n", *zipfile, err) + } + defer rc.Close() // be nice (e.g., -writeIndex mode) + fs.Bind("/", zipfs.New(rc, *zipfile), *goroot, vfs.BindReplace) + } + if *templateDir != "" { + fs.Bind("/lib/godoc", vfs.OS(*templateDir), "/", vfs.BindBefore) + } else { + fs.Bind("/lib/godoc", mapfs.New(static.Files), "/", vfs.BindReplace) + } + + // Bind $GOPATH trees into Go root. + for _, p := range filepath.SplitList(build.Default.GOPATH) { + fs.Bind("/src", gatefs.New(vfs.OS(p), fsGate), "/src", vfs.BindAfter) + } + + httpMode := *httpAddr != "" + + var typeAnalysis, pointerAnalysis bool + if *analysisFlag != "" { + for _, a := range strings.Split(*analysisFlag, ",") { + switch a { + case "type": + typeAnalysis = true + case "pointer": + pointerAnalysis = true + default: + log.Fatalf("unknown analysis: %s", a) + } + } + } + + corpus := godoc.NewCorpus(fs) + corpus.Verbose = *verbose + corpus.MaxResults = *maxResults + corpus.IndexEnabled = *indexEnabled && httpMode + if *maxResults == 0 { + corpus.IndexFullText = false + } + corpus.IndexFiles = *indexFiles + corpus.IndexDirectory = indexDirectoryDefault + corpus.IndexThrottle = *indexThrottle + corpus.IndexInterval = *indexInterval + if *writeIndex { + corpus.IndexThrottle = 1.0 + corpus.IndexEnabled = true + } + if *writeIndex || httpMode || *urlFlag != "" { + if err := corpus.Init(); err != nil { + log.Fatal(err) + } + } + + pres = godoc.NewPresentation(corpus) + pres.TabWidth = *tabWidth + pres.ShowTimestamps = *showTimestamps + pres.ShowPlayground = *showPlayground + pres.ShowExamples = *showExamples + pres.DeclLinks = *declLinks + pres.SrcMode = *srcMode + pres.HTMLMode = *html + if *notesRx != "" { + pres.NotesRx = regexp.MustCompile(*notesRx) + } + + readTemplates(pres, httpMode || *urlFlag != "") + registerHandlers(pres) + + if *writeIndex { + // Write search index and exit. + if *indexFiles == "" { + log.Fatal("no index file specified") + } + + log.Println("initialize file systems") + *verbose = true // want to see what happens + + corpus.UpdateIndex() + + log.Println("writing index file", *indexFiles) + f, err := os.Create(*indexFiles) + if err != nil { + log.Fatal(err) + } + index, _ := corpus.CurrentIndex() + _, err = index.WriteTo(f) + if err != nil { + log.Fatal(err) + } + + log.Println("done") + return + } + + // Print content that would be served at the URL *urlFlag. + if *urlFlag != "" { + handleURLFlag() + return + } + + if httpMode { + // HTTP server mode. + var handler http.Handler = http.DefaultServeMux + if *verbose { + log.Printf("Go Documentation Server") + log.Printf("version = %s", runtime.Version()) + log.Printf("address = %s", *httpAddr) + log.Printf("goroot = %s", *goroot) + log.Printf("tabwidth = %d", *tabWidth) + switch { + case !*indexEnabled: + log.Print("search index disabled") + case *maxResults > 0: + log.Printf("full text index enabled (maxresults = %d)", *maxResults) + default: + log.Print("identifier search index enabled") + } + fs.Fprint(os.Stderr) + handler = loggingHandler(handler) + } + + // Initialize search index. + if *indexEnabled { + go corpus.RunIndexer() + } + + // Start type/pointer analysis. + if typeAnalysis || pointerAnalysis { + go analysis.Run(pointerAnalysis, &corpus.Analysis) + } + + // Start http server. + if err := http.ListenAndServe(*httpAddr, handler); err != nil { + log.Fatalf("ListenAndServe %s: %v", *httpAddr, err) + } + + return + } + + if *query { + handleRemoteSearch() + return + } + + if err := godoc.CommandLine(os.Stdout, fs, pres, flag.Args()); err != nil { + log.Print(err) + } +} diff --git a/src/golang.org/x/tools/cmd/godoc/play.go b/src/golang.org/x/tools/cmd/godoc/play.go new file mode 100644 index 0000000000..a56ffe28bb --- /dev/null +++ b/src/golang.org/x/tools/cmd/godoc/play.go @@ -0,0 +1,44 @@ +// Copyright 2012 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. + +package main + +import ( + "encoding/json" + "fmt" + "go/format" + "net/http" + + // This package registers "/compile" and "/share" handlers + // that redirect to the golang.org playground. + _ "golang.org/x/tools/playground" +) + +func init() { + http.HandleFunc("/fmt", fmtHandler) +} + +type fmtResponse struct { + Body string + Error string +} + +// fmtHandler takes a Go program in its "body" form value, formats it with +// standard gofmt formatting, and writes a fmtResponse as a JSON object. +func fmtHandler(w http.ResponseWriter, r *http.Request) { + resp := new(fmtResponse) + body, err := format.Source([]byte(r.FormValue("body"))) + if err != nil { + resp.Error = err.Error() + } else { + resp.Body = string(body) + } + json.NewEncoder(w).Encode(resp) +} + +// disabledHandler serves a 501 "Not Implemented" response. +func disabledHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) + fmt.Fprint(w, "This functionality is not available via local godoc.") +} diff --git a/src/golang.org/x/tools/cmd/godoc/remotesearch.go b/src/golang.org/x/tools/cmd/godoc/remotesearch.go new file mode 100644 index 0000000000..f01d5c7acf --- /dev/null +++ b/src/golang.org/x/tools/cmd/godoc/remotesearch.go @@ -0,0 +1,72 @@ +// Copyright 2009 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 main + +import ( + "errors" + "flag" + "io" + "log" + "net/http" + "net/url" + "os" +) + +func handleRemoteSearch() { + // Command-line queries. + for i := 0; i < flag.NArg(); i++ { + res, err := remoteSearch(flag.Arg(i)) + if err != nil { + log.Fatalf("remoteSearch: %s", err) + } + io.Copy(os.Stdout, res.Body) + } + return +} + +// remoteSearchURL returns the search URL for a given query as needed by +// remoteSearch. If html is set, an html result is requested; otherwise +// the result is in textual form. +// Adjust this function as necessary if modeNames or FormValue parameters +// change. +func remoteSearchURL(query string, html bool) string { + s := "/search?m=text&q=" + if html { + s = "/search?q=" + } + return s + url.QueryEscape(query) +} + +func remoteSearch(query string) (res *http.Response, err error) { + // list of addresses to try + var addrs []string + if *serverAddr != "" { + // explicit server address - only try this one + addrs = []string{*serverAddr} + } else { + addrs = []string{ + defaultAddr, + "golang.org", + } + } + + // remote search + search := remoteSearchURL(query, *html) + for _, addr := range addrs { + url := "http://" + addr + search + res, err = http.Get(url) + if err == nil && res.StatusCode == http.StatusOK { + break + } + } + + if err == nil && res.StatusCode != http.StatusOK { + err = errors.New(res.Status) + } + + return +} diff --git a/src/golang.org/x/tools/cmd/godoc/setup-godoc-app.bash b/src/golang.org/x/tools/cmd/godoc/setup-godoc-app.bash new file mode 100755 index 0000000000..9d82cd7476 --- /dev/null +++ b/src/golang.org/x/tools/cmd/godoc/setup-godoc-app.bash @@ -0,0 +1,134 @@ +#!/usr/bin/env bash + +# 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. + +# This script creates a complete godoc app in $APPDIR. +# It copies the cmd/godoc and src/go/... sources from GOROOT, +# synthesizes an app.yaml file, and creates the .zip, index, and +# configuration files. +# +# If an argument is provided it is assumed to be the app-engine godoc directory. +# Without an argument, $APPDIR is used instead. If GOROOT is not set, "go env" +# is consulted to find the $GOROOT. +# +# The script creates a .zip file representing the $GOROOT file system +# and computes the correspondig search index files. These files are then +# copied to $APPDIR. A corresponding godoc configuration file is created +# in $APPDIR/appconfig.go. + +ZIPFILE=godoc.zip +INDEXFILE=godoc.index +SPLITFILES=index.split. +GODOC=golang.org/x/tools/cmd/godoc +CONFIGFILE=$GODOC/appconfig.go + +error() { + echo "error: $1" + exit 2 +} + +getArgs() { + if [ -z $APPENGINE_SDK ]; then + error "APPENGINE_SDK environment variable not set" + fi + if [ ! -x $APPENGINE_SDK/goapp ]; then + error "couldn't find goapp command in $APPENGINE_SDK" + fi + if [ -z $GOROOT ]; then + GOROOT=$(go env GOROOT) + echo "GOROOT not set explicitly, using go env value instead" + fi + if [ -z $APPDIR ]; then + if [ $# == 0 ]; then + error "APPDIR not set, and no argument provided" + fi + APPDIR=$1 + echo "APPDIR not set, using argument instead" + fi + + # safety checks + if [ ! -d $GOROOT ]; then + error "$GOROOT is not a directory" + fi + if [ -e $APPDIR ]; then + error "$APPDIR exists; check and remove it before trying again" + fi + + # reporting + echo "GOROOT = $GOROOT" + echo "APPDIR = $APPDIR" +} + +fetchGodoc() { + echo "*** Fetching godoc (if not already in GOPATH)" + unset GOBIN + go=$APPENGINE_SDK/goapp + $go get -d -tags appengine $GODOC + mkdir -p $APPDIR/$GODOC + cp $(find $($go list -f '{{.Dir}}' $GODOC) -type f -depth 1) $APPDIR/$GODOC/ +} + +makeAppYaml() { + echo "*** make $APPDIR/app.yaml" + cat > $APPDIR/app.yaml < $APPDIR/$CONFIGFILE < + + + + + + + + +Nothing to see here; move along. + + +`)) diff --git a/src/golang.org/x/tools/cmd/goimports/doc.go b/src/golang.org/x/tools/cmd/goimports/doc.go new file mode 100644 index 0000000000..46b2b07dfa --- /dev/null +++ b/src/golang.org/x/tools/cmd/goimports/doc.go @@ -0,0 +1,33 @@ +/* + +Command goimports updates your Go import lines, +adding missing ones and removing unreferenced ones. + + $ go get golang.org/x/tools/cmd/goimports + +It's a drop-in replacement for your editor's gofmt-on-save hook. +It has the same command-line interface as gofmt and formats +your code in the same way. + +For emacs, make sure you have the latest go-mode.el: + https://github.com/dominikh/go-mode.el +Then in your .emacs file: + (setq gofmt-command "goimports") + (add-to-list 'load-path "/home/you/somewhere/emacs/") + (require 'go-mode-load) + (add-hook 'before-save-hook 'gofmt-before-save) + +For vim, set "gofmt_command" to "goimports": + https://golang.org/change/39c724dd7f252 + https://golang.org/wiki/IDEsAndTextEditorPlugins + etc + +For GoSublime, follow the steps described here: + http://michaelwhatcott.com/gosublime-goimports/ + +For other editors, you probably know what to do. + +Happy hacking! + +*/ +package main // import "golang.org/x/tools/cmd/goimports" diff --git a/src/golang.org/x/tools/cmd/goimports/goimports.go b/src/golang.org/x/tools/cmd/goimports/goimports.go new file mode 100644 index 0000000000..b0b7aa8393 --- /dev/null +++ b/src/golang.org/x/tools/cmd/goimports/goimports.go @@ -0,0 +1,201 @@ +// 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. + +package main + +import ( + "bytes" + "flag" + "fmt" + "go/scanner" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + + "golang.org/x/tools/imports" +) + +var ( + // main operation modes + list = flag.Bool("l", false, "list files whose formatting differs from goimport's") + write = flag.Bool("w", false, "write result to (source) file instead of stdout") + doDiff = flag.Bool("d", false, "display diffs instead of rewriting files") + + options = &imports.Options{ + TabWidth: 8, + TabIndent: true, + Comments: true, + Fragment: true, + } + exitCode = 0 +) + +func init() { + flag.BoolVar(&options.AllErrors, "e", false, "report all errors (not just the first 10 on different lines)") +} + +func report(err error) { + scanner.PrintError(os.Stderr, err) + exitCode = 2 +} + +func usage() { + fmt.Fprintf(os.Stderr, "usage: goimports [flags] [path ...]\n") + flag.PrintDefaults() + os.Exit(2) +} + +func isGoFile(f os.FileInfo) bool { + // ignore non-Go files + name := f.Name() + return !f.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") +} + +func processFile(filename string, in io.Reader, out io.Writer, stdin bool) error { + opt := options + if stdin { + nopt := *options + nopt.Fragment = true + opt = &nopt + } + + if in == nil { + f, err := os.Open(filename) + if err != nil { + return err + } + defer f.Close() + in = f + } + + src, err := ioutil.ReadAll(in) + if err != nil { + return err + } + + res, err := imports.Process(filename, src, opt) + if err != nil { + return err + } + + if !bytes.Equal(src, res) { + // formatting has changed + if *list { + fmt.Fprintln(out, filename) + } + if *write { + err = ioutil.WriteFile(filename, res, 0) + if err != nil { + return err + } + } + if *doDiff { + data, err := diff(src, res) + if err != nil { + return fmt.Errorf("computing diff: %s", err) + } + fmt.Printf("diff %s gofmt/%s\n", filename, filename) + out.Write(data) + } + } + + if !*list && !*write && !*doDiff { + _, err = out.Write(res) + } + + return err +} + +func visitFile(path string, f os.FileInfo, err error) error { + if err == nil && isGoFile(f) { + err = processFile(path, nil, os.Stdout, false) + } + if err != nil { + report(err) + } + return nil +} + +func walkDir(path string) { + filepath.Walk(path, visitFile) +} + +func main() { + runtime.GOMAXPROCS(runtime.NumCPU()) + + // call gofmtMain in a separate function + // so that it can use defer and have them + // run before the exit. + gofmtMain() + os.Exit(exitCode) +} + +// parseFlags parses command line flags and returns the paths to process. +// It's a var so that custom implementations can replace it in other files. +var parseFlags = func() []string { + flag.Parse() + return flag.Args() +} + +func gofmtMain() { + flag.Usage = usage + paths := parseFlags() + + if options.TabWidth < 0 { + fmt.Fprintf(os.Stderr, "negative tabwidth %d\n", options.TabWidth) + exitCode = 2 + return + } + + if len(paths) == 0 { + if err := processFile("", os.Stdin, os.Stdout, true); err != nil { + report(err) + } + return + } + + for _, path := range paths { + switch dir, err := os.Stat(path); { + case err != nil: + report(err) + case dir.IsDir(): + walkDir(path) + default: + if err := processFile(path, nil, os.Stdout, false); err != nil { + report(err) + } + } + } +} + +func diff(b1, b2 []byte) (data []byte, err error) { + f1, err := ioutil.TempFile("", "gofmt") + if err != nil { + return + } + defer os.Remove(f1.Name()) + defer f1.Close() + + f2, err := ioutil.TempFile("", "gofmt") + if err != nil { + return + } + defer os.Remove(f2.Name()) + defer f2.Close() + + f1.Write(b1) + f2.Write(b2) + + data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput() + if len(data) > 0 { + // diff exits with a non-zero status when the files don't match. + // Ignore that failure as long as we get output. + err = nil + } + return +} diff --git a/src/golang.org/x/tools/cmd/gomvpkg/main.go b/src/golang.org/x/tools/cmd/gomvpkg/main.go new file mode 100644 index 0000000000..959b84ef5f --- /dev/null +++ b/src/golang.org/x/tools/cmd/gomvpkg/main.go @@ -0,0 +1,94 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// licence that can be found in the LICENSE file. + +// The gomvpkg command moves go packages, updating import declarations. +// See the -help message or Usage constant for details. +package main + +import ( + "flag" + "fmt" + "go/build" + "os" + + "golang.org/x/tools/go/buildutil" + "golang.org/x/tools/refactor/rename" +) + +var ( + fromFlag = flag.String("from", "", "Import path of package to be moved") + toFlag = flag.String("to", "", "Destination import path for package") + vcsMvCmdFlag = flag.String("vcs_mv_cmd", "", `A template for the version control system's "move directory" command, e.g. "git mv {{.Src}} {{.Dst}}`) + helpFlag = flag.Bool("help", false, "show usage message") +) + +func init() { + flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc) +} + +const Usage = `gomvpkg: moves a package, updating import declarations + +Usage: + + gomvpkg -from -to [-vcs_mv_cmd