CStyle Plugins
Plugins add the ability to choose what parts of the HTML/CSS spec you add to your application. If you are trying to keep compile sizes small you can remove as many as you need to reach your target size. Here we will go over the basics of how they work and how to use them.
The difference between plugins and transformers is, plugins have a order when they need to be executed and run after the state has been initialised. Transformers on the other hand require nothing but the DOM and the init style definitions. Transformers are used to transform things like an ul tag into a simpler div structure and plugins are used mainly to compute styles outside of the main function (see flex, inline, etc...).
1type Plugin struct {
2 Selector func(*element.Node) bool
3 Level int
4 Handler func(*element.Node, *map[string]element.State)
5}
A plugin is a struct defined in CStyle, in contains three properties:
- Selector
- A function that determines if the plugin should run.
- Level
- Level of priority in which to execute
- All library level should be between 0 and 2
- All others should be greater than 2
- Handler
- Callback function that provides a pointer to a element that matches the properties of Styles
# AddPlugin?(go)
Add Plugin is the CStyle function that add the plugin to the top level cstyle.Plugins array where it is used within the ComputeNodeStyle
function.
# Usage
1css.AddPlugin(block.Init())
The first step in processing the plugins before running there handler functions is to sort them by their levels. We need to sort the plugins by their level because in the example of flex, it relys on a parent element being block position so it can compute styles based of the positioning of its parent elements. If flex was ran before block then it would have nothing to build apon. This is also the reason that if you are building a custom plugin it is reccomended to keep the level above 2 as anything after 2 will have the assumed styles to build apon.
// Sorting the array by the Level field sort.Slice(plugins, func(i, j int) bool { return plugins[i].Level < plugins[j].Level })
After we have the sorted plugins we can check if the current element matches the Styles
of the plugin. The matching is a all of nothing matching system, if one property is missing then the plugin wil not be ran. If it does match then a pointer to the element.Node
(n) is passed to the handler.
for _, v := range plugins { matches := true for name, value := range v.Styles { if styleMap[name] != value && !(value == "*") { matches = false } } if matches { v.Handler(n) } }
# plugins/block
Here is the code for the block styling plugin, it is recommended to keep this standard format.
All other plugins can be found in the cstyle/plugins folder
Error reading file: open /Users/masonwright/Desktop/gui/cstyle/plugins/block/main.go: no such file or directory
1package cstyle
2
3import (
4 "fmt"
5 "gui/border"
6 "gui/color"
7 "gui/element"
8 "gui/font"
9 "gui/parser"
10 "gui/utils"
11 "os"
12 "sort"
13 "strconv"
14 "strings"
15
16 imgFont "golang.org/x/image/font"
17)
18
19// !TODO: Make a fine selector to target tags and if it has children or not etc
20// + could copy the transformers but idk
21type Plugin struct {
22 Selector func(*element.Node) bool
23 Level int
24 Handler func(*element.Node, *map[string]element.State)
25}
26
27type Transformer struct {
28 Selector func(*element.Node) bool
29 Handler func(*element.Node, *CSS) *element.Node
30}
31
32type CSS struct {
33 Width float32
34 Height float32
35 StyleSheets []map[string]map[string]string
36 Plugins []Plugin
37 Transformers []Transformer
38 Document *element.Node
39 Fonts map[string]imgFont.Face
40}
41
42func (c *CSS) Transform(n *element.Node) *element.Node {
43 for _, v := range c.Transformers {
44 if v.Selector(n) {
45 n = v.Handler(n, c)
46 }
47 }
48
49 for i := 0; i < len(n.Children); i++ {
50 tc := c.Transform(n.Children[i])
51 // Removing this causes text to break, what does this do??????
52 // its because we are using the dom methods to inject on text, not like ulol, prob need to get those working bc they effect user dom as well
53 // todo: dom fix, inline-block, text align vert (poss by tgting parent node instead), scroll
54 // n = tc.Parent
55 n.Children[i] = tc
56 }
57
58 return n
59}
60
61func (c *CSS) StyleSheet(path string) {
62 // Parse the CSS file
63 dat, _ := os.ReadFile(path)
64 styles := parser.ParseCSS(string(dat))
65
66 c.StyleSheets = append(c.StyleSheets, styles)
67}
68
69func (c *CSS) StyleTag(css string) {
70 styles := parser.ParseCSS(css)
71 c.StyleSheets = append(c.StyleSheets, styles)
72}
73
74var inheritedProps = []string{
75 "color",
76 "cursor",
77 "font",
78 "font-family",
79 "font-size",
80 "font-style",
81 "font-weight",
82 "letter-spacing",
83 "line-height",
84 // "text-align",
85 "text-indent",
86 "text-justify",
87 "text-shadow",
88 "text-transform",
89 "text-decoration",
90 "visibility",
91 "word-spacing",
92 "display",
93}
94
95func (c *CSS) QuickStyles(n *element.Node) map[string]string {
96 styles := make(map[string]string)
97
98 // Inherit styles from parent
99 if n.Parent != nil {
100 ps := n.Parent.Style
101 for _, prop := range inheritedProps {
102 if value, ok := ps[prop]; ok && value != "" {
103 styles[prop] = value
104 }
105 }
106 }
107
108 // Add node's own styles
109 for k, v := range n.Style {
110 styles[k] = v
111 }
112
113 return styles
114}
115
116func (c *CSS) GetStyles(n *element.Node) map[string]string {
117 styles := make(map[string]string)
118
119 // Inherit styles from parent
120 if n.Parent != nil {
121 ps := n.Parent.Style
122 for _, prop := range inheritedProps {
123 if value, ok := ps[prop]; ok && value != "" {
124 styles[prop] = value
125 }
126 }
127 }
128
129 // Add node's own styles
130 for k, v := range n.Style {
131 styles[k] = v
132 }
133
134 // Check if node is hovered
135 hovered := false
136 for _, class := range n.ClassList.Classes {
137 if class == ":hover" {
138 hovered = true
139 break
140 }
141 }
142
143 // Apply styles from style sheets
144 for _, styleSheet := range c.StyleSheets {
145 for selector, rules := range styleSheet {
146 originalSelector := selector
147
148 if hovered && strings.Contains(selector, ":hover") {
149 selector = strings.Replace(selector, ":hover", "", -1)
150 }
151
152 if element.TestSelector(selector, n) {
153 for k, v := range rules {
154 styles[k] = v
155 }
156 }
157
158 selector = originalSelector // Restore original selector
159 }
160 }
161
162 // Parse inline styles
163 inlineStyles := parser.ParseStyleAttribute(n.GetAttribute("style"))
164 for k, v := range inlineStyles {
165 styles[k] = v
166 }
167
168 // Handle z-index inheritance
169 if n.Parent != nil && styles["z-index"] == "" {
170 if parentZIndex, ok := n.Parent.Style["z-index"]; ok && parentZIndex != "" {
171 z, _ := strconv.Atoi(parentZIndex)
172 z += 1
173 styles["z-index"] = strconv.Itoa(z)
174 }
175 }
176
177 return styles
178}
179
180func (c *CSS) AddPlugin(plugin Plugin) {
181 c.Plugins = append(c.Plugins, plugin)
182}
183
184func (c *CSS) AddTransformer(transformer Transformer) {
185 c.Transformers = append(c.Transformers, transformer)
186}
187
188func CheckNode(n *element.Node, state *map[string]element.State) {
189 s := *state
190 self := s[n.Properties.Id]
191
192 fmt.Println(n.TagName, n.Properties.Id)
193 fmt.Printf("ID: %v\n", n.Id)
194 fmt.Printf("EM: %v\n", self.EM)
195 fmt.Printf("Parent: %v\n", n.Parent.TagName)
196 fmt.Printf("Classes: %v\n", n.ClassList.Classes)
197 fmt.Printf("Text: %v\n", n.InnerText)
198 fmt.Printf("X: %v, Y: %v, Z: %v\n", self.X, self.Y, self.Z)
199 fmt.Printf("Width: %v, Height: %v\n", self.Width, self.Height)
200 fmt.Printf("Styles: %v\n", n.Style)
201 fmt.Printf("Margin: %v\n", self.Margin)
202 fmt.Printf("Padding: %v\n", self.Padding)
203 // fmt.Printf("Background: %v\n", self.Background)
204 // fmt.Printf("Border: %v\n\n\n", self.Border)
205}
206
207func (c *CSS) ComputeNodeStyle(n *element.Node, state *map[string]element.State) *element.Node {
208
209 // Head is not renderable
210 if utils.IsParent(*n, "head") {
211 return n
212 }
213
214 plugins := c.Plugins
215
216 s := *state
217 self := s[n.Properties.Id]
218 parent := s[n.Parent.Properties.Id]
219
220 self.Background = color.Parse(n.Style, "background")
221 self.Border, _ = border.Parse(n.Style, self, parent)
222
223 fs := utils.ConvertToPixels(n.Style["font-size"], parent.EM, parent.Width)
224 self.EM = fs
225
226 if n.Style["display"] == "none" {
227 self.X = 0
228 self.Y = 0
229 self.Width = 0
230 self.Height = 0
231 return n
232 }
233
234 // Set Z index value to be sorted in window
235 if n.Style["z-index"] != "" {
236 z, _ := strconv.Atoi(n.Style["z-index"])
237 self.Z = float32(z)
238 }
239
240 if parent.Z > 0 {
241 self.Z = parent.Z + 1
242 }
243
244 (*state)[n.Properties.Id] = self
245
246 wh := utils.GetWH(*n, state)
247 width := wh.Width
248 height := wh.Height
249
250 x, y := parent.X, parent.Y
251 // !NOTE: Would like to consolidate all XY function into this function like WH
252 offsetX, offsetY := utils.GetXY(n, state)
253 x += offsetX
254 y += offsetY
255
256 var top, left, right, bottom bool = false, false, false, false
257
258 m := utils.GetMP(*n, wh, state, "margin")
259 p := utils.GetMP(*n, wh, state, "padding")
260
261 self.Margin = m
262 self.Padding = p
263
264 if n.Style["position"] == "absolute" {
265 bas := utils.GetPositionOffsetNode(n)
266 base := s[bas.Properties.Id]
267 if n.Style["top"] != "" {
268 v := utils.ConvertToPixels(n.Style["top"], self.EM, parent.Width)
269 y = v + base.Y
270 top = true
271 }
272 if n.Style["left"] != "" {
273 v := utils.ConvertToPixels(n.Style["left"], self.EM, parent.Width)
274 x = v + base.X
275 left = true
276 }
277 if n.Style["right"] != "" {
278 v := utils.ConvertToPixels(n.Style["right"], self.EM, parent.Width)
279 x = (base.Width - width) - v
280 right = true
281 }
282 if n.Style["bottom"] != "" {
283 v := utils.ConvertToPixels(n.Style["bottom"], self.EM, parent.Width)
284 y = (base.Height - height) - v
285 bottom = true
286 }
287
288 } else {
289 for i, v := range n.Parent.Children {
290 if v.Style["position"] != "absolute" {
291 if v.Properties.Id == n.Properties.Id {
292 if i > 0 {
293 sib := n.Parent.Children[i-1]
294 sibling := s[sib.Properties.Id]
295 if sib.Style["position"] != "absolute" {
296 if n.Style["display"] == "inline" {
297 if sib.Style["display"] == "inline" {
298 y = sibling.Y
299 } else {
300 y = sibling.Y + sibling.Height
301 }
302 } else {
303 y = sibling.Y + sibling.Height + (sibling.Border.Top.Width + sibling.Border.Bottom.Width) + sibling.Margin.Bottom
304 }
305 }
306 }
307 break
308 } else if n.Style["display"] != "inline" {
309 vState := s[v.Properties.Id]
310 y += vState.Margin.Top + vState.Margin.Bottom + vState.Padding.Top + vState.Padding.Bottom + vState.Height + (self.Border.Top.Width)
311 }
312 }
313 }
314 }
315
316 // Display modes need to be calculated here
317
318 relPos := !top && !left && !right && !bottom
319
320 if left || relPos {
321 x += m.Left
322 }
323 if top || relPos {
324 y += m.Top
325 }
326 if right {
327 x -= m.Right
328 }
329 if bottom {
330 y -= m.Bottom
331 }
332
333 self.X = x
334 self.Y = y
335 self.Width = width
336 self.Height = height
337 (*state)[n.Properties.Id] = self
338
339 if !utils.ChildrenHaveText(n) && len(n.InnerText) > 0 {
340 // Confirm text exists
341 n.InnerText = strings.TrimSpace(n.InnerText)
342 self = genTextNode(n, state, c)
343 }
344
345 (*state)[n.Properties.Id] = self
346 (*state)[n.Parent.Properties.Id] = parent
347 // Call children here
348
349 var childYOffset float32
350 for i := 0; i < len(n.Children); i++ {
351 v := n.Children[i]
352 v.Parent = n
353 // This is were the tainting comes from
354 n.Children[i] = c.ComputeNodeStyle(v, state)
355
356 cState := (*state)[n.Children[i].Properties.Id]
357 if n.Style["height"] == "" && n.Style["min-height"] == "" {
358 if v.Style["position"] != "absolute" && cState.Y+cState.Height > childYOffset {
359 childYOffset = cState.Y + cState.Height
360 self.Height = (cState.Y - self.Border.Top.Width) - (self.Y) + cState.Height
361 self.Height += cState.Margin.Top
362 self.Height += cState.Margin.Bottom
363 self.Height += cState.Padding.Top
364 self.Height += cState.Padding.Bottom
365 self.Height += cState.Border.Top.Width + cState.Border.Bottom.Width
366 }
367 }
368 if cState.Width > self.Width {
369 self.Width = cState.Width
370 }
371 }
372
373 if n.Style["height"] == "" {
374 self.Height += self.Padding.Bottom
375 }
376
377 (*state)[n.Properties.Id] = self
378
379 // Sorting the array by the Level field
380 sort.Slice(plugins, func(i, j int) bool {
381 return plugins[i].Level < plugins[j].Level
382 })
383
384 for _, v := range plugins {
385 if v.Selector(n) {
386 v.Handler(n, state)
387 }
388 }
389
390 // CheckNode(n, state)
391 return n
392}
393
394func genTextNode(n *element.Node, state *map[string]element.State, css *CSS) element.State {
395 s := *state
396 self := s[n.Properties.Id]
397 parent := s[n.Parent.Properties.Id]
398
399 text := element.Text{}
400
401 bold, italic := false, false
402 // !ISSUE: needs bolder and the 100 -> 900
403 if n.Style["font-weight"] == "bold" {
404 bold = true
405 }
406
407 if n.Style["font-style"] == "italic" {
408 italic = true
409 }
410
411 if text.Font == nil {
412 if css.Fonts == nil {
413 css.Fonts = map[string]imgFont.Face{}
414 }
415 fid := n.Style["font-family"] + fmt.Sprint(self.EM, bold, italic)
416 if css.Fonts[fid] == nil {
417 f, _ := font.LoadFont(n.Style["font-family"], int(self.EM), bold, italic)
418 css.Fonts[fid] = f
419 }
420 fnt := css.Fonts[fid]
421 text.Font = &fnt
422 }
423
424 letterSpacing := utils.ConvertToPixels(n.Style["letter-spacing"], self.EM, parent.Width)
425 wordSpacing := utils.ConvertToPixels(n.Style["word-spacing"], self.EM, parent.Width)
426 lineHeight := utils.ConvertToPixels(n.Style["line-height"], self.EM, parent.Width)
427 if lineHeight == 0 {
428 lineHeight = self.EM + 3
429 }
430
431 text.LineHeight = int(lineHeight)
432 text.WordSpacing = int(wordSpacing)
433 text.LetterSpacing = int(letterSpacing)
434 wb := " "
435
436 if n.Style["word-wrap"] == "break-word" {
437 wb = ""
438 }
439
440 if n.Style["text-wrap"] == "wrap" || n.Style["text-wrap"] == "balance" {
441 wb = ""
442 }
443
444 var dt float32
445
446 if n.Style["text-decoration-thickness"] == "auto" || n.Style["text-decoration-thickness"] == "" {
447 dt = self.EM / 7
448 } else {
449 dt = utils.ConvertToPixels(n.Style["text-decoration-thickness"], self.EM, parent.Width)
450 }
451
452 col := color.Parse(n.Style, "font")
453
454 self.Color = col
455
456 text.Color = col
457 text.DecorationColor = color.Parse(n.Style, "decoration")
458 text.Align = n.Style["text-align"]
459 text.WordBreak = wb
460 text.WordSpacing = int(wordSpacing)
461 text.LetterSpacing = int(letterSpacing)
462 text.WhiteSpace = n.Style["white-space"]
463 text.DecorationThickness = int(dt)
464 text.Overlined = n.Style["text-decoration"] == "overline"
465 text.Underlined = n.Style["text-decoration"] == "underline"
466 text.LineThrough = n.Style["text-decoration"] == "linethrough"
467 text.EM = int(self.EM)
468 text.Width = int(parent.Width)
469 text.Text = n.InnerText
470 text.Last = n.GetAttribute("last") == "true"
471
472 if n.Style["word-spacing"] == "" {
473 text.WordSpacing = font.MeasureSpace(&text)
474 }
475
476 img, width := font.Render(&text)
477 self.Texture = img
478
479 if n.Style["height"] == "" && n.Style["min-height"] == "" {
480 self.Height = float32(text.LineHeight)
481 }
482
483 if n.Style["width"] == "" && n.Style["min-width"] == "" {
484 self.Width = float32(width)
485 }
486
487 return self
488}