xbintsc 0.5.45 → 0.5.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +5 -4
  3. package/README.zh-CN.md +4 -3
  4. package/dist/src/cli/extensions.d.ts +24 -0
  5. package/dist/src/cli/extensions.js +62 -0
  6. package/dist/src/cli/extensions.js.map +1 -0
  7. package/dist/src/cli/help.d.ts +1 -1
  8. package/dist/src/cli/help.js +3 -1
  9. package/dist/src/cli/help.js.map +1 -1
  10. package/dist/src/cli/hints.js +8 -3
  11. package/dist/src/cli/hints.js.map +1 -1
  12. package/dist/src/cli/main.js +21 -28
  13. package/dist/src/cli/main.js.map +1 -1
  14. package/dist/src/diagnostics/diagnostic.d.ts +3 -1
  15. package/dist/src/diagnostics/diagnostic.js +2 -0
  16. package/dist/src/diagnostics/diagnostic.js.map +1 -1
  17. package/dist/src/driver/build-key.d.ts +2 -0
  18. package/dist/src/driver/build-key.js +1 -0
  19. package/dist/src/driver/build-key.js.map +1 -1
  20. package/dist/src/driver/compiler.js +61 -8
  21. package/dist/src/driver/compiler.js.map +1 -1
  22. package/dist/src/driver/config.d.ts +4 -0
  23. package/dist/src/driver/config.js +2 -0
  24. package/dist/src/driver/config.js.map +1 -1
  25. package/dist/src/driver/link-features.d.ts +37 -0
  26. package/dist/src/driver/link-features.js +123 -0
  27. package/dist/src/driver/link-features.js.map +1 -0
  28. package/dist/src/driver/options.d.ts +10 -0
  29. package/dist/src/driver/runtime-objects.d.ts +5 -0
  30. package/dist/src/driver/runtime-objects.js +20 -1
  31. package/dist/src/driver/runtime-objects.js.map +1 -1
  32. package/dist/src/driver/usage.d.ts +23 -0
  33. package/dist/src/driver/usage.js +353 -0
  34. package/dist/src/driver/usage.js.map +1 -0
  35. package/dist/src/driver/version.d.ts +1 -1
  36. package/dist/src/driver/version.js +1 -1
  37. package/dist/src/extensions/catalog.js +1 -2
  38. package/dist/src/extensions/catalog.js.map +1 -1
  39. package/dist/src/extensions/gui/features.d.ts +34 -0
  40. package/dist/src/extensions/gui/features.js +165 -0
  41. package/dist/src/extensions/gui/features.js.map +1 -0
  42. package/dist/src/extensions/gui/index.js +10 -4
  43. package/dist/src/extensions/gui/index.js.map +1 -1
  44. package/dist/src/extensions/link-feature.d.ts +70 -0
  45. package/dist/src/extensions/link-feature.js +18 -0
  46. package/dist/src/extensions/link-feature.js.map +1 -0
  47. package/dist/src/extensions/registry.d.ts +24 -4
  48. package/dist/src/extensions/registry.js +26 -3
  49. package/dist/src/extensions/registry.js.map +1 -1
  50. package/dist/src/index.d.ts +3 -2
  51. package/dist/src/index.js +0 -1
  52. package/dist/src/index.js.map +1 -1
  53. package/dist/tests/cli/hints.test.js +5 -7
  54. package/dist/tests/cli/hints.test.js.map +1 -1
  55. package/dist/tests/driver/link-features.test.js +105 -0
  56. package/dist/tests/driver/link-features.test.js.map +1 -0
  57. package/dist/tests/driver/usage.test.d.ts +1 -0
  58. package/dist/tests/driver/usage.test.js +88 -0
  59. package/dist/tests/driver/usage.test.js.map +1 -0
  60. package/dist/tests/e2e/gui-audio.test.js +4 -4
  61. package/dist/tests/e2e/gui-audio.test.js.map +1 -1
  62. package/dist/tests/e2e/gui-css-coverage.test.d.ts +9 -0
  63. package/dist/tests/e2e/gui-css-coverage.test.js +333 -0
  64. package/dist/tests/e2e/gui-css-coverage.test.js.map +1 -0
  65. package/dist/tests/e2e/gui-css-features.test.d.ts +6 -0
  66. package/dist/tests/e2e/gui-css-features.test.js +270 -0
  67. package/dist/tests/e2e/gui-css-features.test.js.map +1 -0
  68. package/dist/tests/e2e/gui-effects.test.js +92 -11
  69. package/dist/tests/e2e/gui-effects.test.js.map +1 -1
  70. package/dist/tests/e2e/gui-feature-linking.test.d.ts +9 -0
  71. package/dist/tests/e2e/gui-feature-linking.test.js +68 -0
  72. package/dist/tests/e2e/gui-feature-linking.test.js.map +1 -0
  73. package/dist/tests/e2e/gui-font-face.test.d.ts +10 -0
  74. package/dist/tests/e2e/gui-font-face.test.js +142 -0
  75. package/dist/tests/e2e/gui-font-face.test.js.map +1 -0
  76. package/dist/tests/e2e/gui-fonts.test.d.ts +10 -0
  77. package/dist/tests/e2e/gui-fonts.test.js +150 -0
  78. package/dist/tests/e2e/gui-fonts.test.js.map +1 -0
  79. package/dist/tests/e2e/gui-helpers.d.ts +10 -1
  80. package/dist/tests/e2e/gui-helpers.js +6 -0
  81. package/dist/tests/e2e/gui-helpers.js.map +1 -1
  82. package/dist/tests/e2e/gui-layout.test.js +7 -3
  83. package/dist/tests/e2e/gui-layout.test.js.map +1 -1
  84. package/dist/tests/e2e/gui-media-disabled.test.d.ts +5 -4
  85. package/dist/tests/e2e/gui-media-disabled.test.js +7 -6
  86. package/dist/tests/e2e/gui-media-disabled.test.js.map +1 -1
  87. package/dist/tests/e2e/gui-media.test.d.ts +5 -4
  88. package/dist/tests/e2e/gui-media.test.js +9 -9
  89. package/dist/tests/e2e/gui-media.test.js.map +1 -1
  90. package/dist/tests/e2e/gui-paint.test.js +5 -2
  91. package/dist/tests/e2e/gui-paint.test.js.map +1 -1
  92. package/dist/tests/e2e/gui-shaping.test.d.ts +5 -0
  93. package/dist/tests/e2e/gui-shaping.test.js +47 -0
  94. package/dist/tests/e2e/gui-shaping.test.js.map +1 -0
  95. package/dist/tests/e2e/gui-track-sizing.test.d.ts +6 -0
  96. package/dist/tests/e2e/gui-track-sizing.test.js +173 -0
  97. package/dist/tests/e2e/gui-track-sizing.test.js.map +1 -0
  98. package/dist/tests/e2e/gui-transform.test.js +3 -2
  99. package/dist/tests/e2e/gui-transform.test.js.map +1 -1
  100. package/dist/tests/e2e/gui-webgl-pipeline-cache.test.d.ts +18 -0
  101. package/dist/tests/e2e/gui-webgl-pipeline-cache.test.js +120 -0
  102. package/dist/tests/e2e/gui-webgl-pipeline-cache.test.js.map +1 -0
  103. package/dist/tests/extensions/gui-features.test.d.ts +1 -0
  104. package/dist/tests/extensions/gui-features.test.js +175 -0
  105. package/dist/tests/extensions/gui-features.test.js.map +1 -0
  106. package/doc/ai/cli.md +5 -1
  107. package/doc/ai/extensions.md +56 -26
  108. package/doc/ai/troubleshooting.md +10 -7
  109. package/doc/ai/zh-CN/cli.md +5 -1
  110. package/doc/ai/zh-CN/extensions.md +48 -22
  111. package/doc/ai/zh-CN/troubleshooting.md +6 -5
  112. package/doc/gui/animation.md +39 -9
  113. package/doc/gui/api.md +2 -1
  114. package/doc/gui/build.md +137 -19
  115. package/doc/gui/canvas.md +1 -1
  116. package/doc/gui/dom.md +20 -4
  117. package/doc/gui/known-issues.md +63 -26
  118. package/doc/gui/layout.md +31 -8
  119. package/doc/gui/media.md +13 -7
  120. package/doc/gui/rendering.md +114 -39
  121. package/doc/gui/runtime.md +5 -0
  122. package/doc/gui/webgl.md +24 -6
  123. package/doc/gui.md +110 -47
  124. package/doc/implemented/cli.md +2 -0
  125. package/doc/implemented/extensions.md +9 -3
  126. package/doc/npm-package.md +4 -4
  127. package/doc/xbintsc.config.schema.json +10 -0
  128. package/doc/zh-CN/gui/animation.md +33 -10
  129. package/doc/zh-CN/gui/api.md +2 -1
  130. package/doc/zh-CN/gui/build.md +110 -16
  131. package/doc/zh-CN/gui/canvas.md +1 -1
  132. package/doc/zh-CN/gui/dom.md +16 -3
  133. package/doc/zh-CN/gui/known-issues.md +45 -15
  134. package/doc/zh-CN/gui/layout.md +23 -6
  135. package/doc/zh-CN/gui/media.md +11 -6
  136. package/doc/zh-CN/gui/rendering.md +84 -26
  137. package/doc/zh-CN/gui/runtime.md +3 -0
  138. package/doc/zh-CN/gui/webgl.md +3 -3
  139. package/doc/zh-CN/gui.md +92 -41
  140. package/doc/zh-CN/implemented/cli.md +2 -0
  141. package/doc/zh-CN/implemented/extensions.md +9 -3
  142. package/doc/zh-CN/npm-package.md +4 -3
  143. package/package.json +7 -7
  144. package/runtime/ext_gui/animation_values.cpp +425 -0
  145. package/runtime/ext_gui/animation_values.h +64 -0
  146. package/runtime/ext_gui/browser.cpp +22 -15
  147. package/runtime/ext_gui/browser_loop.cpp +13 -1
  148. package/runtime/ext_gui/canvas.h +1 -1
  149. package/runtime/ext_gui/canvas_raster.cpp +1 -1
  150. package/runtime/ext_gui/canvas_text.cpp +1 -1
  151. package/runtime/ext_gui/css.cpp +172 -21
  152. package/runtime/ext_gui/css.h +75 -4
  153. package/runtime/ext_gui/css_match.cpp +4 -0
  154. package/runtime/ext_gui/css_sheet.cpp +218 -2
  155. package/runtime/ext_gui/document.cpp +42 -192
  156. package/runtime/ext_gui/document.h +25 -12
  157. package/runtime/ext_gui/document_animation.cpp +428 -0
  158. package/runtime/ext_gui/dom_api_canvas.cpp +15 -8
  159. package/runtime/ext_gui/dom_api_canvas_extra.cpp +44 -20
  160. package/runtime/ext_gui/dom_api_canvas_image.cpp +3 -1
  161. package/runtime/ext_gui/dom_api_node_read.cpp +10 -0
  162. package/runtime/ext_gui/dom_api_offscreen.cpp +3 -1
  163. package/runtime/ext_gui/dom_api_style.cpp +7 -0
  164. package/runtime/ext_gui/dom_api_webgl_internal.h +4 -0
  165. package/runtime/ext_gui/dom_api_webgl_source.cpp +5 -2
  166. package/runtime/ext_gui/effect_values.cpp +245 -21
  167. package/runtime/ext_gui/gpu_bridge.c +28 -0
  168. package/runtime/ext_gui/gpu_feature.cpp +30 -0
  169. package/runtime/ext_gui/gpu_shader.cpp +22 -0
  170. package/runtime/ext_gui/gpu_shader_api.h +50 -0
  171. package/runtime/ext_gui/gradient_values.cpp +24 -0
  172. package/runtime/ext_gui/grid_values.cpp +23 -0
  173. package/runtime/ext_gui/gui.cpp +6 -37
  174. package/runtime/ext_gui/gui_canvas.cpp +14 -6
  175. package/runtime/ext_gui/gui_cursor.cpp +50 -0
  176. package/runtime/ext_gui/gui_engine.h +1 -18
  177. package/runtime/ext_gui/gui_events.cpp +19 -6
  178. package/runtime/ext_gui/gui_internal.h +3 -0
  179. package/runtime/ext_gui/gui_webgl.cpp +25 -10
  180. package/runtime/ext_gui/image_api.cpp +18 -0
  181. package/runtime/ext_gui/image_api.h +43 -0
  182. package/runtime/ext_gui/image_bitmap_browser.cpp +20 -11
  183. package/runtime/ext_gui/image_bridge.c +22 -0
  184. package/runtime/ext_gui/image_feature.cpp +107 -0
  185. package/runtime/ext_gui/inline_svg.cpp +95 -8
  186. package/runtime/ext_gui/inline_svg.h +5 -1
  187. package/runtime/ext_gui/layout.cpp +15 -9
  188. package/runtime/ext_gui/layout.h +8 -2
  189. package/runtime/ext_gui/layout_flex.cpp +134 -29
  190. package/runtime/ext_gui/layout_flow.cpp +58 -7
  191. package/runtime/ext_gui/layout_flow_internal.h +39 -4
  192. package/runtime/ext_gui/layout_grid.cpp +31 -0
  193. package/runtime/ext_gui/layout_inline.cpp +26 -2
  194. package/runtime/ext_gui/layout_vertical.cpp +196 -0
  195. package/runtime/ext_gui/media.cpp +4 -2
  196. package/runtime/ext_gui/media.h +3 -3
  197. package/runtime/ext_gui/media_api.h +7 -6
  198. package/runtime/ext_gui/paint.cpp +67 -9
  199. package/runtime/ext_gui/paint.h +61 -8
  200. package/runtime/ext_gui/paint_backdrop.cpp +211 -0
  201. package/runtime/ext_gui/paint_background.cpp +175 -97
  202. package/runtime/ext_gui/paint_effects.cpp +12 -6
  203. package/runtime/ext_gui/paint_internal.h +27 -0
  204. package/runtime/ext_gui/paint_outline.cpp +44 -0
  205. package/runtime/ext_gui/paint_shadow.cpp +368 -0
  206. package/runtime/ext_gui/renderer.cpp +15 -7
  207. package/runtime/ext_gui/renderer.h +11 -2
  208. package/runtime/ext_gui/renderer_backdrop.cpp +374 -0
  209. package/runtime/ext_gui/renderer_bitmap.cpp +82 -0
  210. package/runtime/ext_gui/renderer_gradient.cpp +51 -17
  211. package/runtime/ext_gui/renderer_image.cpp +19 -7
  212. package/runtime/ext_gui/renderer_internal.h +21 -0
  213. package/runtime/ext_gui/renderer_layer.cpp +37 -6
  214. package/runtime/ext_gui/renderer_text.cpp +2 -1
  215. package/runtime/ext_gui/stb_truetype.h +5077 -0
  216. package/runtime/ext_gui/style.cpp +62 -8
  217. package/runtime/ext_gui/style.h +111 -4
  218. package/runtime/ext_gui/style_cascade.cpp +83 -0
  219. package/runtime/ext_gui/style_internal.h +13 -1
  220. package/runtime/ext_gui/style_length.cpp +92 -0
  221. package/runtime/ext_gui/style_print.cpp +83 -2
  222. package/runtime/ext_gui/style_svg.cpp +40 -0
  223. package/runtime/ext_gui/style_text.cpp +302 -0
  224. package/runtime/ext_gui/style_ui.cpp +150 -0
  225. package/runtime/ext_gui/style_values.cpp +76 -62
  226. package/runtime/ext_gui/style_values_internal.h +23 -0
  227. package/runtime/ext_gui/text.cpp +27 -499
  228. package/runtime/ext_gui/text.h +18 -9
  229. package/runtime/ext_gui/text_api.h +45 -0
  230. package/runtime/ext_gui/text_bridge.c +23 -0
  231. package/runtime/ext_gui/text_cache.cpp +12 -3
  232. package/runtime/ext_gui/text_font_db.cpp +427 -0
  233. package/runtime/ext_gui/text_font_paths.cpp +250 -0
  234. package/runtime/ext_gui/text_font_paths.h +39 -0
  235. package/runtime/ext_gui/text_font_sfnt.cpp +396 -0
  236. package/runtime/ext_gui/text_font_sfnt.h +44 -0
  237. package/runtime/ext_gui/text_fonts.cpp +348 -0
  238. package/runtime/ext_gui/text_fonts.h +78 -0
  239. package/runtime/ext_gui/text_fonts_internal.h +77 -0
  240. package/runtime/ext_gui/text_glyph_cache.cpp +2 -2
  241. package/runtime/ext_gui/text_hb.cpp +438 -0
  242. package/runtime/ext_gui/text_stb.cpp +369 -0
  243. package/runtime/ext_gui/transform.cpp +107 -9
  244. package/runtime/ext_gui/transform.h +5 -0
  245. package/runtime/ext_gui/webgl_api.cpp +19 -0
  246. package/runtime/ext_gui/webgl_api.h +53 -0
  247. package/runtime/ext_gui/webgl_bridge.c +25 -0
  248. package/runtime/ext_gui/webgl_draw_setup.cpp +42 -1
  249. package/runtime/ext_gui/webgl_feature.cpp +82 -0
  250. package/runtime/ext_gui/webgl_feature.h +28 -0
  251. package/runtime/ext_gui/webgl_msl.cpp +41 -0
  252. package/runtime/ext_gui/webgl_msl.h +6 -0
  253. package/runtime/ext_gui/webgl_shader.cpp +40 -32
  254. package/runtime/ext_gui/window.cpp +7 -7
  255. package/runtime/ext_media/decode_audio.cpp +1 -1
  256. package/runtime/ext_media/media.cpp +1 -1
  257. package/runtime/ext_media/media_bridge.c +7 -7
  258. package/scripts/build-gui.ts +97 -98
  259. package/scripts/check-file-length.ts +1 -0
  260. package/scripts/gui-build-options.ts +122 -0
  261. package/src/cli/extensions.ts +72 -0
  262. package/src/cli/help.ts +3 -1
  263. package/src/cli/hints.ts +9 -3
  264. package/src/cli/main.ts +21 -27
  265. package/src/diagnostics/diagnostic.ts +2 -0
  266. package/src/driver/build-key.ts +3 -0
  267. package/src/driver/compiler.ts +63 -8
  268. package/src/driver/config.ts +6 -0
  269. package/src/driver/link-features.ts +163 -0
  270. package/src/driver/options.ts +10 -0
  271. package/src/driver/runtime-objects.ts +26 -1
  272. package/src/driver/usage.ts +376 -0
  273. package/src/driver/version.ts +1 -1
  274. package/src/extensions/catalog.ts +1 -2
  275. package/src/extensions/gui/features.ts +166 -0
  276. package/src/extensions/gui/index.ts +10 -4
  277. package/src/extensions/link-feature.ts +72 -0
  278. package/src/extensions/registry.ts +45 -4
  279. package/src/index.ts +3 -2
  280. package/dist/src/extensions/media/index.d.ts +0 -28
  281. package/dist/src/extensions/media/index.js +0 -59
  282. package/dist/src/extensions/media/index.js.map +0 -1
  283. package/dist/tests/extensions/media.test.js +0 -63
  284. package/dist/tests/extensions/media.test.js.map +0 -1
  285. package/src/extensions/media/index.ts +0 -65
  286. /package/dist/tests/{extensions/media.test.d.ts → driver/link-features.test.d.ts} +0 -0
