├── .gitignore ├── AUTHORS ├── CONTRIBUTORS ├── LICENSE ├── README.md ├── content ├── dom │ └── quad_element.go ├── event_listeners.go ├── frame.go └── frame_test.go ├── docs ├── README.md ├── basic-diagram.graffle ├── basic-diagram.svg ├── basictypes.md ├── images.md ├── inkings.md └── introduction.md ├── experiments ├── cocoaview │ ├── app.go │ ├── events.go │ ├── goview.h │ ├── goview.m │ ├── start.h │ └── start.m └── main.go ├── graphics ├── arc.go ├── arc_test.go ├── dual.go ├── float32.go ├── gl.go ├── pointf.go └── rectanglef.go ├── main.go └── window ├── window.go └── window_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | gojiraw 2 | experiments/experiments 3 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of Gojiraw authors for copyright purposes. 2 | # This file is distinct from the CONTRIBUTORS files. 3 | # See the latter for an explanation. 4 | 5 | # Names should be added to this file as: 6 | # Name or Organization 7 | # The email address is not required for organizations. 8 | 9 | # Please keep the list sorted. 10 | 11 | Google Inc. 12 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # People who have agreed to one of the CLAs and can contribute patches. 2 | # The AUTHORS file lists the copyright holders; this file 3 | # lists people. For example, Google employees are listed here 4 | # but not in AUTHORS, because Google holds the copyright. 5 | # 6 | # https://developers.google.com/open-source/cla/individual 7 | # https://developers.google.com/open-source/cla/corporate 8 | # 9 | # Names should be added to this file as: 10 | # Name 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project is [Gojiraw](http://google.github.io/gojiraw/) -- a convenient abbreviation of "GO JIffy-fast 2 | RAW-draw". Gojiraw is a library and demo program for a deferred (i.e. 3 | display-list based) GPU-accelerated 2D rendering system. The drawing 4 | primitives (a work in progress) are inspired by the [Plan9 raster 5 | drawing library](https://swtch.com/plan9port/man/man3/draw.html) and the [Blink](http://www.chromium.org/blink) drawing interface. 6 | 7 | -------------------------------------------------------------------------------- /content/dom/quad_element.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gojiraw Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package dom 16 | 17 | import ( 18 | "image/color" 19 | "log" 20 | 21 | "github.com/google/gojiraw/graphics" 22 | ) 23 | 24 | const ( 25 | VERTEX_NON = iota // Draw the default vertex handle. 26 | VERTEX_HOVER // Draw the hover vertex handle. 27 | VERTEX_PRESS // Draw the mouse down vertex handle 28 | NUM_VERTEX_STATES 29 | ) 30 | 31 | const ( 32 | QUAD_ELEMENT_DX = 45. 33 | QUAD_ELEMENT_DY = 45. 34 | QUAD_ELEMENT_DH = 4. 35 | ) 36 | 37 | var modeToColor [NUM_VERTEX_STATES]color.RGBA 38 | 39 | func init() { 40 | modeToColor[VERTEX_NON] = color.RGBA{0, 0, 0, 0xf0} 41 | modeToColor[VERTEX_HOVER] = color.RGBA{0xff, 0, 0, 0xff} 42 | modeToColor[VERTEX_PRESS] = color.RGBA{0, 0, 0xff, 0xff} 43 | } 44 | 45 | func (qe *QuadElement) vertexColor(mode int) color.RGBA { 46 | return modeToColor[mode] 47 | } 48 | 49 | // Element is a placeholder for an element. Will want, not a tree, but a true 50 | // database format for efficent queries (including spatial). 51 | type QuadElement struct { 52 | vertices [4]graphics.Pointf 53 | color color.RGBA 54 | hoverMode int 55 | activeVertex int 56 | } 57 | 58 | func (qe *QuadElement) Init(pt graphics.Pointf) { 59 | qe.vertices[0] = graphics.Pointf{pt.X - QUAD_ELEMENT_DX, pt.Y - QUAD_ELEMENT_DY} 60 | qe.vertices[1] = graphics.Pointf{pt.X + QUAD_ELEMENT_DX, pt.Y - QUAD_ELEMENT_DY} 61 | qe.vertices[2] = graphics.Pointf{pt.X + QUAD_ELEMENT_DX, pt.Y + QUAD_ELEMENT_DY} 62 | qe.vertices[3] = graphics.Pointf{pt.X - QUAD_ELEMENT_DX, pt.Y + QUAD_ELEMENT_DY} 63 | qe.color = color.RGBA{uint8(0), uint8(0), uint8(0), uint8(25)} 64 | qe.hoverMode = VERTEX_NON 65 | qe.activeVertex = -1 66 | } 67 | 68 | func (qe *QuadElement) ActivateVertex(i int) graphics.Pointf { 69 | qe.hoverMode = VERTEX_PRESS 70 | qe.activeVertex = i 71 | return qe.vertices[i] 72 | } 73 | 74 | func (qe *QuadElement) Deactivate() { 75 | qe.hoverMode = VERTEX_HOVER 76 | } 77 | 78 | func (qe *QuadElement) SetActiveVertex(v graphics.Pointf) { 79 | qe.vertices[qe.activeVertex] = v 80 | } 81 | 82 | func (qe *QuadElement) drawHandle(dl *graphics.DisplayList) { 83 | dl.SetPointSize(2.) 84 | dl.SetColor(qe.vertexColor(VERTEX_NON)) 85 | 86 | var ps [4]graphics.Pointf 87 | count := 0 88 | for i, p := range qe.vertices { 89 | if qe.hoverMode == VERTEX_NON || i != qe.activeVertex { 90 | ps[count] = p 91 | count++ 92 | } 93 | } 94 | 95 | dl.DrawPoints(ps[:]) 96 | if qe.hoverMode != VERTEX_NON { 97 | log.Printf("drawing hover vertex") 98 | dl.SetPointSize(2 * QUAD_ELEMENT_DH) 99 | if qe.hoverMode == VERTEX_HOVER { 100 | dl.SetColor(qe.vertexColor(VERTEX_HOVER)) 101 | } else { 102 | dl.SetColor(qe.vertexColor(VERTEX_PRESS)) 103 | } 104 | dl.DrawPoints([]graphics.Pointf{qe.vertices[qe.activeVertex]}) 105 | } 106 | } 107 | 108 | // TODO(vollick): split this out into an interface. 109 | func (qe *QuadElement) Draw(dl *graphics.DisplayList) { 110 | dl.SetColor(qe.color) 111 | dl.DrawQuads([][4]graphics.Pointf{qe.vertices}) 112 | qe.drawHandle(dl) 113 | } 114 | 115 | func (qe *QuadElement) FindVertex(p graphics.Pointf) int { 116 | o := graphics.Pointf{QUAD_ELEMENT_DH, QUAD_ELEMENT_DH} 117 | for i, v := range qe.vertices { 118 | r := graphics.Rectanglef{v.Sub(o), v.Add(o)} 119 | if p.In(r) { 120 | return i 121 | } 122 | } 123 | return -1 124 | } 125 | 126 | func (qe *QuadElement) HoverOn(v int) { 127 | log.Printf("HoverOn %d", v) 128 | qe.hoverMode = VERTEX_HOVER 129 | qe.activeVertex = v 130 | } 131 | 132 | func (qe *QuadElement) HoverOff() { 133 | log.Printf("HoverOff") 134 | qe.hoverMode = VERTEX_NON 135 | } 136 | -------------------------------------------------------------------------------- /content/event_listeners.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gojiraw Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package content 16 | 17 | import ( 18 | "image" 19 | "log" 20 | ) 21 | 22 | // | These together to record which button is up or down. 23 | // In particular: the buttons value will be some or of the following. 24 | const ( 25 | MOUSE_BUTTON_NONE = 0 26 | ) 27 | 28 | // Reset iota. 29 | const ( 30 | MOUSE_BUTTON_LEFT = 1 << iota 31 | MOUSE_BUTTON_MIDDLE 32 | MOUSE_BUTTON_RIGHT 33 | ) 34 | 35 | const ( 36 | EVD_NON = iota // There is no handler. 37 | EVD_PREVDEF // A handler exists and it wishes the default action for the event to be suppressed. 38 | EVD_DEF // A handler exists and it wants the default action. 39 | ) 40 | 41 | // Anything Frame-like entity capable of receiving events 42 | // needs to implement this interface. 43 | type EventHandler interface { 44 | // Corresponds to JS registered with onmousedown 45 | Mousedown(pt image.Point, button, buttons uint32) uint32 46 | 47 | // Corresponds to JS registered with onmouseup 48 | Mouseup(pt image.Point, button, buttons uint32) uint32 49 | 50 | // Corresponds to JS registered with onmousemove 51 | Mousemove(pt image.Point, buttons uint32) uint32 52 | 53 | // Corresponds to JS registered with onmousewheel 54 | Wheel(pt image.Point, buttons uint32, dx, dy, dz float32) uint32 55 | } 56 | 57 | // These are "event listeners": functionality that really 58 | // ought to belong in the JavaScript code of the browser. 59 | // As of yet, this code is busily running here though written in 60 | // Go. Writing these handlers will help define the interfaces that 61 | // we need to schlep events from browser to the v8 thread. 62 | // 63 | // General scheme: in a future with JavaScript, the content side (i.e. main thread) 64 | // for these handlers would receive the event bundle and call into v8 65 | // On the browser side, the messaging proxy would handle this. 66 | // 67 | func (f *Frame) Mousedown(pt image.Point, button, buttons uint32) uint32 { 68 | log.Printf("OnMouseDown") 69 | 70 | e, v := f.FindElementAtPoint(pt) 71 | if e != nil && v > -1 { 72 | f.StartMouseDownMode(pt, e, v) 73 | } 74 | return EVD_PREVDEF 75 | } 76 | 77 | func (f *Frame) Mouseup(pt image.Point, button, buttons uint32) uint32 { 78 | if button == 0 && f.mouseDown { 79 | f.EndMouseDownMode() 80 | } else if button == 0 { 81 | f.AddElement(pt) 82 | } 83 | return EVD_PREVDEF 84 | } 85 | 86 | func (f *Frame) Mousemove(pt image.Point, buttons uint32) uint32 { 87 | if buttons&MOUSE_BUTTON_LEFT == MOUSE_BUTTON_LEFT { 88 | f.InMouseDownMode(pt) 89 | } else { 90 | e, v := f.FindElementAtPoint(pt) 91 | f.MouseOver(e, v) 92 | } 93 | return EVD_PREVDEF 94 | } 95 | 96 | func (f *Frame) Wheel(pt image.Point, buttons uint32, dx, dy, dz float32) uint32 { 97 | // The container for the frame scrolls. 98 | return EVD_NON 99 | } 100 | -------------------------------------------------------------------------------- /content/frame.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gojiraw Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package content 16 | 17 | import ( 18 | "image" 19 | "log" 20 | 21 | "github.com/google/gojiraw/content/dom" 22 | "github.com/google/gojiraw/graphics" 23 | "github.com/go-gl/gl" 24 | ) 25 | 26 | // Frame is the Gojira equivalent of a RenderFrame in Chrome? 27 | // The basic skeleton of shipping the display list and RenderFrame 28 | // sizes between processes will probably entail re-writing this. 29 | // Note(vollick): Frames always have documents. Documents are currently 30 | // "special" in blink in that they don't have a renderer. Let's just leave the 31 | // document related code in Frame. If anything, "document" could just be an 32 | // interface Frame implements. 33 | type Frame struct { 34 | // Probably gonna own a page. :) 35 | // (Note(vollick): Pages are the things that own frame trees. I think 36 | // that's analogous to windows. Let's move the page code into window.) 37 | 38 | // Translation 39 | // TODO(vollick): Probably want a transform here to capture scaling. 40 | x, y, z float32 41 | 42 | // Viewport 43 | w, h float32 44 | 45 | // The most recently mouse-overed element or nil. 46 | overElement *dom.QuadElement 47 | 48 | // The mouse is down. 49 | mouseDown bool 50 | 51 | // A floating point offset from a mouseDown to the centroid of the handle. 52 | // TODO(vollick): It's fishy that Frame knows anything about "handles." 53 | offset graphics.Pointf 54 | 55 | // The root of the document. 56 | document []dom.QuadElement 57 | } 58 | 59 | // AddElement extends the document slice and fills in the new element with a 60 | // quad. 61 | func (f *Frame) AddElement(p image.Point) { 62 | ne := len(f.document) 63 | nd := f.document[0 : ne+1] 64 | pf := graphics.Ptfi(p) 65 | // Note: the following code initializes the new point into the allocated 66 | // memory in the document list. It does *not* resize the list; if we have 67 | // more than 1000 quads, we'll die. 68 | // TODO(vollick): make this dynamic. 69 | (&nd[ne]).Init(pf) 70 | f.document = nd 71 | } 72 | 73 | // Find the control point, if any, under Point p. Return nil, 0 if there is no 74 | // control point for an element under p. The returned int is the index 75 | // of the vertex. 76 | // TODO(rjkroege): The model could be a tree eventually. :-) 77 | func (f *Frame) FindElementAtPoint(p image.Point) (*dom.QuadElement, int) { 78 | pf := graphics.Ptfi(p) 79 | log.Printf("FindElementAtPoint %v", p) 80 | for i := len(f.document) - 1; i >= 0; i-- { 81 | if v := f.document[i].FindVertex(pf); v != -1 { 82 | return &f.document[i], v 83 | } 84 | } 85 | return nil, -1 86 | } 87 | 88 | // Adjusts visual style for elements that are under the 89 | // mouse pointer. 90 | func (f *Frame) MouseOver(qe *dom.QuadElement, v int) { 91 | log.Printf("MouseOver: %+v, %d", qe, v) 92 | if f.overElement != nil && f.overElement != qe { 93 | f.overElement.HoverOff() 94 | } 95 | f.overElement = qe 96 | if qe != nil { 97 | qe.HoverOn(v) 98 | } 99 | } 100 | 101 | // Pan sets a translation on the Frame to permit the Frame to move around within 102 | // its viewport. Translates the viewport w.r.t. the Frame origin by p. 103 | func (f *Frame) Pan(dx, dy float32) { 104 | f.x = graphics.MinF(f.x+dx, f.w) 105 | f.y += graphics.MinF(f.y+dy, f.h) 106 | log.Printf("translation: %d %d", f.x, f.y) 107 | } 108 | 109 | // Resize tells the frame what its size should be. 110 | func (f *Frame) Resize(w, h float32) { 111 | f.w = graphics.MaxF(w, f.w) 112 | f.h = graphics.MaxF(h, f.h) 113 | log.Printf("current size: %d %d", f.w, f.h) 114 | } 115 | 116 | func NewFrame() *Frame { 117 | // TODO(vollick): allow more than 1000 things. 118 | d := make([]dom.QuadElement, 0, 1000) 119 | return &Frame{document: d} 120 | } 121 | 122 | // TODO(rjk): Tell the Frame to clip its drawing to a given viewport. 123 | // x, y is the position of the Frame's origin in the containing port. 124 | // w, h is the width and height of the port in the port's coordinates. 125 | // Returns the enclosing boundary of the Frame. 126 | // TODO(rjkroege): boundaries should admit objects outside [0, w), [0. h)? 127 | // TODO(rjkroege): Provide and wire in types for stuff, boxes, etc. 128 | func (frame *Frame) Draw(x, y, vw, vh float32, program *gl.Program) (fw, fh float32) { 129 | // Build the display list. 130 | dl := &graphics.DisplayList{} 131 | for _, e := range frame.document { 132 | e.Draw(dl) 133 | } 134 | 135 | gl.ClearColor(1, 1, 1, 0) 136 | graphics.CheckForGLErrors() 137 | gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) 138 | graphics.CheckForGLErrors() 139 | 140 | gl.Enable(gl.BLEND) 141 | gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) 142 | graphics.CheckForGLErrors() 143 | 144 | dl.Draw(program, vw, vh) 145 | 146 | return dl.W, dl.H 147 | } 148 | 149 | func (f *Frame) StartMouseDownMode(pt image.Point, qe *dom.QuadElement, v int) { 150 | f.overElement = qe 151 | f.mouseDown = true 152 | pf := graphics.Ptfi(pt) 153 | f.offset = pf.Sub(qe.ActivateVertex(v)) 154 | } 155 | 156 | func (f *Frame) InMouseDownMode(pt image.Point) { 157 | // Is this idiomatic? 158 | pf := graphics.Ptfi(pt) 159 | if qe := f.overElement; qe != nil { 160 | qe.SetActiveVertex(pf.Add(f.offset)) 161 | } 162 | } 163 | 164 | func (f *Frame) EndMouseDownMode() { 165 | f.mouseDown = false 166 | f.overElement.Deactivate() 167 | } 168 | -------------------------------------------------------------------------------- /content/frame_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gojiraw Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package content 16 | 17 | import ( 18 | "github.com/google/gojiraw/content/dom" 19 | "github.com/rjkroege/wikitools/testhelpers" 20 | "image" 21 | "testing" 22 | ) 23 | 24 | func Test_FindElementAtPoint(t *testing.T) { 25 | f := NewFrame() 26 | testhelpers.AssertInt(t, 0, len(f.document)) 27 | 28 | p := image.Pt(10, 10) 29 | 30 | e, _ := f.FindElementAtPoint(p) 31 | if e != nil { 32 | t.Errorf("empty display list can't have element in it") 33 | } 34 | 35 | f.AddElement(image.Pt(30, 10)) 36 | f.AddElement(image.Pt(10, 30)) 37 | 38 | e, v := f.FindElementAtPoint(p) 39 | if e != nil { 40 | t.Errorf("despite a point outside the elements, we hit anyway: %+v", e) 41 | } 42 | 43 | e, v = f.FindElementAtPoint(image.Pt(30-dom.QUAD_ELEMENT_DX, 10+dom.QUAD_ELEMENT_DY)) 44 | if e == nil || *e != f.document[0] { 45 | t.Errorf("point 30,10 in %+v but found element is %+v", f.document[0], e) 46 | } 47 | if v != 3 { 48 | t.Errorf("desired vertex 3 but didn't get it %d", v) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | Design notes for Gojiraw go here. 2 | 3 | My intent was that design notes are written in 4 | [Markdown](http://daringfireball.net/projects/markdown/syntax) and use 5 | [CriticMarkup](http://criticmarkup.com/spec.php) for comments. 6 | GitHub doesn't however support CriticMarkup. I'll use 7 | task lists instead. 8 | 9 | Reading order: 10 | 11 | * [Introduction](introduction.md) 12 | * [Basic types](basictypes.md) 13 | * [Images](images.md) 14 | * [Inkings](inkings.md) 15 | -------------------------------------------------------------------------------- /docs/basic-diagram.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/gojiraw/ba5fac2f7b1f77d235f6ac934c9b71d5e22778b5/docs/basic-diagram.graffle -------------------------------------------------------------------------------- /docs/basic-diagram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Produced by OmniGraffle 6.2.3 2015-07-07 14:44:07 +0000Canvas 1Layer 2Gojiraw UI ServerApplicationGojiraw libraryAPIs…Prototype, stage 1ApplicationGojiraw APIAPIs…ApplicationGojiraw APIAPIs…Prototype, stage 2Shared memory display list storemessagesadd enzos 4 | -------------------------------------------------------------------------------- /docs/basictypes.md: -------------------------------------------------------------------------------- 1 | This section describes the primtive types largely to flesh out some 2 | of the other more interesting sections. See the source for the 3 | actual definition. 4 | 5 | # Geometry 6 | Standard geometry definitions. 7 | 8 | ```go 9 | package geometry 10 | 11 | type Point struct { 12 | x float 13 | y float 14 | } 15 | 16 | type Rectangle struct { 17 | min Point 18 | max Point 19 | } 20 | 21 | type Matrix struct { 22 | // numbers... 23 | } 24 | 25 | func (m Matrix) TransformRect(r Rectangle) Rectangle 26 | // Produces a Matrix that translates by p 27 | func Translate(p Point) Matrix 28 | 29 | ``` 30 | 31 | # Drawop 32 | Each of the compositing operations in Porter-Duff compositing. 33 | Names and definitions per Plan9. 34 | 35 | ```go 36 | package drawop 37 | 38 | const ( 39 | Clear = 0 40 | DoutS = 1 << iota 41 | SoutD = 2 42 | DinS = 4 43 | SinD = 8 44 | S = SinD|SoutD 45 | SoverD = SinD|SoutD|DoutS 46 | SatopD = SinD|DoutS 47 | SxorD = SoutD|DoutS 48 | D = DinS|DoutS 49 | DoverS = DinS|DoutS|SoutD 50 | DatopS = DinS|SoutD 51 | DxorS = DoutS|SoutD /* == SxorD */ 52 | Ncomp = 12 53 | ) 54 | ``` 55 | 56 | # Glyph 57 | Gojiraw deliberately excludes font shaping from its scope. Instead, it deals 58 | in *glyphs*. 59 | 60 | 61 | ```go 62 | type Glyph int32 63 | ``` 64 | 65 | NB: a `glyph` is *not* a UTF `rune`. Gojiraw will attempt to create an 66 | equivalence between valid runes and the letter shape associated with 67 | the identical glyph. For example, rune 0x41 is a valid UTF code point 68 | and also a valid glyph to reference the letter shape for `A` in the 69 | current style. This convenience makes sense when font shaping is 70 | unused as it permits treating most rune arrays as an identical glyph 71 | array. The `utf8.ValidRune(rune)` method will determine if a given 72 | glyph corresponds to a actual UTF code point. 73 | 74 | -------------------------------------------------------------------------------- /docs/images.md: -------------------------------------------------------------------------------- 1 | # Image 2 | An `Image` is an `Inkings` augmented with an optional rectangular 3 | expanse of pixels that can be the target of GPU rasterization or the 4 | source for scanout to the `Display` These pixels are not inside the 5 | Image structure. Instead, the system (i.e. GPU) owns the actual 6 | pixels. 7 | 8 | It is possible that we must support referring to the underlying pixel 9 | data in two different processes. If so, we need a mechanism to pass 10 | image data handles between processes using an operating system 11 | appropriate mechanism such as `gralloc` buffers or `IOSurface` 12 | objects. We should however attempt to satisfy the needs of Gojiraw 13 | clients without providing such a facility as (at least in my opinion) 14 | inter-process GPU image transport has brought enormous complexity to 15 | Chrome. 16 | 17 | When overlays are available, a presentable `Image` will own no pixel 18 | data itself. Instead, it will contain (as it is an `Inkings`) one or 19 | more children `Image` objects that each have their own GPU-resident 20 | pixel data that will be used for scanout. 21 | 22 | ```go 23 | const ( 24 | _ = iota 25 | OverlayCandidate = iota 26 | ) 27 | 28 | type Image interface { 29 | Load(r Rectangle, data []byte, opts ...interface{}) <-channel error 30 | Unload(r Rectangle, data []byte, opts ...interface{}) <-channel error 31 | Release() 32 | NewImage(r Rectangle, opts ...interface{}) (Image, error) 33 | } 34 | 35 | func Zero() Image 36 | ``` 37 | 38 | ## LoadImage 39 | `Load` fills `Rectangle r` with pixel `data` as controlled by the 40 | additional configuration options. The routine returns immediately. The 41 | returned channel indicates when the uploading process has completed. 42 | 43 | Options specify stride, packing etc. as suggested by the arguments to 44 | `glTexSubImage2D`. 45 | 46 | - [ ] Am I providing a rich enough API surface for copy minimization 47 | - [ ] I need to refine and make precise how we can determine and take 48 | advantage of hardware support for image conversion (decompression for 49 | example) during the execution of a `Load` operation. 50 | - [ ] The error should be available synchronously. 51 | 52 | ## Unload 53 | `Unloadimage` reads a rectangle of pixels from the target `Image` and writes 54 | it into data as controlled by the options. The returned channel indicates when 55 | the uploading process has completed. 56 | 57 | ## Release 58 | `Release` explicitly relinquishes the storage underlying this `Image`. Rational: GPU 59 | image data is large, GPU memory is frequently special and/or in short supply. 60 | So provide a mechanism to explicitly indicate when the memory backing 61 | an `Image` is no longer needed. 62 | 63 | ## NewImage 64 | `NewImage` creates an `Image` as specified by the provided options. 65 | Options include the `OverlayCandidate` to indicate that this `Image` 66 | should have backing storage allocated in a fashion that it could be a 67 | scanout source. 68 | 69 | The created `Image` object will use the same GPU pixel storage as 70 | the target object. 71 | 72 | Attempting to render an `Image` object to an `Image` that does not share 73 | the same GPU storage will fail. 74 | 75 | - [ ] Specify when this failure is detected and indicated. 76 | 77 | # Display 78 | `Display` contains state about the connected graphics system. 79 | A `Display` is also an `Image`. A `Display` containing multiple screens will 80 | have several different child `Display` objects corresponding to the actual 81 | screens. 82 | 83 | - [ ] This section ignores that screens can refresh at different rates and 84 | that the Display could be generated by multiple different GPUs. Address. 85 | 86 | ```go 87 | type RefreshDeadline struct { 88 | VblankEnd time.Duration 89 | NextVblankEnd time.Duration 90 | } 91 | 92 | type ScreenInfo struct { 93 | // TBD 94 | } 95 | 96 | type Display interface { 97 | Image 98 | RefreshDeadlineChannel() <-channel RefreshDeadline 99 | Screens() []Display 100 | ScreensInfo() []ScreenInfo 101 | } 102 | 103 | NewMockDisplay(opts ...interface{}) Display 104 | NewDisplay(opts ...interface{}) Display 105 | ``` 106 | 107 | ## RefreshDeadlineChannel 108 | `RefreshDeadlineChannel` returns a channel that becomes ready on 109 | the start of a vblank and provides the `time.Duration` into the future 110 | of the end of the vblank interval and the end of the *next vblank*. 111 | 112 | ## Screens 113 | `Screens` return the children `Display` objects, one for each 114 | physically attached screen. Enumerate the size of actual monitors on 115 | the system like this: 116 | 117 | ```go 118 | 119 | display := NewDisplay(/* details elided */) 120 | for i, s := range display.Screens() { 121 | r := s.Bound() 122 | fmt.Printf("[%d] x %f y %f w %f h %f", i, r.min.x, r.min.y, r.max.x - r.min.x, r.max.y - r.min.y) 123 | } 124 | ``` 125 | 126 | ## ScreensInfo 127 | `ScreensInfo` returns an array of additional information for each attached screen. 128 | 129 | ## Discussion 130 | Create an `Image` for by using `NewImage` on a `Display`. In the multiple screens 131 | case, it will be an error to attempt to do so on the top-level `Display` object. 132 | 133 | `NewMockDisplay()` returns a `Display` whose `Image` objects are memory 134 | resident and cannot be used for scanout. For example, the following 135 | code fragment would provide functionality resembling the ozone files 136 | backend. 137 | 138 | ```go 139 | model := NewInkings() 140 | tick := 0 141 | display := NewMockDisplay() 142 | 143 | for { 144 | select { 145 | case e := <-getNextEvent(): 146 | appUpdatesModelForEvent(model, e) 147 | case <-time.Tick(time.Millisecond * 16): 148 | snapshot := <-ink.Clone() 149 | go func(snapshot Inkings, tick int) { 150 | r := geom.Rect{geom.Point{0,0}, geom.Point{1024, 768}} 151 | image, _ := display.NewImage(r) 152 | <-snapshot.Render(image) 153 | buf := make([]byte, 1024 * 768 * 4) 154 | <-image.Unload(buf, r, /* options for packed, contiguous, alpha, etc. */) 155 | ioutil.WriteFile(fmt.Sprintf("frame_%d", tick), buf, 0x644) 156 | } 157 | tick += 1 158 | } 159 | } 160 | ``` 161 | 162 | `Show` on a `Display` object has different semantics than it does on 163 | an arbitrary `Inkings` In particular: it sets the display controller 164 | to scanout from the provided `Image` immediately. Users of the entry 165 | point wishing to avoid tearing should use the 166 | `RefreshDeadlineChannel()` channel to limit these `Show` calls to 167 | inside the vblank interval. 168 | 169 | ```go 170 | display := NewDisplay() // Assume one screen. 171 | model := NewInkings() 172 | 173 | // This would compile but is silly. 174 | display.Render(display) 175 | 176 | // Make a new Image. 177 | image := display.NewImage(Rect{{0,0}, {1024,768}}) 178 | 179 | // Add enzo to image... 180 | 181 | // This is allowed. Only image Enzo corresponding to overlay 182 | // planes remain. 183 | <-image.Render(image) 184 | 185 | // Show on a display means that display hardware is now scanning 186 | // out from image. 187 | <-display.Show(image) 188 | 189 | // This code executes on the vblank after the frame in which image 190 | // was the scanout source. 191 | fmt.Printf("hello") 192 | ``` 193 | 194 | -------------------------------------------------------------------------------- /docs/inkings.md: -------------------------------------------------------------------------------- 1 | # Enso 2 | `Enso` is a single simple drawable entity like a circle. The word is 3 | from Zen Buddhist calligraphy and means (literally) circle. An Enzo 4 | combines geometry and paint into an opaque object that has membership 5 | in an Inkings. 6 | 7 | ``` 8 | type Enso uint32 9 | ``` 10 | 11 | As with a calligraphic strokes, an Enso is immutable once stroked into 12 | an Inkings. Enzo immutability is intended to permit the library to 13 | take any combination of deferred and eager rasterization of the Enzo 14 | recorded into a specific Inkings. 15 | 16 | For example, a ideal Gorjiraw implementation would convert a sequence 17 | of `Enzo` into an array of floating point numbers and a shader 18 | program. Redrawing the `Enzo` would proceed in the best case 19 | GPU-accelerated fashion: call GPU library with pointer to float array 20 | and pre-compiled shader program. 21 | 22 | # Inkings 23 | Inkings are a mutable record of multiple Enzo and other Inkings. 24 | Inkings form a DAG. Inkings can be rasterized to an Image and hence 25 | presented to the Display. 26 | 27 | My intent with Inkings is to provide a plug-compatible substitute for 28 | an Image except that, per the non-blocking principle, the pixel 29 | contents are available only as a promise. 9p Images are frequently the 30 | source and destination for the same draw operation. To preserve 31 | this capability, lazy rasterization and the mutability of the Inkings, 32 | Inkings offer a `Clone` function. 33 | 34 | A naïve algorithm for rasterizing an Inkings is to postfix depth-first 35 | traverse the Inkings graph, allocating an Image for each Inkings and 36 | rasterizing its Enzo before combining Inkings into their parent Image 37 | via texture draw. 38 | 39 | ```go 40 | type Inkings Interface { 41 | Repl() bool 42 | Bound() Rectangle 43 | Clipr() Rectangle 44 | 45 | Clone() <-channel Inkings 46 | Zero() 47 | 48 | Render(i Image, opts ...interface{}) <-channel Image 49 | Show(i Image, opts ...interface{}) <-channel Image 50 | 51 | // r generalizes to a Path. Worry about that later. 52 | DrawInkings(r Rectangle, src, mask *Inkings, m *Matrix, d Drawop) Enzo 53 | ReDrawInkings(eid Enzo, r Rectangle, src, mask *Inkings, m *Matrix, d Drawop) Enzo 54 | 55 | GlyphString(glyphs []Glyph, styles []byte, stylefonts []StyledFont, p Point, m Matrix) Point 56 | } 57 | 58 | func NewInkings() (Inkings, error) { 59 | } 60 | ``` 61 | 62 | ## NewInkings 63 | `NewInkings` creates a new Inkings. Each Inkings is (conceivably) 64 | infinite in extent and has its own coordinate system. A new empty 65 | Inkings is transparent (alpha = 0) and therefore has no effect when 66 | added to another Inkings. 67 | 68 | ## Clone 69 | `Clone` returns a channel that will contain a deep clone of the target 70 | `Inkings`. Modifying `target` while `Clone` is in progress may have 71 | undefined behaviour. Clone is central to how to use the Gojiraw API: 72 | maintain a `Inkings` *model*, `Clone` it and render that. 73 | 74 | ```go 75 | 76 | // Many elided details... 77 | model := NewInkings() 78 | var display := NewDisplay() 79 | 80 | wins := make(channel Image, 2) 81 | wins <- currentDisplay.NewImage( /* ... */) 82 | wins <- currentDisplay.NewImage(/* ... */), 83 | 84 | rc := display.RefreshDeadlineChannel() 85 | var snapshot Inkings 86 | 87 | ni var <-channel Image 88 | 89 | for { 90 | select { 91 | case <-rc: 92 | snapshot := <- ink.Clone() 93 | win := <- wins 94 | // TODO(rjk): The channel needs to be passed 95 | // in so that Image objects can be reused more easily. 96 | ni = shown = <-snapshot.Show(win) 97 | case im := <- ni 98 | win <- im 99 | case e := <-NextEvent 100 | appUpdatesModelForEvent(model, e) 101 | } 102 | } 103 | ``` 104 | 105 | ## Render 106 | `Render` asynchronously rasterizes the target `Inking` to `Image i` and 107 | triggers the returned channel with an updated `Image` object that contains 108 | only rendered `Images`. 109 | 110 | The returned `Image` may not be "flat". The `Inkings` may request use 111 | of overlays by containing children `Image` objects that have the 112 | `OverlayCandidate` attribute set. Then, the children `Image` objects 113 | of the returned `Image` will correspond to hardware planes. 114 | 115 | Between invoking this call and before the channel is triggered, the caller 116 | should not modify the `Image`. 117 | 118 | Some `Inkings` in the DAG may need longer than others. Intermediate 119 | `Image` objects can be multi-buffered to accomodate this. 120 | 121 | - [ ] Figure out if this requires `Clone` to be synchronous. 122 | 123 | ## Show 124 | `Show` combines rendering with presentation. It asynchronously performs the following steps 125 | 126 | * rasterize the target `Inking` to `Image i`. 127 | * on the next vblank after rasterization, scan from `Image i` 128 | * on next vblank after that, trigger the channel with `Image i` 129 | 130 | ## Zero 131 | `Zero` removes all the Enzos from the `Inkings`. 132 | 133 | ## Bound 134 | `Bound` returns the smallest `Rectangle` enclosing the entire 135 | `Inkings` 136 | 137 | ## DrawInkings 138 | The most basic drawing operation in Gojiraw. It records an Enzo 139 | wrapping `src`, `mask`, `m`, `op` in Inkings `target` and returns an 140 | Enzo identifier. 141 | 142 | DrawInkings has the following semantics for rasterization: 143 | 144 | * create Rectangle `r` in the coordinate system of `target`. 145 | 146 | * interesect `r` with `target.Clipr` (and other clips that might exist.) 147 | 148 | * `m` transforms `r` from the target's coordinate system into `src`. 149 | Combining `r` and `m` permit picking out an arbitrary 150 | rectangle from `src`. 151 | 152 | * `m` also transforms `r` from the target's CS into `mask` in identical fashion. 153 | 154 | * if `src.Repl` is set, consider `src` to tile its plane and if `mask.Repl` is set, consider `mask` to tile its plane. 155 | 156 | * for each pixel location in `r`, combine `target` pixel with `src` pixel using alpha 157 | provided by `mask` pixel using Porter-Duff composition specified by `op`. 158 | 159 | Note that `Image` objects are usable as `Inkings` so this method also 160 | offers the capability of drawing `Image` objects. 161 | 162 | ## ReDrawInkings 163 | Operates identically to `DrawInkings` except that rather than adding a new 164 | Enzo to Inkings `target`, replaces the specified Enzo. 165 | 166 | # GlyphString 167 | `GlyphString` efficiently draws runs of styled text. 168 | The server does not line-break the provided run. 169 | 170 | * `glyphs` an array of glyphs. NB: glyphs are not necessarily runes in order to support 171 | languages like Arabic and ligatures. It seems convenient that runes are 1-1 with glyphs 172 | where possible. For example, glyph 0x41 could be `A`. 173 | * `styles` an array of 1 byte style identies, one per glyph or alternatively an array of 174 | length 1 which selects the StyleFont used for every glyph. 175 | * 'styledfonts' is an array of `StyleFont` objects. Each byte in `styles` chooses 176 | the `StyleFont` used to draw the corresponding glyph in `glyphs` 177 | * `p` is a `Point` specifying where to position the origin corner of the glyph in 178 | the coordinate system of the target `Inkings`. 179 | * 'm' is a `Matrix` that specifies the how to advance to the next glyph position: next `p` 180 | is `p += m * glyph-bounds`. 181 | 182 | Because no line breaking is done. `\n` is treated as white space. Per the 183 | Plan9 `string` function, `GlyphString` "returns a `Point` that is the 184 | position of the next character that would be drawn if the string were 185 | longer." 186 | 187 | Drawing proceeds as the following "Go with vector operations" pseudo-code 188 | 189 | ```go 190 | // A conceptually workable but brutally slow definition of outline fonts. 191 | // The outline origin has its natural meaning. 192 | type StyleFont []Inkings 193 | 194 | m1 := geometry.Translate(p) 195 | 196 | for i, _ := range glyphs { 197 | im := styledfonts[styles[i]][glyphs[i]] 198 | DrawInkings(m1.TransformRect(im.Bound()), im, im, m1, drawop.DoverS) 199 | m1 = m1 + m * (im.Bound.Max - im.Bound.Min) 200 | } 201 | 202 | return /* translation in x and y from m1 */ 203 | ``` 204 | 205 | *insert diagram* 206 | 207 | 208 | ## Discussion 209 | We require: 210 | 211 | * efficient encoding of styled text runs 212 | * a way to control the advance so that runs can be drawn right to left or vertically 213 | * efficient font metrics access on both the client and the server. 214 | * mechanism for client to prepare and upload glyph representations. 215 | 216 | Lots of really tiny text runs need to be efficient. In particular, 217 | this means not shipping the text font name, font description, style 218 | name, size, etc. over again. For example, trying to use the existing 219 | `` `strokeText()` function to draw a styled text run like 220 | "hello *there* gentle reader" entails something like this: 221 | 222 | ```javascript 223 | x, y = ... // set to baseline corner 224 | ctx.strokeText('hello ', x, y) 225 | sz = ctx.measureText('hello ') 226 | ctx.font = ... // string contstant for italic font 227 | x += sz.width 228 | ctx.strokeText('there', x, y) 229 | sz = ctx.measureText('there') 230 | x += sz.width 231 | ctx.font = ... // default font string constant 232 | ctx.strokeTest(' gentle reader', x, y, ) 233 | ``` 234 | A naïve conversion of this into a series of display list commands results 235 | in context adjustment data exceeding the size of the string payload. 236 | Instead, Gojiraw provides a byte-per glyph picking from a current cache 237 | of styles along with an optimized form for character spans of uniform style. 238 | 239 | ## String Measurement 240 | We need a way to efficiently measure the size of a string where we do 241 | layout. I want to leave layout *outside* of the Gojira server. So on 242 | the client. One layout style does not fit all use cases. *Is this 243 | worthy of discussion?* 244 | 245 | Doing layout in the client requires that the client be able to 246 | efficiently measure the size of glyphs. Efficiently advancing down a 247 | text run to draw the next glyph in it requires the server to *also* be 248 | able to efficiently measure the size of glyphs. The most efficient 249 | measurement mechanism is a (hopefully) O(1) look up of the glyph and 250 | style combination's dimensions in a map of some kind. 251 | 252 | Font metrics can be large. It is desirable to not replicate this map 253 | between server and client. Further, it is desirable to share font 254 | metrics between clients. Both blink and the Plan9 font code presume 255 | that the client is responsible for loading and preparing fonts. 256 | Moreover, blink admits dynamically downloading webfonts into the 257 | client. 258 | 259 | I think we can satisfy all of these requirements like this: 260 | 261 | * Gojira server maintains a metric cache in shared memory 262 | * Gojira clients can obtain a read-only memory mapping of the metric cache 263 | * Gojira clients open font files, parse and validate them and convert them into metric and (TBD) arc or curve data. 264 | * Client ships the prepared font data to Gojira 265 | * Server validates the presented data. 266 | * Server places *the metrics* in memory shared RO with clients. 267 | * The same library on client and server can read the metrics from shared memory. 268 | * Server preps the glyph rendering data in its font cache and prepares it for use. 269 | 270 | *yet another picture here* 271 | -------------------------------------------------------------------------------- /docs/introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | This document proposes the Gojiraw API. Gojiraw is a fun portmanteau 3 | of *Go* *Ji*ffy-fast *Raw* draw. Gojiraw is intended to be a general 4 | purpose (primarily) 2D graphics API that could support a wide range of 5 | customers such as Blink, Mojo applications or the Plan9 `devdraw` 6 | interface. 7 | 8 | There are multiple possibilities for how Gojiraw can fit into a larger 9 | system. 10 | 11 | * Client is a simple Go program, the server is a binary derived from 12 | our existing codebase. The client/server split serves to validate 13 | architectual choices prior to the following applications where 14 | client/server operation is required. 15 | 16 | * Client is a [Plan 9 Port](http://swtch.com/plan9port/) application 17 | and server is a replacement for P9P's 18 | [devdraw](http://swtch.com/plan9port/man/man1/devdraw.html). 19 | 20 | * Gojiraw is a Mojo server/service sourcing events and sinking Gojiraw 21 | display lists via Mojo APIs. In this context, Blink could use Gojiraw 22 | as as UI process. 23 | 24 | ![Architecture Overview](https://rawgit.com/rjkroege/gojiraw/addDesignDocs/docs/basic-diagram.svg) 25 | 26 | Gojiraw requires an underlying system that can permit the allocation 27 | of surfaces, render an OpenGL command and source a stream of events. 28 | (With the movement away from OpenGL to Metal, Vulkan, DirectX, it 29 | seems prudent to modify the architecture to assume that Gojiraw will 30 | be structured to support multiple different system GPU 31 | interfaces.) 32 | 33 | Gojiraw provides its customers with an API similar to the 34 | JavaScript canvas API with the following extensions: 35 | 36 | * simple provisions for efficient drawing of text with a bounded number of styles 37 | * bounding box calculations 38 | * pan/zoom regions 39 | * mutable replayable groups 40 | 41 | # Literature 42 | 43 | *survey if necessary* 44 | 45 | # Basic Concepts 46 | I introduce the following basic objects and actions. 47 | 48 | * `Image` a rectangular expanse of pixels that can 49 | be the target of rasterization. GPUs render to `Image` instances. 50 | 51 | * `Screen` a single panel, CRT, etc. An `Image` can be scanned out 52 | onto a `Screen` by a display controller. Overlay capability permits 53 | multiple images scanned out on a single screen. (I argue that a 54 | (clipped) view of the same Image can be displayed on several screens 55 | but not span multiple screens.) 56 | 57 | * `Display` the union of all `Screen`s that we want to display 58 | to. 59 | 60 | * we *present* an `Image` to a `Display`. This action signifies 61 | that scan out of the `Image` should begin at the end of the 62 | next vblank. 63 | 64 | * `Enso` a single simple drawable. A stroke or member in a display 65 | list. 66 | 67 | * `Inkings` a bundle of Ensos that can be transformed and drawn 68 | as a unit. Inkings are recursive. 69 | 70 | * `Glyph` is a number specifying a single glyph outline to draw. 71 | 72 | # Principles 73 | Core principles of the API 74 | 75 | * *lazy rasterization*: all rasterization in Gojiraw can be deferred. 76 | 77 | * *non-blocking*: no Gojiraw API blocks (waiting on the display controller 78 | or GPU). If the result is not immediately available, a promise it provided 79 | in its place. 80 | 81 | # Version History 82 | 83 | 1. first draft. API provides Display, Screen, Image infrastructure and 84 | drawing only of text and bitmaps to rectangular regions. Overlays 85 | remain imprecise. 86 | 87 | -------------------------------------------------------------------------------- /experiments/cocoaview/app.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gojiraw Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cocoaview 16 | 17 | /* 18 | #cgo CFLAGS: -x objective-c 19 | #cgo LDFLAGS: -framework Cocoa 20 | 21 | #include "start.h" 22 | */ 23 | import "C" 24 | 25 | func StartApp() { 26 | C.StartApp() 27 | } 28 | -------------------------------------------------------------------------------- /experiments/cocoaview/events.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gojiraw Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cocoaview 16 | 17 | // Example: bridging Objective-C into Go. 18 | 19 | import ( 20 | "log" 21 | "unsafe" 22 | ) 23 | 24 | /* 25 | #cgo CFLAGS: -x objective-c 26 | #cgo LDFLAGS: -framework Cocoa 27 | #import 28 | #include 29 | 30 | // Make into void* to pass around. 31 | // It ought to be possible to persuade the Go compiler to build 32 | // this code dynamically somehow. 33 | inline static NSPoint 34 | getLocation(void* event) { 35 | return [(NSEvent*)event locationInWindow]; 36 | } 37 | 38 | */ 39 | import "C" 40 | 41 | // Return true if the event is handled. Callable from C. 42 | //export mouseUp 43 | func mouseUp(event unsafe.Pointer) bool { 44 | pt := C.getLocation(event) 45 | log.Printf("mouseUp at %f %f\n", pt.x, pt.y) 46 | return false 47 | } 48 | 49 | // Return true if the event is handled. Callable from C. 50 | //export mouseDown 51 | func mouseDown(event unsafe.Pointer) bool { 52 | pt := C.getLocation(event) 53 | log.Printf("mouseDown at %f %f\n", pt.x, pt.y) 54 | return false 55 | } 56 | -------------------------------------------------------------------------------- /experiments/cocoaview/goview.h: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gojiraw Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #import 16 | 17 | @interface GoView : NSView 18 | 19 | - (void)mouseDown:(NSEvent *)theEvent; 20 | - (void)mouseUp:(NSEvent *)theEvent; 21 | - (BOOL)acceptsFirstResponder; 22 | - (id)initWithFrame:(NSRect)frame; 23 | 24 | @end 25 | 26 | -------------------------------------------------------------------------------- /experiments/cocoaview/goview.m: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gojiraw Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #import "goview.h" 16 | 17 | // To support the execution of foreign code (C, C++, ObjC etc.), Go 18 | // uses the cgo tool to automatically generate interfaces for calling 19 | // into foreign code and back into go. The interface for C -> Go is in 20 | // _cgo_export.h. It needs to be included here so that this ObjC file 21 | // can call functions exported from Go. 22 | #import "_cgo_export.h" 23 | 24 | @implementation GoView 25 | 26 | - (void)mouseDown:(NSEvent *)theEvent { 27 | if (!mouseDown(theEvent)) { 28 | [[self nextResponder] mouseDown:theEvent]; 29 | } 30 | } 31 | 32 | - (void)mouseUp:(NSEvent *)theEvent { 33 | if (!mouseUp(theEvent)) { 34 | [[self nextResponder] mouseUp:theEvent]; 35 | } 36 | } 37 | 38 | - (BOOL)acceptsFirstResponder { 39 | return YES; 40 | } 41 | 42 | - (id)initWithFrame:(NSRect)frame { 43 | self = [super initWithFrame:frame]; 44 | if (self) { 45 | NSLog(@"set up the go structs"); 46 | } 47 | return self; 48 | } 49 | 50 | @end 51 | 52 | -------------------------------------------------------------------------------- /experiments/cocoaview/start.h: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gojiraw Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | int StartApp(void); -------------------------------------------------------------------------------- /experiments/cocoaview/start.m: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gojiraw Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 16 | #import 17 | #import "goview.h" 18 | 19 | int 20 | StartApp(void) { 21 | [NSAutoreleasePool new]; 22 | [NSApplication sharedApplication]; 23 | [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; 24 | 25 | id menubar = [[NSMenu new] autorelease]; 26 | id appMenuItem = [[NSMenuItem new] autorelease]; 27 | [menubar addItem:appMenuItem]; 28 | [NSApp setMainMenu:menubar]; 29 | 30 | id appMenu = [[NSMenu new] autorelease]; 31 | id appName = [[NSProcessInfo processInfo] processName]; 32 | id quitTitle = [@"Quit " stringByAppendingString:appName]; 33 | id quitMenuItem = [[[NSMenuItem alloc] initWithTitle:quitTitle 34 | action:@selector(terminate:) keyEquivalent:@"q"] autorelease]; 35 | [appMenu addItem:quitMenuItem]; 36 | [appMenuItem setSubmenu:appMenu]; 37 | 38 | NSRect winFrame = NSMakeRect(0, 0, 200, 200); 39 | 40 | NSWindow* window = [[[NSWindow alloc] 41 | initWithContentRect:winFrame 42 | styleMask:NSTitledWindowMask 43 | backing:NSBackingStoreBuffered defer:NO] 44 | autorelease]; 45 | [window cascadeTopLeftFromPoint:NSMakePoint(20,20)]; 46 | [window setTitle:appName]; 47 | [window makeKeyAndOrderFront:nil]; 48 | 49 | NSView* view = [[GoView alloc] 50 | initWithFrame:[window contentRectForFrameRect: winFrame]]; 51 | [window setContentView: view]; 52 | 53 | [NSApp activateIgnoringOtherApps:YES]; 54 | [NSApp run]; 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /experiments/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gojiraw Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "github.com/google/gojiraw/experiments/cocoaview" 19 | ) 20 | 21 | func main() { 22 | cocoaview.StartApp() 23 | } 24 | 25 | -------------------------------------------------------------------------------- /graphics/arc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gojiraw Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 16 | package graphics 17 | 18 | import ( 19 | "math" 20 | ) 21 | 22 | type Arc struct { 23 | p0, p1 *Dual 24 | // The two endpoints of the chord and the furthest point from the line 25 | // coincident with the chord form a isosceles triangle. |d| is the tangent 26 | // of the doubled angle. Roughly speaking, it's the "depth" of the arc. 27 | // 28 | // NB: It MUST be the case that -1 < d < 1. In fact, it would do well to be 29 | // smaller than 1/2 in magnitude. 30 | d float32 31 | } 32 | 33 | func (arc *Arc) Normals() (l0, l1 *Dual) { 34 | // (dx, dy) is the vector from p0 to p1. We will rotate it to get the normal 35 | // to the lines l0 and l1 by +/- 2 * atan(d) respectively. 36 | // 37 | // Why 2 * atan(d)? Great question. Let's do some geometry. 38 | // 39 | // Consider the following diagram. 40 | // 41 | // c 42 | // | 43 | // | 44 | // a_____________m_____________b 45 | // | 46 | // | 47 | // | 48 | // o 49 | // | 50 | // | 51 | // | 52 | // | 53 | // | 54 | // | 55 | // d 56 | // 57 | // Duals a, b, c and d are circumscribed by the same circle. Duals a and b 58 | // are p0 and p1 in our arc parlance. o is the center of the circle, m is the 59 | // midpoint of the chord subtended by a and b and c is the furthest point on 60 | // our arc from the chord. d is not on our arc, but is the other point of 61 | // intection on the circle of the bisector of a and b. 62 | // 63 | // We want angle(aoc). That is the angle we would have to rotate vector(co) 64 | // to get the normal at a or b. 65 | // 66 | // Now we know that atan(d) = angle(cam). Since cam forms a right triangle 67 | // angle(acm) = pi/2 - angle(cam). Since cd passes through the center of the 68 | // circle, we also that angle(dac) = pi/2. This means that the triangles cam 69 | // and cad are similar, which is handy because it means that angle(cad) = 70 | // angle(cam) = atan(d). Next, notice that triangles aod and aoc (which form 71 | // triangle cad) are both isosceles. This implies that angle(dao) = atan(d) as 72 | // well. Since angle(dac) = pi/2, we know that angle(oac) = angle(aco) = 73 | // pi/2 - atan(d). Finally, since the angles in triangle aoc must sum to pi, 74 | // we know that angle(aoc) = pi - angle(oac) - angle(aco) 75 | // = pi - 2 * (p/2 - atan(d)) 76 | // = pi - pi + 2 * atan(d) 77 | // = 2 * atan(d). 78 | dx := arc.p1.x - arc.p0.x 79 | dy := arc.p1.y - arc.p0.y 80 | 81 | // We want sin and cos of this angle for rotating. There are happen to be 82 | // cute closed forms for getting these values. 83 | sin := Sin2Atan(arc.d) 84 | cos := Cos2Atan(arc.d) 85 | 86 | // Initialize the lines with the correct orientation, but passing through the 87 | // origin. 88 | l0 = &Dual{dx*cos - dy*sin, dx*sin + dy*cos, 0.0} 89 | l1 = &Dual{-dx*cos - dy*sin, dx*sin - dy*cos, 0.0} 90 | 91 | // Adjust the lines to pass through p0, and p1. 92 | l0.w = -l0.AngularDistanceTo(arc.p0) 93 | l1.w = -l1.AngularDistanceTo(arc.p1) 94 | 95 | return l0, l1 96 | } 97 | 98 | func IsInWedge(point, n0, n1 *Dual) bool { 99 | return n0.ProjectiveDistanceTo(point) > 0 && n1.ProjectiveDistanceTo(point) > 0 100 | } 101 | 102 | func (arc *Arc) IsInWedge(point *Dual) bool { 103 | // Perhaps we should cache these? 104 | n0, n1 := arc.Normals() 105 | return IsInWedge(point, n0, n1) 106 | } 107 | 108 | func (arc *Arc) Apex() *Dual { 109 | dx := arc.p1.x - arc.p0.x 110 | dy := arc.p1.y - arc.p0.y 111 | 112 | return &Dual{arc.p0.x + 0.5*(dx-arc.d*dy), 113 | arc.p0.y + 0.5*(dy+arc.d*dx), 114 | 1.0} 115 | } 116 | 117 | type SignedVector struct { 118 | x, y float32 119 | negated bool 120 | } 121 | 122 | func (arc *Arc) SignedVectorToClosestArcPoint(point *Dual) *SignedVector { 123 | n0, n1 := arc.Normals() 124 | // Note: in the shader, we will never ask for a point outside the wedge. 125 | if !IsInWedge(point, n0, n1) { 126 | dx0 := point.x - arc.p0.x 127 | dy0 := point.y - arc.p0.y 128 | dx1 := point.x - arc.p1.x 129 | dy1 := point.y - arc.p1.y 130 | if dx0*dx0+dy0*dy0 < dx1*dx1+dy1*dy1 { 131 | return &SignedVector{dx0, dy0, false} 132 | } 133 | return &SignedVector{dx1, dy1, false} 134 | } 135 | 136 | // In the shader, this will be much simpler. What we're computing here is 137 | // the distance to the two normal lines. If we store these distances at 138 | // each of the vertices, GL will interpolate for us. 139 | d0 := n0.ProjectiveDistanceTo(point) 140 | d1 := n1.ProjectiveDistanceTo(point) 141 | 142 | // (px, py) is a vector in the dirction of the tangent at the closest point on 143 | // the circle to |point|. 144 | px := d1*n0.x - d0*n1.x 145 | py := d1*n0.y - d0*n1.y 146 | 147 | // This is the line that passes through the center of the circle and p. 148 | l0 := Dual{px, py, -(px*point.x + py*point.y)} 149 | 150 | // FIXME: the only reason for the following sqrt's is to get the angular 151 | // bisector of n0 and (px, py). Can that be obtained more cheaply? Maybe 152 | // it doesn't matter: invsqrt is pretty fast. 153 | length0 := float32(math.Hypot(float64(n0.x), float64(n0.y))) 154 | length1 := float32(math.Hypot(float64(px), float64(py))) 155 | 156 | // This is is a line (through the origin) coincident with the angular bisector 157 | // of n0 and (px, py). 158 | l1 := Dual{-py/length1 - n0.y/length0, 159 | px/length1 + n0.x/length0, 160 | 0.0} 161 | 162 | // Make it pass through p0. 163 | l1.w = -(l1.x*arc.p0.x + l1.y*arc.p0.y) 164 | 165 | // This is the closest point on the arc. 166 | arcPoint := l1.Intersection(&l0).Normalize() 167 | 168 | v := SignedVector{arcPoint.x - point.x, arcPoint.y - point.y, true} 169 | 170 | v.negated = v.x*py-v.y*px < 0 171 | if arc.d < 0 { 172 | v.negated = !v.negated 173 | } 174 | 175 | return &v 176 | } 177 | 178 | func (arc *Arc) EuclideanDistanceTo(point *Dual) float32 { 179 | v := arc.SignedVectorToClosestArcPoint(point) 180 | distance := float32(math.Hypot(float64(v.x), float64(v.y))) 181 | if v.negated { 182 | distance *= -1.0 183 | } 184 | return distance 185 | } 186 | -------------------------------------------------------------------------------- /graphics/arc_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gojiraw Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package graphics 16 | 17 | import ( 18 | "math" 19 | "testing" 20 | ) 21 | 22 | const ( 23 | FLOAT_TOL = 1.0e-5 24 | ) 25 | 26 | func AssertFloatEqual(t *testing.T, expected float32, actual float32) { 27 | delta := expected - actual 28 | if delta > FLOAT_TOL || delta < -FLOAT_TOL { 29 | t.Errorf("Expected near %f, received %f", expected, actual) 30 | } 31 | } 32 | 33 | func AssertTrue(t *testing.T, value bool) { 34 | if !value { 35 | t.Error("Expected true, got false.") 36 | } 37 | } 38 | 39 | func AssertFalse(t *testing.T, value bool) { 40 | if value { 41 | t.Error("Expected false, got true.") 42 | } 43 | } 44 | 45 | var ( 46 | ATAN_TEST_CASES = []float32{-.99, 0, 0.0001, 0.99} 47 | ) 48 | 49 | func TestSin2Atan(t *testing.T) { 50 | for _, theta := range ATAN_TEST_CASES { 51 | AssertFloatEqual(t, Sin2Atan(theta), float32(math.Sin(2.0*math.Atan(float64(theta))))) 52 | } 53 | } 54 | 55 | func TestCos2Atan(t *testing.T) { 56 | for _, theta := range ATAN_TEST_CASES { 57 | AssertFloatEqual(t, Cos2Atan(theta), float32(math.Cos(2.0*math.Atan(float64(theta))))) 58 | } 59 | } 60 | 61 | // Returns tan(0.5 * acos(d)) 62 | func TanHalfAcos(d float32) float32 { 63 | return float32(math.Tan(math.Acos(float64(d)) * 0.25)) 64 | } 65 | 66 | func TestIsInWedge(t *testing.T) { 67 | p0 := &Dual{1.0, 0.0, 1.0} 68 | p1 := &Dual{2.0, 0.0, 1.0} 69 | center := &Dual{1.5, -0.5, 1.0} 70 | 71 | c0 := p0.Intersection(center) 72 | c1 := p1.Intersection(center) 73 | 74 | cos_d := c0.AngularDistanceTo(c1) 75 | a := Arc{p0, p1, float32(math.Tan(math.Acos(float64(cos_d)) * 0.25))} 76 | 77 | type TestCase struct { 78 | p *Dual 79 | expected bool 80 | } 81 | 82 | testCases := []TestCase{ 83 | {&Dual{1.5, 0.5, 1.0}, true}, 84 | {&Dual{1.5, -0.2, 1.0}, true}, 85 | {&Dual{1.5, -1.5, 1.0}, false}, 86 | {&Dual{3.5, 0.5, 1.0}, false}, 87 | {&Dual{-1.5, 0.5, 1.0}, false}} 88 | 89 | for i, test := range testCases { 90 | if a.IsInWedge(test.p) != test.expected { 91 | t.Errorf("Case %d Failed: p(%f, %f), is in = %t\n", 92 | i, test.p.x, test.p.y, a.IsInWedge(test.p)) 93 | } 94 | } 95 | } 96 | 97 | func TestEuclideanDistanceTo(t *testing.T) { 98 | p0 := &Dual{1.0, 0.0, 1.0} 99 | p1 := &Dual{2.0, 0.0, 1.0} 100 | center := &Dual{1.5, -0.5, 1.0} 101 | 102 | c0 := p0.Intersection(center) 103 | c1 := p1.Intersection(center) 104 | 105 | cos_d := c0.AngularDistanceTo(c1) 106 | radius := float32(math.Hypot(float64(c0.x), float64(c0.y))) 107 | cos_d /= radius * radius 108 | arc := Arc{p0, p1, float32(math.Tan(math.Acos(float64(cos_d)) * 0.25))} 109 | apex := arc.Apex() 110 | 111 | type TestCase struct { 112 | p *Dual 113 | expectedDistance float32 114 | } 115 | 116 | exterior := Dual{1.7872, 5.22, 1.0} 117 | interior := Dual{1.26, 0.0115, 1.0} 118 | 119 | testCases := []TestCase{ 120 | {apex, 0.0}, 121 | {&Dual{apex.x, apex.y + 1.0, 1.0}, 1.0}, 122 | {&Dual{apex.x, apex.y - 0.2, 1.0}, -0.2}, 123 | {&Dual{apex.x, apex.y + 10.0, 1.0}, 10.0}, 124 | {&exterior, float32(math.Hypot(float64(exterior.x-center.x), float64(exterior.y-center.y))) - radius}, 125 | {&interior, float32(math.Hypot(float64(interior.x-center.x), float64(interior.y-center.y))) - radius}, 126 | {p0, 0.0}, 127 | {p1, 0.0}} 128 | 129 | // Crap is not normalized. Is of limited use.. 130 | for _, test := range testCases { 131 | AssertFloatEqual(t, test.expectedDistance, arc.EuclideanDistanceTo(test.p)) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /graphics/dual.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gojiraw Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 16 | package graphics 17 | 18 | // Represents both projective points and lines. 19 | type Dual struct { 20 | x, y, w float32 21 | } 22 | 23 | func (d *Dual) Bisector(other *Dual) *Dual { 24 | sx := d.x + other.x 25 | sy := d.y + other.y 26 | dx := other.x - d.x 27 | dy := other.y - d.y 28 | return &Dual{dx, dy, -0.5 * (dx*sx + dy*sy)} 29 | } 30 | 31 | func (d *Dual) Normalize() *Dual { 32 | return &Dual{d.x / d.w, d.y / d.w, 1} 33 | } 34 | 35 | func (a *Dual) ProjectiveDistanceTo(b *Dual) float32 { 36 | return a.x*b.x + a.y*b.y + a.w*b.w 37 | } 38 | 39 | func (a *Dual) AngularDistanceTo(b *Dual) float32 { 40 | return a.x*b.x + a.y*b.y 41 | } 42 | 43 | func (d0 *Dual) Intersection(d1 *Dual) *Dual { 44 | return &Dual{d0.y*d1.w - d0.w*d1.y, 45 | d0.w*d1.x - d0.x*d1.w, 46 | d0.x*d1.y - d0.y*d1.x} 47 | } 48 | -------------------------------------------------------------------------------- /graphics/float32.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gojiraw Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package graphics 16 | 17 | func MaxF(x1, x2 float32) float32 { 18 | if x1 > x2 { 19 | return x1 20 | } 21 | return x2 22 | } 23 | 24 | func MinF(x1, x2 float32) float32 { 25 | if x1 < x2 { 26 | return x1 27 | } 28 | return x2 29 | } 30 | 31 | // Returns sin(2 * atan(d)) 32 | func Sin2Atan(d float32) float32 { 33 | return 2.0 * d / (1.0 + d*d) 34 | } 35 | 36 | // Returns cos(2 * atan(d)) 37 | func Cos2Atan(d float32) float32 { 38 | return (1.0 - d*d) / (1.0 + d*d) 39 | } 40 | -------------------------------------------------------------------------------- /graphics/gl.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gojiraw Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package graphics 16 | 17 | import ( 18 | "fmt" 19 | "image/color" 20 | "log" 21 | 22 | "github.com/go-gl/gl" 23 | "github.com/go-gl/glu" 24 | ) 25 | 26 | const ( 27 | // Sort these alphabetically or vollick will hunt you down. 28 | DRAW_OP_COLOR = iota 29 | DRAW_OP_QUADS 30 | ) 31 | 32 | func CheckForGLErrors() { 33 | errcode := gl.GetError() 34 | if errcode != gl.NO_ERROR { 35 | // The error is non-nil here if we can't get an error string. 36 | s, err := glu.ErrorString(errcode) 37 | if err != nil { 38 | log.Panic("GLError(string): ", s) 39 | } else { 40 | log.Panicf("GLError(code): %x", errcode) 41 | } 42 | } 43 | } 44 | 45 | const ( 46 | // The default vertex shader takes 2D points and scales them to fit in 47 | // the viewport. 48 | defaultVertexShader = ` 49 | #version 400 50 | 51 | // The x and y components represent the reciprocal of the width and height of the 52 | // viewport. We've used the reciprocal to avoid unnecessary division. 53 | uniform vec2 u_Viewport; 54 | 55 | // The default shader only supports 2D points. 56 | in vec2 in_Position; 57 | 58 | void main() 59 | { 60 | gl_Position = vec4(2.0 * in_Position.x * u_Viewport.x - 1.0, 61 | -(2.0 * in_Position.y * u_Viewport.y - 1.0), 62 | 0.0, 1.0); 63 | }` // defaultVertexShader 64 | 65 | // The default fragment shader simply passes along a uniform color. 66 | defaultFragmentShader = ` 67 | #version 400 68 | 69 | uniform vec4 u_Color; 70 | out vec4 out_Color; 71 | 72 | void main() 73 | { 74 | out_Color = u_Color; 75 | }` // defaultFragmentShader 76 | 77 | ) 78 | 79 | func CreateDefaultShaders() (program gl.Program) { 80 | vertex_shader := gl.CreateShader(gl.VERTEX_SHADER) 81 | vertex_shader.Source(defaultVertexShader) 82 | vertex_shader.Compile() 83 | fmt.Println(vertex_shader.GetInfoLog()) 84 | defer vertex_shader.Delete() 85 | 86 | fragment_shader := gl.CreateShader(gl.FRAGMENT_SHADER) 87 | fragment_shader.Source(defaultFragmentShader) 88 | fragment_shader.Compile() 89 | fmt.Println(fragment_shader.GetInfoLog()) 90 | defer fragment_shader.Delete() 91 | 92 | program = gl.CreateProgram() 93 | program.AttachShader(vertex_shader) 94 | program.AttachShader(fragment_shader) 95 | 96 | program.BindFragDataLocation(0, "out_Color") 97 | program.Link() 98 | program.Use() 99 | return 100 | } 101 | 102 | // TODO(vollick): We might just want to store a gob encoder here? Do we need to 103 | // have tighter control of the binary rep so we can pass data directly to card. 104 | 105 | // TODO(vollick): We need to consider spatial queries, mutability and display 106 | // list optimization. I am not at all convinced that this representation is 107 | // ideal for all of these purposes. 108 | type DisplayList struct { 109 | opCodes []uint8 110 | integers []uint32 111 | floats []float32 112 | bytes []uint8 113 | cur_integer, cur_float, cur_byte int 114 | W, H float32 115 | cur_point_size float32 116 | } 117 | 118 | func (dl *DisplayList) SetColor(c color.RGBA) { 119 | dl.opCodes = append(dl.opCodes, DRAW_OP_COLOR) 120 | dl.bytes = append(dl.bytes, c.R, c.G, c.B, c.A) 121 | } 122 | 123 | func (dl *DisplayList) SetPointSize(s float32) { 124 | dl.cur_point_size = s 125 | } 126 | 127 | func (dl *DisplayList) DrawPoints(ps []Pointf) { 128 | // TODO(vollick): We could, if we wanted, use the "unsafe" package here to 129 | // add the raw bytes. 130 | quads := make([][4]Pointf, len(ps)) 131 | delta := 0.5 * dl.cur_point_size 132 | for i, p := range ps { 133 | quads[i] = [...]Pointf{ 134 | Pointf{p.X - delta, p.Y - delta}, 135 | Pointf{p.X + delta, p.Y - delta}, 136 | Pointf{p.X + delta, p.Y + delta}, 137 | Pointf{p.X - delta, p.Y + delta}} 138 | } 139 | dl.DrawQuads(quads) 140 | } 141 | 142 | func (dl *DisplayList) DrawQuads(qs [][4]Pointf) { 143 | dl.opCodes = append(dl.opCodes, DRAW_OP_QUADS) 144 | dl.integers = append(dl.integers, uint32(len(qs))) 145 | for _, q := range qs { 146 | for _, p := range q { 147 | dl.W = MaxF(dl.W, p.X) 148 | dl.H = MaxF(dl.H, p.Y) 149 | dl.floats = append(dl.floats, p.X, p.Y) 150 | } 151 | } 152 | } 153 | 154 | func (dl *DisplayList) Draw(program *gl.Program, width, height float32) { 155 | viewportUniform := program.GetUniformLocation("u_Viewport") 156 | viewportUniform.Uniform2f(1.0/width, 1.0/height) 157 | 158 | // TODO(vollick): Can we do something like this in parallel? 159 | dl.cur_integer = 0 160 | dl.cur_float = 0 161 | dl.cur_byte = 0 162 | for _, op := range dl.opCodes { 163 | switch op { 164 | case DRAW_OP_COLOR: 165 | dl.DoColor(program) 166 | case DRAW_OP_QUADS: 167 | dl.DoQuads(program) 168 | } 169 | CheckForGLErrors() 170 | } 171 | } 172 | 173 | func (dl *DisplayList) DoColor(program *gl.Program) { 174 | colorLocation := program.GetUniformLocation("u_Color") 175 | colorLocation.Uniform4f( 176 | float32(dl.bytes[dl.cur_byte])/255, 177 | float32(dl.bytes[dl.cur_byte+1])/255, 178 | float32(dl.bytes[dl.cur_byte+2])/255, 179 | float32(dl.bytes[dl.cur_byte+3])/255) 180 | dl.cur_byte += 4 181 | CheckForGLErrors() 182 | } 183 | 184 | func (dl *DisplayList) DoQuads(program *gl.Program) { 185 | num_quads := dl.integers[dl.cur_integer] 186 | dl.cur_integer++ 187 | quads := []float32{} 188 | 189 | // FIXME: we shouldn't recreate this every time the display list is 190 | // drawn. 191 | for i := uint32(0); i < num_quads; i++ { 192 | quads = append(quads, dl.floats[dl.cur_float:dl.cur_float+6]...) 193 | quads = append(quads, dl.floats[dl.cur_float:dl.cur_float+2]...) 194 | quads = append(quads, dl.floats[dl.cur_float+4:dl.cur_float+6]...) 195 | quads = append(quads, dl.floats[dl.cur_float+6:dl.cur_float+8]...) 196 | dl.cur_float += 8 197 | } 198 | 199 | // FIXME: this is atrocious. We need to retain these objects rather than 200 | // creating and destroying them constantly. 201 | vao := gl.GenVertexArray() 202 | vao.Bind() 203 | 204 | vbo := gl.GenBuffer() 205 | vbo.Bind(gl.ARRAY_BUFFER) 206 | 207 | gl.BufferData(gl.ARRAY_BUFFER, len(quads)*4, quads, gl.STATIC_DRAW) 208 | 209 | positionAttrib := program.GetAttribLocation("in_Position") 210 | positionAttrib.AttribPointer(2, gl.FLOAT, false, 0, nil) 211 | positionAttrib.EnableArray() 212 | defer positionAttrib.DisableArray() 213 | 214 | gl.DrawArrays(gl.TRIANGLES, 0, len(quads)/2) 215 | 216 | CheckForGLErrors() 217 | } 218 | -------------------------------------------------------------------------------- /graphics/pointf.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gojiraw Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package graphics 16 | 17 | // Assorted primitives inspired by the image.Point and image.Rect 18 | // code from Go. 19 | 20 | import ( 21 | "image" 22 | "strconv" 23 | ) 24 | 25 | // A Pointf is an X, Y coordinate pair. The axes increase right and down. 26 | // TODO(rjkroege|ianvollick): Write conversion Pointf <-> Dual as needed. 27 | type Pointf struct { 28 | X, Y float32 29 | } 30 | 31 | // String returns a string representation of p like "(3,4)". 32 | func (p Pointf) String() string { 33 | return "(" + strconv.FormatFloat(float64(p.X), 'f', -1, 32) + "," + strconv.FormatFloat(float64(p.Y), 'f', -1, 32) + ")" 34 | } 35 | 36 | // Add returns the vector p+q. 37 | func (p Pointf) Add(q Pointf) Pointf { 38 | return Pointf{p.X + q.X, p.Y + q.Y} 39 | } 40 | 41 | // Sub returns the vector p-q. 42 | func (p Pointf) Sub(q Pointf) Pointf { 43 | return Pointf{p.X - q.X, p.Y - q.Y} 44 | } 45 | 46 | // Mul returns the vector p*k. 47 | func (p Pointf) Mul(k float32) Pointf { 48 | return Pointf{p.X * k, p.Y * k} 49 | } 50 | 51 | // Div returns the vector p/k. 52 | func (p Pointf) Div(k float32) Pointf { 53 | return Pointf{p.X / k, p.Y / k} 54 | } 55 | 56 | // In reports whether p is in r. 57 | func (p Pointf) In(r Rectanglef) bool { 58 | return r.Min.X <= p.X && p.X < r.Max.X && 59 | r.Min.Y <= p.Y && p.Y < r.Max.Y 60 | } 61 | 62 | // Eq reports whether p and q are equal. 63 | func (p Pointf) Eq(q Pointf) bool { 64 | return p.X == q.X && p.Y == q.Y 65 | } 66 | 67 | // ZP is the zero Pointf. 68 | var ZP Pointf 69 | 70 | // Ptf is shorthand for Pointf{X, Y}. 71 | func Ptf(X, Y float32) Pointf { 72 | return Pointf{X, Y} 73 | } 74 | 75 | // Make a Pointf from an integer point 76 | func Ptfi(p image.Point) Pointf { 77 | return Pointf{float32(p.X), float32(p.Y)} 78 | } 79 | -------------------------------------------------------------------------------- /graphics/rectanglef.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gojiraw Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package graphics 16 | 17 | // A Rectanglef contains the points with Min.X <= X < Max.X, Min.Y <= Y < Max.Y. 18 | // It is well-formed if Min.X <= Max.X and likewise for Y. Pointfs are always 19 | // well-formed. A rectangle's methods always return well-formed outputs for 20 | // well-formed inputs. 21 | type Rectanglef struct { 22 | Min, Max Pointf 23 | } 24 | 25 | // String returns a string representation of r like "(3,4)-(6,5)". 26 | func (r Rectanglef) String() string { 27 | return r.Min.String() + "-" + r.Max.String() 28 | } 29 | 30 | // Dx returns r's width. 31 | func (r Rectanglef) Dx() float32 { 32 | return r.Max.X - r.Min.X 33 | } 34 | 35 | // Dy returns r's height. 36 | func (r Rectanglef) Dy() float32 { 37 | return r.Max.Y - r.Min.Y 38 | } 39 | 40 | // Size returns r's width and height. 41 | func (r Rectanglef) Size() Pointf { 42 | return Pointf{ 43 | r.Max.X - r.Min.X, 44 | r.Max.Y - r.Min.Y, 45 | } 46 | } 47 | 48 | // Add returns the rectangle r translated by p. 49 | func (r Rectanglef) Add(p Pointf) Rectanglef { 50 | return Rectanglef{ 51 | Pointf{r.Min.X + p.X, r.Min.Y + p.Y}, 52 | Pointf{r.Max.X + p.X, r.Max.Y + p.Y}, 53 | } 54 | } 55 | 56 | // Sub returns the rectangle r translated by -p. 57 | func (r Rectanglef) Sub(p Pointf) Rectanglef { 58 | return Rectanglef{ 59 | Pointf{r.Min.X - p.X, r.Min.Y - p.Y}, 60 | Pointf{r.Max.X - p.X, r.Max.Y - p.Y}, 61 | } 62 | } 63 | 64 | // Intersect returns the largest rectangle contained by both r and s. If the 65 | // two rectangles do not overlap then the zero rectangle will be returned. 66 | func (r Rectanglef) Intersect(s Rectanglef) Rectanglef { 67 | if r.Min.X < s.Min.X { 68 | r.Min.X = s.Min.X 69 | } 70 | if r.Min.Y < s.Min.Y { 71 | r.Min.Y = s.Min.Y 72 | } 73 | if r.Max.X > s.Max.X { 74 | r.Max.X = s.Max.X 75 | } 76 | if r.Max.Y > s.Max.Y { 77 | r.Max.Y = s.Max.Y 78 | } 79 | if r.Min.X > r.Max.X || r.Min.Y > r.Max.Y { 80 | return ZR 81 | } 82 | return r 83 | } 84 | 85 | // Union returns the smallest rectangle that contains both r and s. 86 | func (r Rectanglef) Union(s Rectanglef) Rectanglef { 87 | if r.Min.X > s.Min.X { 88 | r.Min.X = s.Min.X 89 | } 90 | if r.Min.Y > s.Min.Y { 91 | r.Min.Y = s.Min.Y 92 | } 93 | if r.Max.X < s.Max.X { 94 | r.Max.X = s.Max.X 95 | } 96 | if r.Max.Y < s.Max.Y { 97 | r.Max.Y = s.Max.Y 98 | } 99 | return r 100 | } 101 | 102 | // Empty reports whether the rectangle contains no points. 103 | func (r Rectanglef) Empty() bool { 104 | return r.Min.X >= r.Max.X || r.Min.Y >= r.Max.Y 105 | } 106 | 107 | // Eq reports whether r and s are equal. 108 | func (r Rectanglef) Eq(s Rectanglef) bool { 109 | return r.Min.X == s.Min.X && r.Min.Y == s.Min.Y && 110 | r.Max.X == s.Max.X && r.Max.Y == s.Max.Y 111 | } 112 | 113 | // Overlaps reports whether r and s have a non-empty intersection. 114 | func (r Rectanglef) Overlaps(s Rectanglef) bool { 115 | return r.Min.X < s.Max.X && s.Min.X < r.Max.X && 116 | r.Min.Y < s.Max.Y && s.Min.Y < r.Max.Y 117 | } 118 | 119 | // In reports whether every point in r is in s. 120 | func (r Rectanglef) In(s Rectanglef) bool { 121 | if r.Empty() { 122 | return true 123 | } 124 | // Note that r.Max is an exclusive bound for r, so that r.In(s) 125 | // does not require that r.Max.In(s). 126 | return s.Min.X <= r.Min.X && r.Max.X <= s.Max.X && 127 | s.Min.Y <= r.Min.Y && r.Max.Y <= s.Max.Y 128 | } 129 | 130 | // Canon returns the canonical version of r. The returned rectangle has minimum 131 | // and maximum coordinates swapped if necessary so that it is well-formed. 132 | func (r Rectanglef) Canon() Rectanglef { 133 | if r.Max.X < r.Min.X { 134 | r.Min.X, r.Max.X = r.Max.X, r.Min.X 135 | } 136 | if r.Max.Y < r.Min.Y { 137 | r.Min.Y, r.Max.Y = r.Max.Y, r.Min.Y 138 | } 139 | return r 140 | } 141 | 142 | // ZR is the zero Rectanglef. 143 | var ZR Rectanglef 144 | 145 | // Rect is shorthand for Rectanglef{Pt(x0, y0), Pt(x1, y1)}. 146 | func Rect(x0, y0, x1, y1 float32) Rectanglef { 147 | if x0 > x1 { 148 | x0, x1 = x1, x0 149 | } 150 | if y0 > y1 { 151 | y0, y1 = y1, y0 152 | } 153 | return Rectanglef{Pointf{x0, y0}, Pointf{x1, y1}} 154 | } 155 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gojiraw Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "log" 19 | 20 | "github.com/google/gojiraw/window" 21 | "github.com/go-gl/glfw3/v3.0/glfw" 22 | ) 23 | 24 | func main() { 25 | // Initialize glfw. 26 | if !glfw.Init() { 27 | log.Panic("Couldn't initialize glfw3") 28 | return 29 | } 30 | 31 | defer glfw.Terminate() 32 | 33 | width := 256 34 | height := 256 35 | window := window.NewWindow(width, height) 36 | 37 | // This will block until the window is closed. 38 | window.Open() 39 | } 40 | -------------------------------------------------------------------------------- /window/window.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gojiraw Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 16 | package window 17 | 18 | import ( 19 | "image" 20 | "log" 21 | 22 | "github.com/google/gojiraw/content" 23 | "github.com/google/gojiraw/graphics" 24 | "github.com/go-gl/gl" 25 | 26 | glfw "github.com/go-gl/glfw3/v3.0/glfw" 27 | ) 28 | 29 | // Stores the current mouse pointer position. 30 | type Mousepointer struct { 31 | x, y int 32 | buttonmask uint32 33 | } 34 | 35 | // TODO: when this gets big, it might want to be a package. 36 | type Window struct { 37 | width, height uint32 38 | frame *content.Frame 39 | pointer Mousepointer 40 | 41 | // Position of the Frame's origin in the viewport. Starts as 0, 0 where the 42 | // Window (aka viewport) will show the upper left corner of the content.Frame. 43 | // rjk presumes that positive y extends down the page and positive x increase 44 | // towards the right. As a result, x, y are <= 0. 45 | // 46 | // rjk: T S cs = ws for Translation and Scale and cs is in content coordinates. Verify. 47 | x, y float32 48 | 49 | // Width, height of the frame. Must start the same size as the Window. 50 | fw, fh float32 51 | 52 | // TODO(rjkroege): When the mouse wheel handling is rational and actually 53 | // ships us deltas, then we can remove this code. 54 | previous_absolute_displacement int 55 | 56 | // Event handling interface 57 | ev content.EventHandler 58 | } 59 | 60 | func NewWindow(width int, height int) *Window { 61 | c := content.NewFrame() 62 | return &Window{uint32(width), uint32(height), c, Mousepointer{0, 0, 0}, 0.0, 0.0, 63 | float32(width), float32(height), 0, c} 64 | } 65 | 66 | func (window *Window) RunMessageLoop(w *glfw.Window, program *gl.Program) { 67 | gl.GetError() 68 | for !w.ShouldClose() { 69 | // TODO(rjkroege): full generality: provide the transform to bring the Frame into 70 | // Window coordinates and the width and height. 71 | window.fw, window.fh = window.frame.Draw(window.x, window.y, float32(window.width), float32(window.height), program) 72 | w.SwapBuffers() 73 | glfw.PollEvents() 74 | } 75 | } 76 | 77 | // Based on https://raw.github.com/go-gl/examples/master/glfw/simplewindow 78 | func (window *Window) Open() { 79 | glfw.WindowHint(glfw.ContextVersionMajor, 4) 80 | glfw.WindowHint(glfw.ContextVersionMinor, 1) 81 | glfw.WindowHint(glfw.OpenglForwardCompatible, glfw.True) 82 | glfw.WindowHint(glfw.OpenglProfile, glfw.OpenglCoreProfile) 83 | 84 | // TODO: what's go style here? How do you get clang-format for go? 85 | // TODO(rjkroege): sizes should be uints 86 | glfwWindow, err := glfw.CreateWindow(int(window.width), int(window.height), "Testing", nil, nil) 87 | 88 | if err != nil { 89 | log.Panic(err) 90 | } 91 | 92 | defer glfwWindow.Destroy() 93 | 94 | glfwWindow.MakeContextCurrent() 95 | 96 | // Apparantly, this enables vsync? 97 | glfw.SwapInterval(1) 98 | 99 | gl.Init() 100 | 101 | glfwWindow.SetSizeCallback(func(_ *glfw.Window, w, h int) { 102 | window.onResize(w, h) 103 | }) 104 | 105 | glfwWindow.SetMouseButtonCallback(func(_ *glfw.Window, button glfw.MouseButton, action glfw.Action, _ glfw.ModifierKey) { 106 | window.onMouseBtn(button, action) 107 | }) 108 | 109 | // TODO(rjkroege): Figure out how these events should be dealt with. 110 | // glfw.SetMouseWheelCallback(func(d int) { 111 | // window.onMouseWheel(d) 112 | // }) 113 | 114 | // glfw.SetKeyCallback(func(k, s int) { 115 | // window.onKey(k, s) 116 | // }) 117 | 118 | // glfw.SetCharCallback(func(k, s int) { 119 | // window.onChar(k, s) 120 | // }) 121 | 122 | // TODO(vollick): Passing around one program like this is a stopgap. We 123 | // should really be initializing our shader library here. 124 | program := graphics.CreateDefaultShaders() 125 | defer program.Delete() 126 | 127 | glfwWindow.SetCursorPositionCallback(func(_ *glfw.Window, x, y float64) { 128 | window.onMousePos(int(x), int(y)) 129 | }) 130 | 131 | window.RunMessageLoop(glfwWindow, &program) 132 | } 133 | 134 | func (window *Window) onResize(w, h int) { 135 | window.width = uint32(w) 136 | window.height = uint32(h) 137 | log.Printf("Resize %d %d", window.width, window.height) 138 | } 139 | 140 | func (window *Window) onMouseBtn(button glfw.MouseButton, action glfw.Action) { 141 | state := uint32(action) 142 | if button < 0 || button > 31 || state < 0 || state > 1 { 143 | log.Fatal("button/state values from glfw are silly: ", button, state) 144 | } 145 | 146 | b := uint32(button) 147 | 148 | // log.Printf("onMouseButton. state = %d, button = %d", state, b) 149 | p := window.mousePositionInFrame() 150 | if state == 1 { 151 | window.pointer.buttonmask |= 1 << b 152 | window.ev.Mousedown(p, b, window.pointer.buttonmask) 153 | } else { 154 | window.pointer.buttonmask &= ^(1 << b) 155 | window.ev.Mouseup(p, b, window.pointer.buttonmask) 156 | } 157 | } 158 | 159 | // Get the position of the mouse in the coordinates of the frame. 160 | // The sub is right for y-axis but since we can't scroll x-axis. 161 | func (w *Window) mousePositionInFrame() image.Point { 162 | p := image.Pt(w.pointer.x, w.pointer.y) 163 | p = p.Sub(image.Pt(int(w.x), int(w.y))) 164 | return p 165 | } 166 | 167 | // Mini-essay on the way of scrolling. In scrolling, there are two 168 | // conceptual entities: the viewport and the contents. 169 | // 170 | // I maintain that scrolling should be handled by the viewport. The 171 | // contents should provide the capability of drawing an arbitrary 172 | // subrectangle of itself. But the choice of subrectangle is managed by 173 | // the viewport. 174 | // 175 | // The principle here is to handle the event as "close" to where it 176 | // arrives as possible. In particular: scrolling will happen in the 177 | // browser. 178 | func (window *Window) onMouseWheel(absolute_displacement int) { 179 | log.Printf("mouse wheel abs: %d\n", absolute_displacement) 180 | 181 | // TODO(rjkroege): displacement is stupid because of glfw issue. Fix. 182 | // delta is not a delta. I am instead interested in the difference. 183 | delta := absolute_displacement - window.previous_absolute_displacement 184 | window.previous_absolute_displacement = absolute_displacement 185 | // log.Printf("mouse wheel delta: %d\n", delta) 186 | 187 | dx := float32(delta) 188 | // Scrolling is currently only in one dimension because of glfw limitation. 189 | // TODO(rjkroege): enable two-dimensional scrolling. 190 | 191 | p := window.mousePositionInFrame() 192 | if window.ev.Wheel(p, window.pointer.buttonmask, dx, 0, 0) != content.EVD_PREVDEF { 193 | // window.x = content.Max(window.width - window.fw, window.x + float32(delta.x)) 194 | // Consider putting Max in some kind of base-like class. 195 | window.y = graphics.MaxF(float32(window.height)-window.fh, window.y+dx) 196 | 197 | // Can't scroll before the start of the content area (need x limit in the future.) 198 | if window.y > 0.0 { 199 | window.y = 0.0 200 | } 201 | 202 | log.Printf("window.y is max of %f %f\n", float32(window.height)-window.fh, window.y+float32(delta)) 203 | 204 | if window.y >= 0.0 { 205 | log.Print("i have the scrolling sign backwards") 206 | } 207 | } 208 | } 209 | 210 | // TODO(rjkroege): Add support for delivering of key events. 211 | func (window *Window) onKey(key, state int) { 212 | log.Printf("key: %d, %d\n", key, state) 213 | } 214 | 215 | func (window *Window) onChar(key, state int) { 216 | log.Printf("char: %d, %d\n", key, state) 217 | } 218 | 219 | func (window *Window) onMousePos(x, y int) { 220 | window.pointer.x = x 221 | window.pointer.y = y 222 | 223 | p := window.mousePositionInFrame() 224 | 225 | // TODO(rjkroege): filter/collapse/schedule the events as desirable. 226 | window.ev.Mousemove(p, window.pointer.buttonmask) 227 | } 228 | -------------------------------------------------------------------------------- /window/window_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gojiraw Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package window 16 | 17 | import ( 18 | "github.com/google/gojiraw/content" 19 | "github.com/rjkroege/wikitools/testhelpers" 20 | "image" 21 | "testing" 22 | ) 23 | 24 | // Mock event handler. 25 | type mockeventhandler struct { 26 | } 27 | 28 | func (f *mockeventhandler) Mousedown(pt image.Point, button, buttons uint32) uint32 { 29 | return content.EVD_NON 30 | } 31 | 32 | func (f *mockeventhandler) Mouseup(pt image.Point, button, buttons uint32) uint32 { 33 | return content.EVD_NON 34 | } 35 | 36 | func (f *mockeventhandler) Mousemove(pt image.Point, buttons uint32) uint32 { 37 | return content.EVD_NON 38 | } 39 | 40 | func (f *mockeventhandler) Wheel(pt image.Point, buttons uint32, dx, dy, dz float32) uint32 { 41 | return content.EVD_NON 42 | } 43 | 44 | func Test_windowCreation(t *testing.T) { 45 | w := new(Window) 46 | w.ev = new(mockeventhandler) 47 | testhelpers.AssertInt(t, 0, int(w.width)) 48 | testhelpers.AssertInt(t, 0, int(w.height)) 49 | testhelpers.AssertInt(t, 0, int(w.width)) 50 | testhelpers.AssertInt(t, 0, int(w.pointer.x)) 51 | testhelpers.AssertInt(t, 0, int(w.pointer.y)) 52 | testhelpers.AssertInt(t, 0, int(w.pointer.buttonmask)) 53 | } 54 | 55 | func Test_onMouseBtn(t *testing.T) { 56 | w := new(Window) 57 | w.ev = new(mockeventhandler) 58 | 59 | testhelpers.AssertInt(t, 1, content.MOUSE_BUTTON_LEFT) 60 | testhelpers.AssertInt(t, 2, content.MOUSE_BUTTON_MIDDLE) 61 | testhelpers.AssertInt(t, 4, content.MOUSE_BUTTON_RIGHT) 62 | 63 | w.onMouseBtn(0, 1) 64 | testhelpers.AssertInt(t, int(w.pointer.buttonmask), content.MOUSE_BUTTON_LEFT) 65 | w.onMouseBtn(0, 0) 66 | testhelpers.AssertInt(t, int(w.pointer.buttonmask), content.MOUSE_BUTTON_NONE) 67 | 68 | w.onMouseBtn(1, 1) 69 | testhelpers.AssertInt(t, int(w.pointer.buttonmask), content.MOUSE_BUTTON_MIDDLE) 70 | w.onMouseBtn(1, 0) 71 | testhelpers.AssertInt(t, int(w.pointer.buttonmask), content.MOUSE_BUTTON_NONE) 72 | 73 | w.onMouseBtn(2, 1) 74 | testhelpers.AssertInt(t, int(w.pointer.buttonmask), content.MOUSE_BUTTON_RIGHT) 75 | w.onMouseBtn(2, 0) 76 | testhelpers.AssertInt(t, int(w.pointer.buttonmask), content.MOUSE_BUTTON_NONE) 77 | } 78 | 79 | func Test_onMousePos(t *testing.T) { 80 | w := new(Window) 81 | w.ev = new(mockeventhandler) 82 | 83 | w.onMousePos(1, 3) 84 | testhelpers.AssertInt(t, 1, int(w.pointer.x)) 85 | testhelpers.AssertInt(t, 3, int(w.pointer.y)) 86 | } 87 | --------------------------------------------------------------------------------