xbintsc 0.3.68 → 0.3.79
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.
- package/README.md +59 -4
- package/README.zh-CN.md +52 -2
- package/dist/tests/e2e/gui-advanced.test.d.ts +6 -0
- package/dist/tests/e2e/gui-advanced.test.js +261 -0
- package/dist/tests/e2e/gui-advanced.test.js.map +1 -0
- package/dist/tests/e2e/gui-canvas.test.d.ts +9 -0
- package/dist/tests/e2e/gui-canvas.test.js +233 -0
- package/dist/tests/e2e/gui-canvas.test.js.map +1 -0
- package/dist/tests/e2e/gui-example.test.d.ts +1 -1
- package/dist/tests/e2e/gui-example.test.js +2 -2
- package/dist/tests/e2e/gui-helpers.d.ts +3 -0
- package/dist/tests/e2e/gui-helpers.js +5 -0
- package/dist/tests/e2e/gui-helpers.js.map +1 -1
- package/dist/tests/e2e/gui-layout.test.js +73 -0
- package/dist/tests/e2e/gui-layout.test.js.map +1 -1
- package/dist/tests/e2e/gui-media.test.d.ts +8 -0
- package/dist/tests/e2e/gui-media.test.js +119 -0
- package/dist/tests/e2e/gui-media.test.js.map +1 -0
- package/dist/tests/e2e/gui-paint.test.js +40 -0
- package/dist/tests/e2e/gui-paint.test.js.map +1 -1
- package/doc/ai/extensions.md +16 -3
- package/doc/ai/zh-CN/extensions.md +10 -3
- package/doc/gui/animation.md +46 -0
- package/doc/gui/api.md +92 -0
- package/doc/gui/build.md +91 -0
- package/doc/gui/canvas.md +98 -0
- package/doc/gui/dom.md +123 -0
- package/doc/gui/input.md +22 -0
- package/doc/gui/known-issues.md +66 -0
- package/doc/gui/layout.md +72 -0
- package/doc/gui/media.md +87 -0
- package/doc/gui/rendering.md +98 -0
- package/doc/gui/runtime.md +88 -0
- package/doc/gui-scripts.md +7 -3
- package/doc/gui.md +80 -612
- package/doc/implemented.md +1 -0
- package/doc/zh-CN/gui/animation.md +46 -0
- package/doc/zh-CN/gui/api.md +88 -0
- package/doc/zh-CN/gui/build.md +84 -0
- package/doc/zh-CN/gui/canvas.md +80 -0
- package/doc/zh-CN/gui/dom.md +114 -0
- package/doc/zh-CN/gui/input.md +21 -0
- package/doc/zh-CN/gui/known-issues.md +50 -0
- package/doc/zh-CN/gui/layout.md +60 -0
- package/doc/zh-CN/gui/media.md +71 -0
- package/doc/zh-CN/gui/rendering.md +81 -0
- package/doc/zh-CN/gui/runtime.md +76 -0
- package/doc/zh-CN/gui-scripts.md +6 -2
- package/doc/zh-CN/gui.md +64 -532
- package/doc/zh-CN/implemented.md +1 -0
- package/doc/zh-CN/post/xbintsc-build-pelican.md +2 -2
- package/llms.txt +15 -2
- package/package.json +1 -1
- package/runtime/ext_gui/canvas.cpp +580 -0
- package/runtime/ext_gui/canvas.h +170 -0
- package/runtime/ext_gui/canvas_internal.h +59 -0
- package/runtime/ext_gui/canvas_raster.cpp +254 -0
- package/runtime/ext_gui/document.cpp +24 -1
- package/runtime/ext_gui/document.h +11 -0
- package/runtime/ext_gui/dom_api.cpp +3 -0
- package/runtime/ext_gui/dom_api_canvas.cpp +548 -0
- package/runtime/ext_gui/dom_api_internal.h +73 -0
- package/runtime/ext_gui/dom_api_media.cpp +274 -0
- package/runtime/ext_gui/dom_api_proto.cpp +80 -0
- package/runtime/ext_gui/gui.cpp +6 -0
- package/runtime/ext_gui/gui_canvas.cpp +95 -0
- package/runtime/ext_gui/gui_engine.h +49 -0
- package/runtime/ext_gui/gui_events.cpp +21 -5
- package/runtime/ext_gui/gui_media.cpp +193 -0
- package/runtime/ext_gui/image.cpp +31 -12
- package/runtime/ext_gui/image.h +20 -7
- package/runtime/ext_gui/layout.cpp +52 -5
- package/runtime/ext_gui/layout.h +33 -1
- package/runtime/ext_gui/layout_flex.cpp +263 -99
- package/runtime/ext_gui/layout_flow.cpp +85 -9
- package/runtime/ext_gui/layout_flow_internal.h +84 -5
- package/runtime/ext_gui/layout_inline.cpp +54 -37
- package/runtime/ext_gui/media.cpp +514 -0
- package/runtime/ext_gui/media.h +104 -0
- package/runtime/ext_gui/nanosvg.h +3278 -0
- package/runtime/ext_gui/nanosvgrast.h +1472 -0
- package/runtime/ext_gui/paint.cpp +123 -38
- package/runtime/ext_gui/paint.h +36 -6
- package/runtime/ext_gui/renderer.cpp +113 -10
- package/runtime/ext_gui/renderer.h +49 -9
- package/runtime/ext_gui/renderer_image.cpp +203 -77
- package/runtime/ext_gui/renderer_internal.h +6 -8
- package/runtime/ext_gui/renderer_text.cpp +11 -26
- package/runtime/ext_gui/style.h +11 -0
- package/runtime/ext_gui/style_print.cpp +11 -0
- package/runtime/ext_gui/style_ua.cpp +7 -0
- package/runtime/ext_gui/style_values.cpp +40 -1
- package/runtime/ext_gui/svg.cpp +182 -0
- package/runtime/ext_gui/svg.h +46 -0
- package/runtime/ext_gui/text.cpp +156 -25
- package/runtime/ext_gui/text.h +11 -2
- package/runtime/ext_gui/window.cpp +4 -1
- package/scripts/build-gui.ts +148 -6
- package/scripts/check-file-length.ts +5 -1
- package/scripts/install-gui-deps.sh +28 -0
package/doc/zh-CN/implemented.md
CHANGED
|
@@ -358,6 +358,7 @@ const result = build("program.ts", { emit: "exe", outDir: "build" });
|
|
|
358
358
|
| 值模型 | 64 位 NaN-boxing、统一函数 ABI(含 `this`)、闭包环境、对象原型链 |
|
|
359
359
|
| 运行时 | 字符串 / 对象 / 数组 / 类型化数组 / 闭包 / 算术 / 比较 / 可捕获异常 / Promise / 集合 / symbol / 生成器 / `fetch` / `console` |
|
|
360
360
|
| 扩展 | 扩展注册表、`core`(print)、`node`(fs / path / os / process / buffer / stream / net / dgram / http,按说明符导入) |
|
|
361
|
+
| GUI(`gui` 扩展) | 自托管 HTML/CSS 渲染器窗口(SDL3 + SDL_GPU):DOM/CSS/布局、`<img>`/SVG、`<video>`/`<audio>`、`<canvas>` 2D、事件、transition |
|
|
361
362
|
| 工具链 | clang 编译 IR/C、链接、增量缓存 |
|
|
362
363
|
| 项目配置 | `xbintsc.config.json`(`entry` / `outDir` / `optimize` / `extensions` / `app`),支持查找与 CLI 优先级 |
|
|
363
364
|
| 应用图标 | 编译期内嵌(PNG/ICO/ICNS),Windows PE 资源、macOS `.app` bundle、`gui` 运行时窗口/Dock 图标 |
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
|
|
20
20
|
当然有兴趣下载鹈鹕骑车小游戏编译结果,也编译了多个不同平台windows/mac/ubutun26+的版本。
|
|
21
21
|
|
|
22
|
-
编译结果地址:[https://github.com/zy445566/xbintsc/releases/tag/v0.3.
|
|
22
|
+
编译结果地址:[https://github.com/zy445566/xbintsc/releases/tag/v0.3.69](https://github.com/zy445566/xbintsc/releases/tag/v0.3.69)
|
|
23
23
|
|
|
24
|
-
选择pelican-bike-demo-v0.3.
|
|
24
|
+
选择pelican-bike-demo-v0.3.69-multi-platform.zip下载,解压后选择自己的平台就能运行。
|
|
25
25
|
|
|
26
26
|
由于该游戏目前没有做签名,所以可能会有系统的安全性拦截。
|
|
27
27
|
macOS 打开后需要:打开“系统设置” → “隐私与安全性”,向下滚动到“安全性”,选择“仍要打开”运行。
|
package/llms.txt
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
> xbintsc compiles a subset of TypeScript directly to native binaries: it parses
|
|
4
4
|
> TypeScript itself, lowers the program to LLVM IR text, and drives clang to
|
|
5
5
|
> produce a standalone executable linked against a small C runtime. There is no
|
|
6
|
-
> Node.js, V8 or TypeScript compiler inside the produced binary.
|
|
6
|
+
> Node.js, V8 or TypeScript compiler inside the produced binary. An optional
|
|
7
|
+
> `gui` extension links a self-hosted, GPU-accelerated HTML/CSS renderer, so the
|
|
8
|
+
> same TypeScript can also build a native desktop application whose UI is
|
|
9
|
+
> HTML/CSS (no system WebView, no runtime JS engine). It runs on
|
|
7
10
|
> macOS, Linux and Windows (x64 and arm64), compiles itself, and ships prebuilt
|
|
8
11
|
> archives per platform. A clang 16+ toolchain is required to link binaries;
|
|
9
12
|
> `xbintsc emit` (LLVM IR only) needs nothing.
|
|
@@ -32,7 +35,17 @@
|
|
|
32
35
|
- [doc/unimplemented.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/unimplemented.md): unsupported syntax, missing semantics and known deviations, with a quick-reference list
|
|
33
36
|
- [doc/node-implemented.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/node-implemented.md): Node module coverage of the `node` extension
|
|
34
37
|
- [doc/node-unimplemented.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/node-unimplemented.md): what the `node` extension is still missing
|
|
35
|
-
- [doc/gui.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/gui.md): the self-hosted GPU HTML/CSS renderer
|
|
38
|
+
- [doc/gui.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/gui.md): the self-hosted GPU HTML/CSS renderer — overview, architecture and roadmap
|
|
39
|
+
- [doc/gui/api.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/gui/api.md): the TypeScript window API, events and diagnostics hooks
|
|
40
|
+
- [doc/gui/dom.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/gui/dom.md): HTML/CSS parsing, the cascade, the interactive DOM and AOT scripts
|
|
41
|
+
- [doc/gui/layout.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/gui/layout.md): block, inline, Flexbox and CSS positioning
|
|
42
|
+
- [doc/gui/rendering.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/gui/rendering.md): the display list, SDL_GPU pipelines and the text stack
|
|
43
|
+
- [doc/gui/media.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/gui/media.md): images, SVG, video and audio
|
|
44
|
+
- [doc/gui/input.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/gui/input.md): hit testing, events and `:hover`/`:focus`
|
|
45
|
+
- [doc/gui/animation.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/gui/animation.md): `requestAnimationFrame` and CSS transitions
|
|
46
|
+
- [doc/gui/runtime.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/gui/runtime.md): event loop integration, the native bridge and memory
|
|
47
|
+
- [doc/gui/build.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/gui/build.md): building `gui.a` and running the examples
|
|
48
|
+
- [doc/gui/known-issues.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/gui/known-issues.md): known issues and open questions
|
|
36
49
|
- [doc/gui-scripts.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/gui-scripts.md): the GUI scripting surface
|
|
37
50
|
- [doc/icon.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/icon.md): application icons and project config packaging
|
|
38
51
|
- [examples/extensions/README.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/examples/extensions/README.md): authoring C++ and Rust native extensions
|
package/package.json
CHANGED
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — the Canvas2D object model.
|
|
3
|
+
*
|
|
4
|
+
* This unit owns the drawing state, the current path and the draw entry
|
|
5
|
+
* points; the actual pixel work is delegated to `canvas_raster.cpp`. Canvases
|
|
6
|
+
* are registered in a process-wide id table so the GPU renderer can find the
|
|
7
|
+
* bitmap behind a display-list entry without knowing about the DOM.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#include "canvas.h"
|
|
11
|
+
|
|
12
|
+
#include <algorithm>
|
|
13
|
+
#include <atomic>
|
|
14
|
+
#include <cmath>
|
|
15
|
+
#include <cstdlib>
|
|
16
|
+
#include <cstring>
|
|
17
|
+
#include <unordered_map>
|
|
18
|
+
|
|
19
|
+
#include "canvas_internal.h"
|
|
20
|
+
#include "css.h"
|
|
21
|
+
|
|
22
|
+
namespace xtgui {
|
|
23
|
+
|
|
24
|
+
namespace {
|
|
25
|
+
|
|
26
|
+
std::atomic<uint64_t> g_next_canvas_id{1};
|
|
27
|
+
|
|
28
|
+
/** Process-wide id -> canvas table. The engine is single-threaded; the table
|
|
29
|
+
* lives for the process while the objects are owned by the window. */
|
|
30
|
+
std::unordered_map<uint64_t, Canvas2D *> &canvas_registry() {
|
|
31
|
+
static std::unordered_map<uint64_t, Canvas2D *> registry;
|
|
32
|
+
return registry;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
std::string lower_ascii(const std::string &value) {
|
|
36
|
+
std::string out = value;
|
|
37
|
+
for (char &c : out) {
|
|
38
|
+
if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a');
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
bool parse_font_size_token(const std::string &token, float fallback, float *out) {
|
|
44
|
+
std::string lower = lower_ascii(token);
|
|
45
|
+
struct Suffix {
|
|
46
|
+
const char *text;
|
|
47
|
+
float factor;
|
|
48
|
+
};
|
|
49
|
+
static const Suffix suffixes[] = {{"px", 1.0f}, {"pt", 96.0f / 72.0f}, {"pc", 16.0f},
|
|
50
|
+
{"in", 96.0f}, {"cm", 96.0f / 2.54f}, {"mm", 96.0f / 25.4f},
|
|
51
|
+
{"q", 96.0f / 101.6f}, {"em", 0.0f}, {"rem", 0.0f}};
|
|
52
|
+
for (const Suffix &suffix : suffixes) {
|
|
53
|
+
size_t length = std::strlen(suffix.text);
|
|
54
|
+
if (lower.size() <= length || lower.compare(lower.size() - length, length, suffix.text) != 0)
|
|
55
|
+
continue;
|
|
56
|
+
char *end = nullptr;
|
|
57
|
+
float value = std::strtof(lower.c_str(), &end);
|
|
58
|
+
if (end == lower.c_str()) return false;
|
|
59
|
+
if (suffix.factor == 0.0f) {
|
|
60
|
+
*out = value * fallback;
|
|
61
|
+
} else {
|
|
62
|
+
*out = value * suffix.factor;
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Parse the CSS `font` shorthand into a `FontSpec` (the pieces Canvas2D
|
|
70
|
+
* understands; unknown tokens are ignored). */
|
|
71
|
+
FontSpec parse_font_string(const std::string &text) {
|
|
72
|
+
FontSpec spec;
|
|
73
|
+
spec.family = "sans-serif";
|
|
74
|
+
spec.pixel_size = 10.0f;
|
|
75
|
+
spec.weight = 400;
|
|
76
|
+
spec.italic = false;
|
|
77
|
+
std::vector<std::string> tokens = xt_css_split_whitespace(text);
|
|
78
|
+
std::vector<std::string> family;
|
|
79
|
+
float size = 10.0f;
|
|
80
|
+
bool have_size = false;
|
|
81
|
+
for (const std::string &token : tokens) {
|
|
82
|
+
std::string lower = lower_ascii(token);
|
|
83
|
+
float parsed = 0.0f;
|
|
84
|
+
if (!have_size && parse_font_size_token(lower, 16.0f, &parsed)) {
|
|
85
|
+
size = parsed;
|
|
86
|
+
have_size = true;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (lower == "italic" || lower == "oblique") {
|
|
90
|
+
spec.italic = true;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (lower == "bold") {
|
|
94
|
+
spec.weight = 700;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (lower == "bolder") {
|
|
98
|
+
spec.weight = 700;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (lower == "lighter") {
|
|
102
|
+
spec.weight = 300;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (lower == "normal" || lower == "small-caps") continue;
|
|
106
|
+
if (!lower.empty() && lower.back() == ',') lower.pop_back();
|
|
107
|
+
if (!lower.empty()) family.push_back(lower);
|
|
108
|
+
}
|
|
109
|
+
spec.pixel_size = size;
|
|
110
|
+
if (!family.empty()) spec.family = family[0];
|
|
111
|
+
return spec;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
void parse_color_into(const std::string &css, float color[4], std::string *stored) {
|
|
115
|
+
Color parsed = xt_css_parse_color(css);
|
|
116
|
+
if (!parsed.valid) return;
|
|
117
|
+
color[0] = parsed.r;
|
|
118
|
+
color[1] = parsed.g;
|
|
119
|
+
color[2] = parsed.b;
|
|
120
|
+
color[3] = parsed.a;
|
|
121
|
+
*stored = css;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
CanvasPoint lerp(const CanvasPoint &a, const CanvasPoint &b, float t) {
|
|
125
|
+
return CanvasPoint{a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
} // namespace
|
|
129
|
+
|
|
130
|
+
Canvas2D* xt_canvas_by_id(uint64_t id) {
|
|
131
|
+
auto found = canvas_registry().find(id);
|
|
132
|
+
return found == canvas_registry().end() ? nullptr : found->second;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
float CanvasTransform::scale() const {
|
|
136
|
+
float determinant = std::fabs(a * d - b * c);
|
|
137
|
+
return std::sqrt(determinant);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
Canvas2D::Canvas2D(int width, int height) : id_(g_next_canvas_id.fetch_add(1)) {
|
|
141
|
+
resize(width, height);
|
|
142
|
+
canvas_registry()[id_] = this;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
Canvas2D::~Canvas2D() {
|
|
146
|
+
std::unordered_map<uint64_t, Canvas2D *> ®istry = canvas_registry();
|
|
147
|
+
auto found = registry.find(id_);
|
|
148
|
+
if (found != registry.end() && found->second == this) registry.erase(found);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
void Canvas2D::resize(int width, int height) {
|
|
152
|
+
if (width < 1) width = 1;
|
|
153
|
+
if (height < 1) height = 1;
|
|
154
|
+
image_.width = width;
|
|
155
|
+
image_.height = height;
|
|
156
|
+
image_.pixels.assign((size_t)width * (size_t)height * 4, 0);
|
|
157
|
+
generation_++;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
void Canvas2D::save() { stack_.push_back(state_); }
|
|
161
|
+
|
|
162
|
+
void Canvas2D::restore() {
|
|
163
|
+
if (stack_.empty()) return;
|
|
164
|
+
state_ = stack_.back();
|
|
165
|
+
stack_.pop_back();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
void Canvas2D::translate(float x, float y) {
|
|
169
|
+
transform(1.0f, 0.0f, 0.0f, 1.0f, x, y);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
void Canvas2D::rotate(float radians) {
|
|
173
|
+
float c = std::cos(radians);
|
|
174
|
+
float s = std::sin(radians);
|
|
175
|
+
transform(c, s, -s, c, 0.0f, 0.0f);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
void Canvas2D::scale(float sx, float sy) { transform(sx, 0.0f, 0.0f, sy, 0.0f, 0.0f); }
|
|
179
|
+
|
|
180
|
+
void Canvas2D::transform(float a, float b, float c, float d, float e, float f) {
|
|
181
|
+
const CanvasTransform &m = state_.transform;
|
|
182
|
+
CanvasTransform next;
|
|
183
|
+
next.a = a * m.a + c * m.b;
|
|
184
|
+
next.b = b * m.a + d * m.b;
|
|
185
|
+
next.c = a * m.c + c * m.d;
|
|
186
|
+
next.d = b * m.c + d * m.d;
|
|
187
|
+
next.e = a * m.e + c * m.f + e;
|
|
188
|
+
next.f = b * m.e + d * m.f + f;
|
|
189
|
+
state_.transform = next;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
void Canvas2D::set_transform(float a, float b, float c, float d, float e, float f) {
|
|
193
|
+
state_.transform = CanvasTransform{a, b, c, d, e, f};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
void Canvas2D::set_fill_style(const std::string &css) {
|
|
197
|
+
parse_color_into(css, state_.fill_color, &state_.fill_style);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
void Canvas2D::set_stroke_style(const std::string &css) {
|
|
201
|
+
parse_color_into(css, state_.stroke_color, &state_.stroke_style);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
void Canvas2D::set_global_alpha(float alpha) {
|
|
205
|
+
if (alpha < 0.0f) alpha = 0.0f;
|
|
206
|
+
if (alpha > 1.0f) alpha = 1.0f;
|
|
207
|
+
state_.global_alpha = alpha;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
void Canvas2D::set_font(const std::string &font) {
|
|
211
|
+
state_.font = parse_font_string(font);
|
|
212
|
+
state_.font_string = font;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
void Canvas2D::set_text_align(const std::string &value) {
|
|
216
|
+
std::string lower = lower_ascii(value);
|
|
217
|
+
if (lower == "start" || lower == "left") {
|
|
218
|
+
state_.text_align = 0;
|
|
219
|
+
} else if (lower == "center") {
|
|
220
|
+
state_.text_align = 1;
|
|
221
|
+
} else if (lower == "right" || lower == "end") {
|
|
222
|
+
state_.text_align = 2;
|
|
223
|
+
} else {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
state_.text_align_string = lower;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
void Canvas2D::set_text_baseline(const std::string &value) {
|
|
230
|
+
std::string lower = lower_ascii(value);
|
|
231
|
+
if (lower == "alphabetic") {
|
|
232
|
+
state_.text_baseline = 0;
|
|
233
|
+
} else if (lower == "top") {
|
|
234
|
+
state_.text_baseline = 1;
|
|
235
|
+
} else if (lower == "middle") {
|
|
236
|
+
state_.text_baseline = 2;
|
|
237
|
+
} else if (lower == "bottom") {
|
|
238
|
+
state_.text_baseline = 3;
|
|
239
|
+
} else if (lower == "hanging") {
|
|
240
|
+
state_.text_baseline = 4;
|
|
241
|
+
} else if (lower == "ideographic") {
|
|
242
|
+
state_.text_baseline = 5;
|
|
243
|
+
} else {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
state_.text_baseline_string = lower;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
void Canvas2D::begin_path() { path_.clear(); }
|
|
250
|
+
|
|
251
|
+
void Canvas2D::move_to(float x, float y) { path_.push_back(CanvasSubpath{CanvasPoint{x, y}}); }
|
|
252
|
+
|
|
253
|
+
void Canvas2D::line_to(float x, float y) {
|
|
254
|
+
if (path_.empty()) path_.push_back(CanvasSubpath{});
|
|
255
|
+
path_.back().push_back(CanvasPoint{x, y});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
void Canvas2D::close_path() {
|
|
259
|
+
if (path_.empty() || path_.back().size() < 2) return;
|
|
260
|
+
CanvasPoint first = path_.back().front();
|
|
261
|
+
CanvasPoint last = path_.back().back();
|
|
262
|
+
if (first.x != last.x || first.y != last.y) path_.back().push_back(first);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
void Canvas2D::rect(float x, float y, float width, float height) {
|
|
266
|
+
path_.push_back(CanvasSubpath{CanvasPoint{x, y}, CanvasPoint{x + width, y},
|
|
267
|
+
CanvasPoint{x + width, y + height}, CanvasPoint{x, y + height},
|
|
268
|
+
CanvasPoint{x, y}});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
void Canvas2D::flatten_arc(float cx, float cy, float radius, float start, float end, bool ccw,
|
|
272
|
+
CanvasSubpath *out) const {
|
|
273
|
+
float sweep = end - start;
|
|
274
|
+
if (ccw) {
|
|
275
|
+
while (sweep > 0.0f) sweep -= 2.0f * 3.14159265358979323846f;
|
|
276
|
+
while (sweep < -2.0f * 3.14159265358979323846f) sweep += 2.0f * 3.14159265358979323846f;
|
|
277
|
+
} else {
|
|
278
|
+
while (sweep < 0.0f) sweep += 2.0f * 3.14159265358979323846f;
|
|
279
|
+
while (sweep > 2.0f * 3.14159265358979323846f) sweep -= 2.0f * 3.14159265358979323846f;
|
|
280
|
+
}
|
|
281
|
+
int segments = (int)std::ceil(std::fabs(sweep) / (3.14159265358979323846f / 32.0f));
|
|
282
|
+
if (segments < 1) segments = 1;
|
|
283
|
+
if (segments > 2048) segments = 2048;
|
|
284
|
+
for (int i = 0; i <= segments; i++) {
|
|
285
|
+
float angle = start + sweep * ((float)i / (float)segments);
|
|
286
|
+
out->push_back(CanvasPoint{cx + radius * std::cos(angle), cy + radius * std::sin(angle)});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
void Canvas2D::arc(float cx, float cy, float radius, float start, float end, bool ccw) {
|
|
291
|
+
CanvasSubpath arcPoints;
|
|
292
|
+
flatten_arc(cx, cy, radius, start, end, ccw, &arcPoints);
|
|
293
|
+
if (arcPoints.empty()) return;
|
|
294
|
+
if (path_.empty() || path_.back().empty()) {
|
|
295
|
+
path_.push_back(CanvasSubpath{arcPoints.front()});
|
|
296
|
+
} else {
|
|
297
|
+
CanvasPoint last = path_.back().back();
|
|
298
|
+
if (last.x != arcPoints.front().x || last.y != arcPoints.front().y) {
|
|
299
|
+
path_.back().push_back(arcPoints.front());
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
for (size_t i = 1; i < arcPoints.size(); i++) path_.back().push_back(arcPoints[i]);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
void Canvas2D::quadratic_curve_to(float cpx, float cpy, float x, float y) {
|
|
306
|
+
if (path_.empty() || path_.back().empty()) move_to(cpx, cpy);
|
|
307
|
+
CanvasPoint start = path_.back().back();
|
|
308
|
+
CanvasPoint control{cpx, cpy};
|
|
309
|
+
CanvasPoint target{x, y};
|
|
310
|
+
const int segments = 16;
|
|
311
|
+
for (int i = 1; i <= segments; i++) {
|
|
312
|
+
float t = (float)i / (float)segments;
|
|
313
|
+
CanvasPoint a = lerp(start, control, t);
|
|
314
|
+
CanvasPoint b = lerp(control, target, t);
|
|
315
|
+
CanvasPoint point = lerp(a, b, t);
|
|
316
|
+
path_.back().push_back(point);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
void Canvas2D::bezier_curve_to(float c1x, float c1y, float c2x, float c2y, float x, float y) {
|
|
321
|
+
if (path_.empty() || path_.back().empty()) move_to(c1x, c1y);
|
|
322
|
+
CanvasPoint p0 = path_.back().back();
|
|
323
|
+
CanvasPoint p1{c1x, c1y};
|
|
324
|
+
CanvasPoint p2{c2x, c2y};
|
|
325
|
+
CanvasPoint p3{x, y};
|
|
326
|
+
const int segments = 24;
|
|
327
|
+
for (int i = 1; i <= segments; i++) {
|
|
328
|
+
float t = (float)i / (float)segments;
|
|
329
|
+
float u = 1.0f - t;
|
|
330
|
+
CanvasPoint point{u * u * u * p0.x + 3.0f * u * u * t * p1.x + 3.0f * u * t * t * p2.x + t * t * t * p3.x,
|
|
331
|
+
u * u * u * p0.y + 3.0f * u * u * t * p1.y + 3.0f * u * t * t * p2.y + t * t * t * p3.y};
|
|
332
|
+
path_.back().push_back(point);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
void Canvas2D::to_device(float x, float y, float *outX, float *outY) const {
|
|
337
|
+
state_.transform.apply(x, y, outX, outY);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
std::vector<CanvasSubpath> Canvas2D::device_subpaths() const {
|
|
341
|
+
std::vector<CanvasSubpath> result;
|
|
342
|
+
result.reserve(path_.size());
|
|
343
|
+
for (const CanvasSubpath &subpath : path_) {
|
|
344
|
+
CanvasSubpath device;
|
|
345
|
+
device.reserve(subpath.size());
|
|
346
|
+
for (const CanvasPoint &point : subpath) {
|
|
347
|
+
CanvasPoint mapped;
|
|
348
|
+
to_device(point.x, point.y, &mapped.x, &mapped.y);
|
|
349
|
+
device.push_back(mapped);
|
|
350
|
+
}
|
|
351
|
+
result.push_back(std::move(device));
|
|
352
|
+
}
|
|
353
|
+
return result;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
void Canvas2D::fill() {
|
|
357
|
+
std::vector<CanvasSubpath> subpaths = device_subpaths();
|
|
358
|
+
std::vector<CanvasSubpath> loops;
|
|
359
|
+
for (CanvasSubpath &subpath : subpaths) {
|
|
360
|
+
if (subpath.size() < 3) continue;
|
|
361
|
+
loops.push_back(std::move(subpath));
|
|
362
|
+
}
|
|
363
|
+
if (loops.empty()) return;
|
|
364
|
+
canvas_detail::fill_loops(image_.pixels, image_.width, image_.height, loops, state_.fill_color,
|
|
365
|
+
state_.global_alpha);
|
|
366
|
+
generation_++;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
void Canvas2D::stroke_subpath(const CanvasSubpath &points, bool closed, const float color[4]) {
|
|
370
|
+
if (points.size() < 2) return;
|
|
371
|
+
float half = state_.line_width * state_.transform.scale() * 0.5f;
|
|
372
|
+
if (half <= 0.0f) half = 0.5f;
|
|
373
|
+
size_t segmentCount = points.size() - 1;
|
|
374
|
+
for (size_t i = 0; i < segmentCount; i++) {
|
|
375
|
+
const CanvasPoint &p = points[i];
|
|
376
|
+
const CanvasPoint &q = points[i + 1];
|
|
377
|
+
float dx = q.x - p.x;
|
|
378
|
+
float dy = q.y - p.y;
|
|
379
|
+
float length = std::sqrt(dx * dx + dy * dy);
|
|
380
|
+
if (length <= 0.0001f) continue;
|
|
381
|
+
float nx = -dy / length * half;
|
|
382
|
+
float ny = dx / length * half;
|
|
383
|
+
CanvasSubpath quad{CanvasPoint{p.x + nx, p.y + ny}, CanvasPoint{q.x + nx, q.y + ny},
|
|
384
|
+
CanvasPoint{q.x - nx, q.y - ny}, CanvasPoint{p.x - nx, p.y - ny}};
|
|
385
|
+
canvas_detail::fill_loop(image_.pixels, image_.width, image_.height, quad, color,
|
|
386
|
+
state_.global_alpha);
|
|
387
|
+
}
|
|
388
|
+
(void)closed;
|
|
389
|
+
for (const CanvasPoint &p : points) {
|
|
390
|
+
CanvasSubpath disc;
|
|
391
|
+
const int segments = 16;
|
|
392
|
+
for (int i = 0; i < segments; i++) {
|
|
393
|
+
float angle = 2.0f * 3.14159265358979323846f * ((float)i / (float)segments);
|
|
394
|
+
disc.push_back(CanvasPoint{p.x + half * std::cos(angle), p.y + half * std::sin(angle)});
|
|
395
|
+
}
|
|
396
|
+
canvas_detail::fill_loop(image_.pixels, image_.width, image_.height, disc, color,
|
|
397
|
+
state_.global_alpha);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
void Canvas2D::stroke() {
|
|
402
|
+
std::vector<CanvasSubpath> subpaths = device_subpaths();
|
|
403
|
+
for (const CanvasSubpath &subpath : subpaths) {
|
|
404
|
+
if (subpath.size() < 2) continue;
|
|
405
|
+
bool closed = subpath.size() >= 3 && subpath.front().x == subpath.back().x &&
|
|
406
|
+
subpath.front().y == subpath.back().y;
|
|
407
|
+
stroke_subpath(subpath, closed, state_.stroke_color);
|
|
408
|
+
}
|
|
409
|
+
generation_++;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
void Canvas2D::clear_rect(float x, float y, float width, float height) {
|
|
413
|
+
float x0, y0, x1, y1;
|
|
414
|
+
to_device(x, y, &x0, &y0);
|
|
415
|
+
to_device(x + width, y + height, &x1, &y1);
|
|
416
|
+
canvas_detail::clear_rect(image_.pixels, image_.width, image_.height, x0, y0, x1, y1);
|
|
417
|
+
generation_++;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
void Canvas2D::fill_rect(float x, float y, float width, float height) {
|
|
421
|
+
if (state_.transform.axis_aligned()) {
|
|
422
|
+
float x0, y0, x1, y1;
|
|
423
|
+
to_device(x, y, &x0, &y0);
|
|
424
|
+
to_device(x + width, y + height, &x1, &y1);
|
|
425
|
+
canvas_detail::fill_rect(image_.pixels, image_.width, image_.height, x0, y0, x1, y1,
|
|
426
|
+
state_.fill_color, state_.global_alpha);
|
|
427
|
+
} else {
|
|
428
|
+
CanvasSubpath loop;
|
|
429
|
+
const float corners[4][2] = {
|
|
430
|
+
{x, y}, {x + width, y}, {x + width, y + height}, {x, y + height}};
|
|
431
|
+
for (const auto &corner : corners) {
|
|
432
|
+
CanvasPoint point;
|
|
433
|
+
to_device(corner[0], corner[1], &point.x, &point.y);
|
|
434
|
+
loop.push_back(point);
|
|
435
|
+
}
|
|
436
|
+
canvas_detail::fill_loop(image_.pixels, image_.width, image_.height, loop, state_.fill_color,
|
|
437
|
+
state_.global_alpha);
|
|
438
|
+
}
|
|
439
|
+
generation_++;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
void Canvas2D::stroke_rect(float x, float y, float width, float height) {
|
|
443
|
+
CanvasSubpath loop;
|
|
444
|
+
const float corners[5][2] = {
|
|
445
|
+
{x, y}, {x + width, y}, {x + width, y + height}, {x, y + height}, {x, y}};
|
|
446
|
+
for (const auto &corner : corners) {
|
|
447
|
+
CanvasPoint point;
|
|
448
|
+
to_device(corner[0], corner[1], &point.x, &point.y);
|
|
449
|
+
loop.push_back(point);
|
|
450
|
+
}
|
|
451
|
+
stroke_subpath(loop, true, state_.stroke_color);
|
|
452
|
+
generation_++;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
float Canvas2D::measure_text(const std::string &text) const {
|
|
456
|
+
return xt_text_measure_width(text, state_.font);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
void Canvas2D::paint_text(const std::string &text, float x, float y, const float color[4],
|
|
460
|
+
bool stroke) {
|
|
461
|
+
Font *font = xt_text_resolve(state_.font);
|
|
462
|
+
if (font == nullptr) return;
|
|
463
|
+
std::vector<ShapedGlyph> glyphs;
|
|
464
|
+
float width = xt_text_shape_run(text, font, glyphs);
|
|
465
|
+
float ascent = 0.0f, descent = 0.0f, lineHeight = 0.0f;
|
|
466
|
+
xt_text_metrics(state_.font, &ascent, &descent, &lineHeight);
|
|
467
|
+
|
|
468
|
+
float originX = x;
|
|
469
|
+
if (state_.text_align == 1) {
|
|
470
|
+
originX -= width * 0.5f;
|
|
471
|
+
} else if (state_.text_align == 2) {
|
|
472
|
+
originX -= width;
|
|
473
|
+
}
|
|
474
|
+
float originY = y;
|
|
475
|
+
switch (state_.text_baseline) {
|
|
476
|
+
case 1:
|
|
477
|
+
originY += ascent;
|
|
478
|
+
break;
|
|
479
|
+
case 2:
|
|
480
|
+
originY += (ascent - descent) * 0.5f;
|
|
481
|
+
break;
|
|
482
|
+
case 3:
|
|
483
|
+
originY -= descent;
|
|
484
|
+
break;
|
|
485
|
+
case 4:
|
|
486
|
+
originY += ascent * 0.8f;
|
|
487
|
+
break;
|
|
488
|
+
case 5:
|
|
489
|
+
originY -= descent;
|
|
490
|
+
break;
|
|
491
|
+
default:
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
float scale = state_.transform.scale();
|
|
496
|
+
if (scale <= 0.0001f) scale = 1.0f;
|
|
497
|
+
bool scaled = std::fabs(scale - 1.0f) > 0.01f;
|
|
498
|
+
|
|
499
|
+
float penX = 0.0f;
|
|
500
|
+
for (const ShapedGlyph &glyph : glyphs) {
|
|
501
|
+
float userX = originX + penX + glyph.x_offset;
|
|
502
|
+
float userY = originY + glyph.y_offset;
|
|
503
|
+
float deviceX = 0.0f, deviceY = 0.0f;
|
|
504
|
+
to_device(userX, userY, &deviceX, &deviceY);
|
|
505
|
+
Font *face = glyph.font != nullptr ? glyph.font : font;
|
|
506
|
+
if (scaled) {
|
|
507
|
+
Font *sized = xt_text_scale(face, state_.font.pixel_size * scale);
|
|
508
|
+
if (sized != nullptr) face = sized;
|
|
509
|
+
}
|
|
510
|
+
GlyphImage image;
|
|
511
|
+
if (xt_text_rasterize(face, glyph.glyph, image) && image.width > 0 && image.height > 0) {
|
|
512
|
+
canvas_detail::blit_glyph(image_.pixels, image_.width, image_.height, image.pixels, image.width,
|
|
513
|
+
image.height, image.left, image.top, deviceX, deviceY, color,
|
|
514
|
+
state_.global_alpha);
|
|
515
|
+
}
|
|
516
|
+
penX += glyph.x_advance;
|
|
517
|
+
}
|
|
518
|
+
(void)stroke;
|
|
519
|
+
generation_++;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
void Canvas2D::fill_text(const std::string &text, float x, float y) {
|
|
523
|
+
paint_text(text, x, y, state_.fill_color, false);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
void Canvas2D::stroke_text(const std::string &text, float x, float y) {
|
|
527
|
+
paint_text(text, x, y, state_.stroke_color, true);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
void Canvas2D::draw_image(const Image &source, float dx, float dy, float dw, float dh, float sx,
|
|
531
|
+
float sy, float sw, float sh) {
|
|
532
|
+
if (state_.transform.axis_aligned()) {
|
|
533
|
+
float x0, y0, x1, y1;
|
|
534
|
+
to_device(dx, dy, &x0, &y0);
|
|
535
|
+
to_device(dx + dw, dy + dh, &x1, &y1);
|
|
536
|
+
canvas_detail::draw_image(image_.pixels, image_.width, image_.height, source, x0, y0, x1 - x0,
|
|
537
|
+
y1 - y0, sx, sy, sw, sh, state_.global_alpha);
|
|
538
|
+
} else {
|
|
539
|
+
float cornersX[4], cornersY[4];
|
|
540
|
+
const float corners[4][2] = {
|
|
541
|
+
{dx, dy}, {dx + dw, dy}, {dx + dw, dy + dh}, {dx, dy + dh}};
|
|
542
|
+
float minX = 0.0f, minY = 0.0f, maxX = 0.0f, maxY = 0.0f;
|
|
543
|
+
for (int i = 0; i < 4; i++) {
|
|
544
|
+
to_device(corners[i][0], corners[i][1], &cornersX[i], &cornersY[i]);
|
|
545
|
+
if (i == 0) {
|
|
546
|
+
minX = maxX = cornersX[i];
|
|
547
|
+
minY = maxY = cornersY[i];
|
|
548
|
+
} else {
|
|
549
|
+
minX = std::min(minX, cornersX[i]);
|
|
550
|
+
maxX = std::max(maxX, cornersX[i]);
|
|
551
|
+
minY = std::min(minY, cornersY[i]);
|
|
552
|
+
maxY = std::max(maxY, cornersY[i]);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
canvas_detail::draw_image(image_.pixels, image_.width, image_.height, source, minX, minY,
|
|
556
|
+
maxX - minX, maxY - minY, sx, sy, sw, sh, state_.global_alpha);
|
|
557
|
+
}
|
|
558
|
+
generation_++;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
bool Canvas2D::get_image_data(int x, int y, int width, int height,
|
|
562
|
+
std::vector<unsigned char> *out) const {
|
|
563
|
+
if (width <= 0 || height <= 0 || out == nullptr) return false;
|
|
564
|
+
out->assign((size_t)width * (size_t)height * 4, 0);
|
|
565
|
+
for (int py = 0; py < height; py++) {
|
|
566
|
+
int sourceY = y + py;
|
|
567
|
+
if (sourceY < 0 || sourceY >= image_.height) continue;
|
|
568
|
+
for (int px = 0; px < width; px++) {
|
|
569
|
+
int sourceX = x + px;
|
|
570
|
+
if (sourceX < 0 || sourceX >= image_.width) continue;
|
|
571
|
+
const unsigned char *src =
|
|
572
|
+
image_.pixels.data() + ((size_t)sourceY * (size_t)image_.width + (size_t)sourceX) * 4;
|
|
573
|
+
unsigned char *dst = out->data() + ((size_t)py * (size_t)width + (size_t)px) * 4;
|
|
574
|
+
std::memcpy(dst, src, 4);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return true;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
} // namespace xtgui
|