@@ -0,0 +1,211 @@
1
+ /*
2
+ * xbintsc GUI engine — CSS `backdrop-filter`.
3
+ *
4
+ * The backdrop is whatever has already been painted behind the element, which
5
+ * only the renderer can see. Paint therefore resolves the filter list into the
6
+ * two things the compositor needs: a Gaussian sigma and one combined 3x3 colour
7
+ * matrix. The renderer snapshots the scene, applies both in a full-screen
8
+ * shader, and composites the result back inside the element's rounded border
9
+ * box (see `renderer_layer.cpp`).
10
+ */
11
+
12
+ #include "paint_internal.h"
13
+
14
+ #include <algorithm>
15
+ #include <cmath>
16
+
17
+ namespace xtgui {
18
+ namespace paint_detail {
19
+
20
+ namespace {
21
+
22
+ struct ColorMatrix {
23
+ float m[9] = {1, 0, 0, 0, 1, 0, 0, 0, 1};
24
+ float offset[3] = {0, 0, 0};
25
+ };
26
+
27
+ /** `out = left * right` for row-major 3x3 matrices. */
28
+ ColorMatrix multiply(const ColorMatrix &left, const ColorMatrix &right) {
29
+ ColorMatrix result;
30
+ for (int row = 0; row < 3; row++) {
31
+ for (int column = 0; column < 3; column++) {
32
+ float sum = 0.0f;
33
+ for (int k = 0; k < 3; k++) sum += left.m[row * 3 + k] * right.m[k * 3 + column];
34
+ result.m[row * 3 + column] = sum;
35
+ }
36
+ result.offset[row] = left.m[row * 3] * right.offset[0] +
37
+ left.m[row * 3 + 1] * right.offset[1] +
38
+ left.m[row * 3 + 2] * right.offset[2] + left.offset[row];
39
+ }
40
+ return result;
41
+ }
42
+
43
+ ColorMatrix identity() { return ColorMatrix(); }
44
+
45
+ ColorMatrix brightness(float amount) {
46
+ ColorMatrix matrix;
47
+ matrix.m[0] = matrix.m[4] = matrix.m[8] = amount;
48
+ return matrix;
49
+ }
50
+
51
+ ColorMatrix contrast(float amount) {
52
+ ColorMatrix matrix = brightness(amount);
53
+ float shift = 0.5f - 0.5f * amount;
54
+ matrix.offset[0] = matrix.offset[1] = matrix.offset[2] = shift;
55
+ return matrix;
56
+ }
57
+
58
+ ColorMatrix invert(float amount) {
59
+ ColorMatrix matrix;
60
+ float scale = 1.0f - 2.0f * amount;
61
+ matrix.m[0] = matrix.m[4] = matrix.m[8] = scale;
62
+ matrix.offset[0] = matrix.offset[1] = matrix.offset[2] = amount;
63
+ return matrix;
64
+ }
65
+
66
+ ColorMatrix grayscale(float amount) {
67
+ const float lr = 0.2126f, lg = 0.7152f, lb = 0.0722f;
68
+ ColorMatrix matrix;
69
+ matrix.m[0] = 1.0f - amount + amount * lr;
70
+ matrix.m[1] = amount * lg;
71
+ matrix.m[2] = amount * lb;
72
+ matrix.m[3] = amount * lr;
73
+ matrix.m[4] = 1.0f - amount + amount * lg;
74
+ matrix.m[5] = amount * lb;
75
+ matrix.m[6] = amount * lr;
76
+ matrix.m[7] = amount * lg;
77
+ matrix.m[8] = 1.0f - amount + amount * lb;
78
+ return matrix;
79
+ }
80
+
81
+ ColorMatrix sepia(float amount) {
82
+ const float base[9] = {0.393f, 0.769f, 0.189f, 0.349f, 0.686f,
83
+ 0.168f, 0.272f, 0.534f, 0.131f};
84
+ ColorMatrix matrix;
85
+ for (int i = 0; i < 9; i++) matrix.m[i] = (1.0f - amount) * (i % 4 == 0 ? 1.0f : 0.0f) +
86
+ amount * base[i];
87
+ return matrix;
88
+ }
89
+
90
+ ColorMatrix saturate(float amount) {
91
+ const float lr = 0.213f, lg = 0.715f, lb = 0.072f;
92
+ ColorMatrix matrix;
93
+ matrix.m[0] = lr + (1.0f - lr) * amount;
94
+ matrix.m[1] = lg - lg * amount;
95
+ matrix.m[2] = lb - lb * amount;
96
+ matrix.m[3] = lr - lr * amount;
97
+ matrix.m[4] = lg + (1.0f - lg) * amount;
98
+ matrix.m[5] = lb - lb * amount;
99
+ matrix.m[6] = lr - lr * amount;
100
+ matrix.m[7] = lg - lg * amount;
101
+ matrix.m[8] = lb + (1.0f - lb) * amount;
102
+ return matrix;
103
+ }
104
+
105
+ ColorMatrix hueRotate(float degrees) {
106
+ float radians = degrees * 3.14159265358979323846f / 180.0f;
107
+ float c = std::cos(radians);
108
+ float s = std::sin(radians);
109
+ ColorMatrix matrix;
110
+ matrix.m[0] = 0.213f + c * 0.787f - s * 0.213f;
111
+ matrix.m[1] = 0.715f - c * 0.715f - s * 0.715f;
112
+ matrix.m[2] = 0.072f - c * 0.072f + s * 0.928f;
113
+ matrix.m[3] = 0.213f - c * 0.213f + s * 0.143f;
114
+ matrix.m[4] = 0.715f + c * 0.285f + s * 0.140f;
115
+ matrix.m[5] = 0.072f - c * 0.072f - s * 0.283f;
116
+ matrix.m[6] = 0.213f - c * 0.213f - s * 0.787f;
117
+ matrix.m[7] = 0.715f - c * 0.715f + s * 0.715f;
118
+ matrix.m[8] = 0.072f + c * 0.928f + s * 0.072f;
119
+ return matrix;
120
+ }
121
+
122
+ /** Combine the filter list in application order: the first function applies to
123
+ * the backdrop first, so each new matrix multiplies the accumulated one on the
124
+ * left. Drop shadows are a silhouette effect that the colour matrix cannot
125
+ * express; they are ignored rather than misapplied. */
126
+ void compileFilters(const std::vector<FilterOp> &filters, const XtStyle &style,
127
+ float viewportWidth, float viewportHeight, ColorMatrix *matrix, float *blur,
128
+ float *alpha) {
129
+ *matrix = identity();
130
+ *blur = 0.0f;
131
+ *alpha = 1.0f;
132
+ for (const FilterOp &filter : filters) {
133
+ switch (filter.kind) {
134
+ case FilterKind::Blur:
135
+ *blur += resolveLength(filter.blur, 0.0f, style.font_size, viewportWidth, viewportHeight);
136
+ break;
137
+ case FilterKind::Brightness:
138
+ *matrix = multiply(brightness(filter.amount), *matrix);
139
+ break;
140
+ case FilterKind::Contrast:
141
+ *matrix = multiply(contrast(filter.amount), *matrix);
142
+ break;
143
+ case FilterKind::Grayscale:
144
+ *matrix = multiply(grayscale(filter.amount), *matrix);
145
+ break;
146
+ case FilterKind::Sepia:
147
+ *matrix = multiply(sepia(filter.amount), *matrix);
148
+ break;
149
+ case FilterKind::Invert:
150
+ *matrix = multiply(invert(filter.amount), *matrix);
151
+ break;
152
+ case FilterKind::Saturate:
153
+ *matrix = multiply(saturate(filter.amount), *matrix);
154
+ break;
155
+ case FilterKind::HueRotate:
156
+ *matrix = multiply(hueRotate(filter.amount), *matrix);
157
+ break;
158
+ case FilterKind::Opacity:
159
+ *alpha *= filter.amount;
160
+ break;
161
+ case FilterKind::DropShadow:
162
+ break;
163
+ }
164
+ }
165
+ }
166
+
167
+ } // namespace
168
+
169
+ bool addBackdropFilter(DisplayList &out, const LayoutBox &box, const XtStyle &style,
170
+ const PaintClip &clip, float viewportWidth, float viewportHeight) {
171
+ if (style.backdrop_filters.empty()) return false;
172
+ ColorMatrix matrix;
173
+ float blur = 0.0f;
174
+ float alpha = 1.0f;
175
+ compileFilters(style.backdrop_filters, style, viewportWidth, viewportHeight, &matrix, &blur,
176
+ &alpha);
177
+ if (blur <= 0.0f && alpha >= 1.0f && matrix.m[0] == 1.0f && matrix.m[1] == 0.0f &&
178
+ matrix.m[2] == 0.0f && matrix.m[3] == 0.0f && matrix.m[4] == 1.0f &&
179
+ matrix.m[5] == 0.0f && matrix.m[6] == 0.0f && matrix.m[7] == 0.0f &&
180
+ matrix.m[8] == 1.0f) {
181
+ return false;
182
+ }
183
+ PaintBackdrop backdrop;
184
+ float x = box.x;
185
+ float y = box.y;
186
+ float width = box.width;
187
+ float height = box.height;
188
+ backdrop.radius = box.border_radius;
189
+ if (box.has_transform) {
190
+ LayoutRect bounds = xt_layout_transform_rect(box.world, x, y, width, height);
191
+ x = bounds.x;
192
+ y = bounds.y;
193
+ width = bounds.width;
194
+ height = bounds.height;
195
+ }
196
+ backdrop.x = x;
197
+ backdrop.y = y;
198
+ backdrop.width = width;
199
+ backdrop.height = height;
200
+ backdrop.blur = blur;
201
+ backdrop.alpha = alpha;
202
+ for (int i = 0; i < 9; i++) backdrop.color[i] = matrix.m[i];
203
+ for (int i = 0; i < 3; i++) backdrop.offset[i] = matrix.offset[i];
204
+ out.backdrops.push_back(backdrop);
205
+ out.ops.push_back(
206
+ PaintOp{PaintKind::Backdrop, (uint32_t)(out.backdrops.size() - 1), clip});
207
+ return true;
208
+ }
209
+
210
+ } // namespace paint_detail
211
+ } // namespace xtgui
@@ -2,19 +2,21 @@
2
2
  * xbintsc GUI engine — background layer painting.
3
3
  *
4
4
  * `background-color` is one rounded rectangle and a `background-image` layer is
5
- * a gradient or an image. A linear gradient is emitted as a run of solid bands
6
- * (18 strips, 64 while repeating). A radial gradient is emitted as a single
5
+ * a gradient or an image. Both gradient kinds are emitted as a single
7
6
  * `PaintGradient` that the renderer rasterises per pixel
8
- * (`renderer_gradient.cpp`): stacking translucent radial bands composites their
7
+ * (`renderer_gradient.cpp`). Stacking translucent bands instead composites their
9
8
  * alpha once per band, which turns a vignette whose stops fade to `transparent`
10
- * into an opaque disc.
9
+ * into an opaque disc, and a run of bands cannot draw a stop narrower than one
10
+ * band: a 1px divider line either falls between two samples or becomes a block.
11
11
  */
12
12
 
13
13
  #include "paint_internal.h"
14
14
 
15
15
  #include <algorithm>
16
16
  #include <cmath>
17
+ #include <cstddef>
17
18
  #include <cstdio>
19
+ #include <limits>
18
20
  #include <memory>
19
21
  #include <string>
20
22
  #include <vector>
@@ -69,23 +71,31 @@ Color gradientColorAt(const std::vector<GradientStop> &stops, bool repeating, fl
69
71
  namespace {
70
72
 
71
73
  /** Resolve the declaration's percentage/length offsets against the gradient
72
- * line, then distribute the omitted (`Unit::Auto`) ones between their
73
- * neighbours. The last explicit stop is preserved: for a normal gradient it
74
- * holds that colour, and for a repeating one it defines the period. */
74
+ * line, then distribute the omitted ones between their neighbours. The last
75
+ * explicit stop is preserved: for a normal gradient it holds that colour, and
76
+ * for a repeating one it defines the period. */
75
77
  std::vector<GradientStop> resolveStops(const BackgroundLayer &layer, float line_length,
76
78
  const XtStyle &style, float viewportWidth,
77
79
  float viewportHeight) {
78
80
  std::vector<GradientStop> stops;
81
+ stops.reserve(layer.stops.size());
79
82
  for (const BackgroundGradientStop &stop : layer.stops) {
80
83
  GradientStop resolved;
81
84
  resolved.color = stop.color;
82
- resolved.offset = -1.0f;
85
+ /* NaN means "this stop has no offset"; a resolved offset may legitimately be
86
+ * negative (`red -10%`, or a `calc()` that lands below the line's start), so
87
+ * it must not double as the "unset" marker — that turned a negative offset
88
+ * into an implicit one and re-spaced the whole ramp. */
89
+ resolved.offset = std::numeric_limits<float>::quiet_NaN();
83
90
  if (stop.offset.unit == Unit::Percent) {
84
91
  resolved.offset = stop.offset.value / 100.0f;
85
92
  } else if (stop.offset.unit != Unit::Auto && stop.offset.unit != Unit::Invalid &&
86
93
  stop.offset.unit != Unit::None) {
87
- float pixels = resolveLength(stop.offset, 0.0f, style.font_size, viewportWidth,
88
- viewportHeight);
94
+ /* A percentage inside `calc()` is relative to the gradient line, exactly
95
+ * like a bare percentage; resolving it against a zero base made
96
+ * `calc(10% - 1px)` come out as `-1px`. */
97
+ float pixels =
98
+ resolveLength(stop.offset, line_length, style.font_size, viewportWidth, viewportHeight);
89
99
  resolved.offset = line_length > 0.00001f ? pixels / line_length : 0.0f;
90
100
  }
91
101
  stops.push_back(resolved);
@@ -93,24 +103,27 @@ std::vector<GradientStop> resolveStops(const BackgroundLayer &layer, float line_
93
103
  if (stops.empty()) return stops;
94
104
 
95
105
  /* Explicit offsets are non-decreasing, as in CSS. */
96
- float last = -1.0f;
106
+ bool have_previous = false;
107
+ float previous = 0.0f;
97
108
  for (GradientStop &stop : stops) {
98
- if (stop.offset < 0.0f) continue;
99
- if (stop.offset < last) stop.offset = last;
100
- last = stop.offset;
109
+ if (std::isnan(stop.offset)) continue;
110
+ if (have_previous && stop.offset < previous) stop.offset = previous;
111
+ previous = stop.offset;
112
+ have_previous = true;
101
113
  }
102
114
 
103
115
  /* Fill implicit offsets by distributing them between their neighbours. */
104
116
  size_t i = 0;
105
117
  while (i < stops.size()) {
106
- if (stops[i].offset >= 0.0f) {
118
+ if (!std::isnan(stops[i].offset)) {
107
119
  i++;
108
120
  continue;
109
121
  }
110
122
  size_t j = i;
111
- while (j < stops.size() && stops[j].offset < 0.0f) j++;
123
+ while (j < stops.size() && std::isnan(stops[j].offset)) j++;
112
124
  float before = (i == 0) ? 0.0f : stops[i - 1].offset;
113
125
  float after = (j >= stops.size()) ? 1.0f : stops[j].offset;
126
+ if (after < before) after = before;
114
127
  size_t count = j - i;
115
128
  for (size_t k = 0; k < count; k++) {
116
129
  float offset = 0.0f;
@@ -164,35 +177,108 @@ PaintClip boxClip(const PaintClip &clip, const LayoutBox &box) {
164
177
  return out;
165
178
  }
166
179
 
180
+ /** Stable identity of a rasterised gradient: the ramp plus its geometry. The
181
+ * renderer keys its texture cache by this string, so an unchanged declaration
182
+ * rasterises once. */
183
+ std::string gradientKey(const PaintGradient &gradient) {
184
+ char buffer[192];
185
+ std::string key;
186
+ for (const GradientStop &stop : gradient.stops) {
187
+ std::snprintf(buffer, sizeof(buffer), "%.4f/%.5f,%.5f,%.5f,%.5f;", stop.offset, stop.color.r,
188
+ stop.color.g, stop.color.b, stop.color.a);
189
+ key += buffer;
190
+ }
191
+ std::snprintf(buffer, sizeof(buffer), "%d;%.3f,%.3f,%.3f,%.3f;%.3f,%.3f,%.4f,%.4f,%.3f;%d;%.5f",
192
+ gradient.kind == PaintGradient::Kind::Linear ? 1 : 0, gradient.center_x,
193
+ gradient.center_y, gradient.radius_x, gradient.radius_y, gradient.start_x,
194
+ gradient.start_y, gradient.dir_x, gradient.dir_y, gradient.length,
195
+ gradient.repeating ? 1 : 0, gradient.period);
196
+ key += buffer;
197
+ return key;
198
+ }
199
+
200
+ /** The CSS gradient line of `layer` inside `box`: the box-local point where the
201
+ * ramp starts, the direction increasing offsets run in, and the line's length.
202
+ * CSS angles grow clockwise from "to top" and y grows downwards, hence the
203
+ * negated cosine. The line passes through the box centre and its length is the
204
+ * box's extent projected onto the direction, so `t` spans exactly 0..1 across
205
+ * the box and `%` offsets in `calc()` resolve against it. */
206
+ void linearLine(const LayoutBox &box, float angle, float *start_x, float *start_y, float *dir_x,
207
+ float *dir_y, float *length) {
208
+ const float radians = angle * 3.14159265358979f / 180.0f;
209
+ const float dx = std::sin(radians);
210
+ const float dy = -std::cos(radians);
211
+ const float span = std::fabs(box.width * dx) + std::fabs(box.height * dy);
212
+ *dir_x = dx;
213
+ *dir_y = dy;
214
+ *length = span;
215
+ *start_x = box.width * 0.5f - dx * span * 0.5f;
216
+ *start_y = box.height * 0.5f - dy * span * 0.5f;
217
+ }
218
+
219
+ /** Fold the colour half of the filter chain into `stops` and emit the layer as
220
+ * one gradient image. `brightness()` and `opacity()` stay out of the stops: the
221
+ * image tint carries them exactly, and leaving them out keeps the ramp — and
222
+ * therefore the texture cache key — stable while a fade animates. */
223
+ void emitGradient(const LayoutBox &box, const FilterChain &chain, const PaintClip &clip,
224
+ const std::shared_ptr<PaintGradient> &gradient,
225
+ const std::vector<GradientStop> &stops, DisplayList &out) {
226
+ FilterChain stopChain = chain;
227
+ stopChain.opacity = 1.0f;
228
+ for (size_t i = stopChain.ops.size(); i-- > 0;) {
229
+ FilterKind kind = stopChain.ops[i].kind;
230
+ if (kind == FilterKind::Brightness || kind == FilterKind::Opacity) {
231
+ stopChain.ops.erase(stopChain.ops.begin() + (std::ptrdiff_t)i);
232
+ }
233
+ }
234
+ gradient->stops.reserve(stops.size());
235
+ bool visible = false;
236
+ for (const GradientStop &stop : stops) {
237
+ GradientStop resolved = stop;
238
+ resolved.color = filterColor(stopChain, resolved.color);
239
+ visible = visible || resolved.color.a > 0.0f;
240
+ gradient->stops.push_back(resolved);
241
+ }
242
+ if (!visible) return;
243
+ gradient->key = gradientKey(*gradient);
244
+
245
+ /* One quad for the whole layer: the renderer resolves the ramp per pixel, so
246
+ * the layer composites exactly once instead of once per translucent band, and
247
+ * a stop narrower than a band keeps its exact position and width. */
248
+ PaintImage image;
249
+ image.x = box.x;
250
+ image.y = box.y;
251
+ image.width = box.width;
252
+ image.height = box.height;
253
+ image.transform = box.world;
254
+ image.gradient = gradient;
255
+ addImage(out, std::move(image), clip, chain);
256
+ }
257
+
167
258
  void paintLinear(const LayoutBox &box, const XtStyle &style, const BackgroundLayer &layer,
168
259
  const FilterChain &chain, const PaintClip &clip, float viewportWidth,
169
260
  float viewportHeight, DisplayList &out) {
170
261
  if (layer.stops.size() < 2 || box.width <= 0.0f || box.height <= 0.0f) return;
171
- /* Repeating patterns need more samples than one gradient ramp: a 10%
172
- * stripe is only two bands at the old 18-strip resolution. */
173
- const int strips = layer.repeating ? 64 : 18;
174
- bool horizontal = (layer.angle > 80.0f && layer.angle < 100.0f) ||
175
- (layer.angle > 260.0f && layer.angle < 280.0f);
176
- bool reverse = (layer.angle > 260.0f && layer.angle < 280.0f) || layer.angle < 10.0f ||
177
- layer.angle > 350.0f;
178
- const float line_length = horizontal ? box.width : box.height;
179
- std::vector<GradientStop> stops = resolveStops(layer, line_length, style, viewportWidth,
180
- viewportHeight);
262
+ float start_x = 0.0f;
263
+ float start_y = 0.0f;
264
+ float dir_x = 1.0f;
265
+ float dir_y = 0.0f;
266
+ float length = 0.0f;
267
+ linearLine(box, layer.angle, &start_x, &start_y, &dir_x, &dir_y, &length);
268
+ if (length <= 0.00001f) return;
269
+ std::vector<GradientStop> stops =
270
+ resolveStops(layer, length, style, viewportWidth, viewportHeight);
181
271
  if (stops.size() < 2) return;
182
- const float period = stops.back().offset;
183
- for (int i = 0; i < strips; i++) {
184
- float t = ((float)i + 0.5f) / (float)strips;
185
- Color color = gradientColorAt(stops, layer.repeating, period, reverse ? 1.0f - t : t);
186
- color = filterColor(chain, color);
187
- if (color.a <= 0.0f) continue;
188
- if (horizontal) {
189
- addRect(out, box.x + box.width * (float)i / (float)strips, box.y,
190
- box.width / (float)strips + 0.4f, box.height, 0.0f, color, clip, box.world);
191
- } else {
192
- addRect(out, box.x, box.y + box.height * (float)i / (float)strips, box.width,
193
- box.height / (float)strips + 0.4f, 0.0f, color, clip, box.world);
194
- }
195
- }
272
+ auto gradient = std::make_shared<PaintGradient>();
273
+ gradient->kind = PaintGradient::Kind::Linear;
274
+ gradient->start_x = start_x;
275
+ gradient->start_y = start_y;
276
+ gradient->dir_x = dir_x;
277
+ gradient->dir_y = dir_y;
278
+ gradient->length = length;
279
+ gradient->repeating = layer.repeating;
280
+ gradient->period = stops.back().offset;
281
+ emitGradient(box, chain, clip, gradient, stops, out);
196
282
  }
197
283
 
198
284
  /** Radii of a keyword-sized ellipse: it keeps the box's aspect ratio and is
@@ -283,24 +369,6 @@ void radialRadii(const LayoutBox &box, const BackgroundLayer &layer, const XtSty
283
369
  *cy = centerY;
284
370
  }
285
371
 
286
- /** Stable identity of a rasterised gradient: the ramp plus its geometry. The
287
- * renderer keys its texture cache by this string, so an unchanged declaration
288
- * rasterises once. */
289
- std::string gradientKey(const PaintGradient &gradient) {
290
- char buffer[192];
291
- std::string key;
292
- for (const GradientStop &stop : gradient.stops) {
293
- std::snprintf(buffer, sizeof(buffer), "%.4f/%.5f,%.5f,%.5f,%.5f;", stop.offset, stop.color.r,
294
- stop.color.g, stop.color.b, stop.color.a);
295
- key += buffer;
296
- }
297
- std::snprintf(buffer, sizeof(buffer), "%.3f,%.3f,%.3f,%.3f;%d;%.5f", gradient.center_x,
298
- gradient.center_y, gradient.radius_x, gradient.radius_y,
299
- gradient.repeating ? 1 : 0, gradient.period);
300
- key += buffer;
301
- return key;
302
- }
303
-
304
372
  void paintRadial(const LayoutBox &box, const XtStyle &style, const BackgroundLayer &layer,
305
373
  const FilterChain &chain, const PaintClip &clip, float viewportWidth,
306
374
  float viewportHeight, DisplayList &out) {
@@ -314,53 +382,63 @@ void paintRadial(const LayoutBox &box, const XtStyle &style, const BackgroundLay
314
382
  std::vector<GradientStop> stops = resolveStops(layer, std::max(rx, ry), style, viewportWidth,
315
383
  viewportHeight);
316
384
  if (stops.size() < 2) return;
317
- /* The colour functions fold into the stops (a per-stop application is exact
318
- * for the linear ones); `brightness()` and `opacity()` stay out, because the
319
- * image tint carries them exactly and leaving them out keeps the ramp — and
320
- * therefore the texture cache key — stable while a fade animates. */
321
- FilterChain stopChain = chain;
322
- stopChain.opacity = 1.0f;
323
- for (size_t i = stopChain.ops.size(); i-- > 0;) {
324
- FilterKind kind = stopChain.ops[i].kind;
325
- if (kind == FilterKind::Brightness || kind == FilterKind::Opacity) {
326
- stopChain.ops.erase(stopChain.ops.begin() + (ptrdiff_t)i);
327
- }
328
- }
329
385
  auto gradient = std::make_shared<PaintGradient>();
330
- gradient->stops.reserve(stops.size());
331
- bool visible = false;
332
- for (size_t i = 0; i < stops.size(); i++) {
333
- GradientStop resolved = stops[i];
334
- resolved.color = filterColor(stopChain, resolved.color);
335
- visible = visible || resolved.color.a > 0.0f;
336
- gradient->stops.push_back(resolved);
337
- }
338
- if (!visible) return;
386
+ gradient->kind = PaintGradient::Kind::Radial;
339
387
  gradient->center_x = cx - box.x;
340
388
  gradient->center_y = cy - box.y;
341
389
  gradient->radius_x = rx;
342
390
  gradient->radius_y = ry;
343
391
  gradient->repeating = layer.repeating;
344
392
  gradient->period = stops.back().offset;
345
- gradient->key = gradientKey(*gradient);
346
-
347
- /* One quad for the whole layer: the renderer resolves the ramp per pixel, so
348
- * the layer composites exactly once instead of once per translucent band. */
349
- PaintImage image;
350
- image.x = box.x;
351
- image.y = box.y;
352
- image.width = box.width;
353
- image.height = box.height;
354
- image.transform = box.world;
355
- image.gradient = gradient;
356
- addImage(out, std::move(image), clip, chain);
393
+ emitGradient(box, chain, clip, gradient, stops, out);
357
394
  }
358
395
 
359
- void paintImage(const LayoutBox &box, const BackgroundLayer &layer, const FilterChain &chain,
360
- const PaintClip &clip, DisplayList &out) {
396
+ void paintImage(const LayoutBox &box, const BackgroundLayer &layer, const XtStyle &style,
397
+ size_t index, const FilterChain &chain, const PaintClip &clip, DisplayList &out,
398
+ float viewportWidth, float viewportHeight) {
361
399
  if (layer.image_src.empty() || box.width <= 0.0f || box.height <= 0.0f) return;
362
- /* A `background-image: url(...)` layer fills the box. (CSS `background-size`
363
- * is not modelled, so the intrinsic-size/tiling default becomes a stretch.) */
400
+ const BackgroundSize *size =
401
+ index < style.background_sizes.size() ? &style.background_sizes[index] : nullptr;
402
+ if (size != nullptr) {
403
+ bool width_auto = size->width.unit == Unit::Auto || size->width.unit == Unit::Invalid ||
404
+ size->width.unit == Unit::None;
405
+ bool height_auto = size->height.unit == Unit::Auto || size->height.unit == Unit::Invalid ||
406
+ size->height.unit == Unit::None;
407
+ float tile_width =
408
+ width_auto ? box.width
409
+ : resolveLength(size->width, box.width, style.font_size, viewportWidth,
410
+ viewportHeight);
411
+ float tile_height =
412
+ height_auto ? box.height
413
+ : resolveLength(size->height, box.height, style.font_size, viewportWidth,
414
+ viewportHeight);
415
+ if (tile_width > 0.0f && tile_height > 0.0f) {
416
+ /* The default `background-repeat` is `repeat`; the size is per layer. */
417
+ int columns = (int)std::ceil(box.width / tile_width);
418
+ int rows = (int)std::ceil(box.height / tile_height);
419
+ if (columns < 1) columns = 1;
420
+ if (rows < 1) rows = 1;
421
+ const int kMaxTiles = 4096;
422
+ if (columns * rows > kMaxTiles) {
423
+ columns = 1;
424
+ rows = 1;
425
+ }
426
+ for (int row = 0; row < rows; row++) {
427
+ for (int column = 0; column < columns; column++) {
428
+ PaintImage image;
429
+ image.x = box.x + (float)column * tile_width;
430
+ image.y = box.y + (float)row * tile_height;
431
+ image.width = tile_width;
432
+ image.height = tile_height;
433
+ image.src = layer.image_src;
434
+ image.transform = box.world;
435
+ addImage(out, image, clip, chain);
436
+ }
437
+ }
438
+ return;
439
+ }
440
+ }
441
+ /* No `background-size`: keep the historical stretch to the box. */
364
442
  PaintImage image;
365
443
  image.x = box.x;
366
444
  image.y = box.y;
@@ -382,7 +460,7 @@ void paintBackgrounds(DisplayList &out, const LayoutBox &box, const XtStyle &sty
382
460
  /* The rectangle is already the border box, so the incoming clip is enough;
383
461
  * narrowing it to the box again would only change the reported clip (and
384
462
  * would clip a filtered/rounded fill differently from before M19). */
385
- addRectFiltered(out, box.x, box.y, box.width, box.height, style.border_radius,
463
+ addRectFiltered(out, box.x, box.y, box.width, box.height, box.border_radius,
386
464
  style.background, clip, box.world, chain);
387
465
  }
388
466
  /* CSS paints the first layer on top, so walk the list backwards. */
@@ -393,7 +471,7 @@ void paintBackgrounds(DisplayList &out, const LayoutBox &box, const XtStyle &sty
393
471
  } else if (layer.kind == BackgroundKind::Radial) {
394
472
  paintRadial(box, style, layer, chain, layerClip, viewportWidth, viewportHeight, out);
395
473
  } else {
396
- paintImage(box, layer, chain, layerClip, out);
474
+ paintImage(box, layer, style, i, chain, layerClip, out, viewportWidth, viewportHeight);
397
475
  }
398
476
  }
399
477
  }
@@ -228,6 +228,7 @@ void addRectFiltered(DisplayList &out, float x, float y, float width, float heig
228
228
 
229
229
  void addBoxDropShadows(DisplayList &out, const LayoutBox &box, const XtStyle &style,
230
230
  const FilterChain &chain, const PaintClip &clip) {
231
+ (void)style; // box.border_radius is the resolved form
231
232
  if (chain.shadows.empty()) return;
232
233
  for (const FilterChain::Shadow &shadow : chain.shadows) {
233
234
  Color color = filterColor(chain, shadow.color);
@@ -237,7 +238,7 @@ void addBoxDropShadows(DisplayList &out, const LayoutBox &box, const XtStyle &st
237
238
  Color copy = color;
238
239
  if (shadow.blur > 0.0f) copy.a *= kBlurTapWeight;
239
240
  addRect(out, box.x + shadow.dx + tap.dx, box.y + shadow.dy + tap.dy, box.width, box.height,
240
- style.border_radius, copy, clip, box.world);
241
+ box.border_radius, copy, clip, box.world);
241
242
  }
242
243
  }
243
244
  }
@@ -274,7 +275,9 @@ void addTextRun(DisplayList &out, float x, float baseline, float width, float he
274
275
  }
275
276
 
276
277
  /* `text-shadow`, bottom-most first: CSS paints later layers under earlier
277
- * ones, so reverse the declaration list. */
278
+ * ones, so reverse the declaration list. A zero-blur layer stays a crisp text
279
+ * run at an offset; a blurred one is a real Gaussian of the glyph coverage,
280
+ * composited as one tinted texture. */
278
281
  for (size_t index = style.text_shadow.size(); index-- > 0;) {
279
282
  const TextShadow &shadow = style.text_shadow[index];
280
283
  float dx = resolveLength(shadow.dx, 0.0f, style.font_size, viewportWidth, viewportHeight);
@@ -282,11 +285,14 @@ void addTextRun(DisplayList &out, float x, float baseline, float width, float he
282
285
  float blur =
283
286
  resolveLength(shadow.blur, 0.0f, style.font_size, viewportWidth, viewportHeight);
284
287
  Color shadowColor = shadow.has_color ? filterColor(chain, shadow.color) : color;
285
- for (const Tap &tap : blurTaps(blur)) {
286
- Color copy = shadowColor;
287
- if (blur > 0.0f) copy.a *= kBlurTapWeight;
288
- emit(dx + tap.dx, dy + tap.dy, copy, true);
288
+ if (blur <= 0.0f) {
289
+ emit(dx, dy, shadowColor, true);
290
+ continue;
289
291
  }
292
+ std::shared_ptr<const PaintBitmap> mask = textShadowMask(text, font, blur);
293
+ if (mask == nullptr) continue;
294
+ addBitmapImage(out, mask, x + dx + mask->origin_x, baseline + dy + mask->origin_y,
295
+ shadowColor, transform, clip);
290
296
  }
291
297
 
292
298
  /* `filter: blur()` spreads the glyphs themselves. */
@@ -72,10 +72,37 @@ void paintBackgrounds(DisplayList &out, const LayoutBox &box, const XtStyle &sty
72
72
  const FilterChain &chain, const PaintClip &clip, float viewportWidth,
73
73
  float viewportHeight);
74
74
 
75
+ /** Append the `outline` edge rectangles (after the border, before content). */
76
+ void addOutline(DisplayList &out, const LayoutBox &box, const XtStyle &style,
77
+ const FilterChain &chain, const PaintClip &clip, float viewportWidth,
78
+ float viewportHeight);
79
+
75
80
  /** Append the drop-shadow silhouettes of a box (once, before its content). */
76
81
  void addBoxDropShadows(DisplayList &out, const LayoutBox &box, const XtStyle &style,
77
82
  const FilterChain &chain, const PaintClip &clip);
78
83
 
84
+ /** Append the `box-shadow` layers of a box. Outer shadows paint behind the
85
+ * background; inset shadows paint above it (call with `inset = true` for the
86
+ * latter). Declaration order is topmost first, so the list is reversed. */
87
+ void addBoxShadows(DisplayList &out, const LayoutBox &box, const XtStyle &style,
88
+ const FilterChain &chain, const PaintClip &clip, bool inset,
89
+ float viewportWidth, float viewportHeight);
90
+
91
+ /** Rasterise (or fetch from the bounded cache) the blurred glyph mask of a
92
+ * `text-shadow` layer. Returns null when the run has no visible glyphs. */
93
+ std::shared_ptr<const PaintBitmap> textShadowMask(const std::string &text, const FontSpec &font,
94
+ float blur);
95
+
96
+ /** Append a bitmap-backed image op (a rasterised shadow) with `color` as its
97
+ * tint. The bitmap now owns its pixels; the renderer uploads it once. */
98
+ void addBitmapImage(DisplayList &out, std::shared_ptr<const PaintBitmap> bitmap, float x, float y,
99
+ const Color &color, const Matrix2D &transform, const PaintClip &clip);
100
+
101
+ /** Resolve and append a `backdrop-filter` op for a box (before its
102
+ * background). Returns false when the filter list has no visible effect. */
103
+ bool addBackdropFilter(DisplayList &out, const LayoutBox &box, const XtStyle &style,
104
+ const PaintClip &clip, float viewportWidth, float viewportHeight);
105
+
79
106
  /** Resolve `clip-path` into a device-space triangulated polygon. */
80
107
  int addClipPathForBox(DisplayList &out, const LayoutBox &box, const XtStyle &style,
81
108
  float viewportWidth, float viewportHeight);