zaniah 0.1.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +70 -0
- data/assets/fonts/Abel-Regular.ttf +0 -0
- data/assets/fonts/OFL.txt +93 -0
- data/docs/adr/000-template.md +24 -0
- data/docs/adr/001-native-gpu-backends.md +36 -0
- data/docs/adr/002-objective-c-aggregates.md +34 -0
- data/docs/adr/003-fiddle-call-boundary.md +33 -0
- data/docs/adr/README.md +10 -0
- data/docs/native.md +66 -0
- data/docs/process_pool.md +67 -0
- data/docs/text.md +90 -0
- data/docs/vector_and_list.md +52 -0
- data/examples/ffi_bench.rb +16 -0
- data/examples/linux_smoke.rb +53 -0
- data/examples/mac_event_check.rb +61 -0
- data/examples/mac_poll_bench.rb +24 -0
- data/examples/metal_state_check.rb +72 -0
- data/examples/native_bench.rb +18 -0
- data/examples/native_drop.rb +82 -0
- data/examples/native_font.rb +15 -0
- data/examples/native_ime.rb +50 -0
- data/examples/native_smoke.rb +75 -0
- data/examples/native_text_bench.rb +83 -0
- data/examples/native_watch.rb +25 -0
- data/examples/vulkan_triangle.rb +20 -0
- data/examples/word_count_worker.rb +8 -0
- data/lib/zaniah/anchored.rb +10 -0
- data/lib/zaniah/app.rb +130 -0
- data/lib/zaniah/bounds.rb +22 -0
- data/lib/zaniah/canvas.rb +15 -0
- data/lib/zaniah/color.rb +35 -0
- data/lib/zaniah/configuration.rb +5 -0
- data/lib/zaniah/corners.rb +7 -0
- data/lib/zaniah/data_compat.rb +25 -0
- data/lib/zaniah/device_pixels.rb +7 -0
- data/lib/zaniah/div.rb +5 -0
- data/lib/zaniah/edges.rb +7 -0
- data/lib/zaniah/element.rb +106 -0
- data/lib/zaniah/entity.rb +9 -0
- data/lib/zaniah/entity_context.rb +17 -0
- data/lib/zaniah/error.rb +5 -0
- data/lib/zaniah/ffi/com.rb +24 -0
- data/lib/zaniah/ffi/library.rb +29 -0
- data/lib/zaniah/ffi/objc.rb +86 -0
- data/lib/zaniah/ffi/struct/signature.rb +46 -0
- data/lib/zaniah/ffi/struct.rb +61 -0
- data/lib/zaniah/ffi/wayland.rb +140 -0
- data/lib/zaniah/frame_context.rb +14 -0
- data/lib/zaniah/geometry.rb +16 -0
- data/lib/zaniah/gpu/buffer.rb +23 -0
- data/lib/zaniah/gpu/frame_encoder.rb +43 -0
- data/lib/zaniah/gpu/instance_packing.rb +53 -0
- data/lib/zaniah/gpu/metal.rb +209 -0
- data/lib/zaniah/gpu/open_gl.rb +210 -0
- data/lib/zaniah/gpu/pipeline.rb +7 -0
- data/lib/zaniah/gpu/software.rb +126 -0
- data/lib/zaniah/gpu/texture.rb +33 -0
- data/lib/zaniah/gpu/vulkan.rb +233 -0
- data/lib/zaniah/gpu.rb +18 -0
- data/lib/zaniah/hsla.rb +14 -0
- data/lib/zaniah/image.rb +19 -0
- data/lib/zaniah/input/action_registry.rb +12 -0
- data/lib/zaniah/input/composition.rb +7 -0
- data/lib/zaniah/input/context_predicate.rb +87 -0
- data/lib/zaniah/input/dispatcher.rb +59 -0
- data/lib/zaniah/input/file_drop.rb +7 -0
- data/lib/zaniah/input/focus_handle.rb +24 -0
- data/lib/zaniah/input/key_down.rb +7 -0
- data/lib/zaniah/input/key_up.rb +7 -0
- data/lib/zaniah/input/keymap/binding.rb +9 -0
- data/lib/zaniah/input/keymap.rb +55 -0
- data/lib/zaniah/input/keystroke.rb +20 -0
- data/lib/zaniah/input/mouse_down.rb +7 -0
- data/lib/zaniah/input/mouse_move.rb +7 -0
- data/lib/zaniah/input/mouse_up.rb +7 -0
- data/lib/zaniah/input/scroll_wheel.rb +7 -0
- data/lib/zaniah/input/text_input.rb +7 -0
- data/lib/zaniah/input.rb +17 -0
- data/lib/zaniah/layout/available_space.rb +11 -0
- data/lib/zaniah/layout/engine.rb +214 -0
- data/lib/zaniah/layout/node.rb +36 -0
- data/lib/zaniah/layout/style.rb +19 -0
- data/lib/zaniah/layout/tree.rb +16 -0
- data/lib/zaniah/layout.rb +11 -0
- data/lib/zaniah/length.rb +14 -0
- data/lib/zaniah/length_units.rb +9 -0
- data/lib/zaniah/list/height_index.rb +68 -0
- data/lib/zaniah/list.rb +101 -0
- data/lib/zaniah/overlay.rb +10 -0
- data/lib/zaniah/platform/display.rb +7 -0
- data/lib/zaniah/platform/file_event.rb +7 -0
- data/lib/zaniah/platform/headless/window.rb +185 -0
- data/lib/zaniah/platform/linux/appearance_aware.rb +60 -0
- data/lib/zaniah/platform/linux/displays.rb +55 -0
- data/lib/zaniah/platform/linux/free_type.rb +80 -0
- data/lib/zaniah/platform/linux/watcher.rb +70 -0
- data/lib/zaniah/platform/linux/wayland_display_discovery.rb +60 -0
- data/lib/zaniah/platform/linux/wayland_window.rb +369 -0
- data/lib/zaniah/platform/linux/window.rb +418 -0
- data/lib/zaniah/platform/linux.rb +3 -0
- data/lib/zaniah/platform/mac/app.rb +67 -0
- data/lib/zaniah/platform/mac/core_text.rb +94 -0
- data/lib/zaniah/platform/mac/displays.rb +23 -0
- data/lib/zaniah/platform/mac/watcher.rb +63 -0
- data/lib/zaniah/platform/mac/window.rb +298 -0
- data/lib/zaniah/platform/mac.rb +19 -0
- data/lib/zaniah/platform/tui/input_decoder.rb +145 -0
- data/lib/zaniah/platform/tui/text_renderer.rb +40 -0
- data/lib/zaniah/platform/tui/window.rb +91 -0
- data/lib/zaniah/platform/windows/terminal.rb +127 -0
- data/lib/zaniah/platform/windows/watcher.rb +91 -0
- data/lib/zaniah/platform/windows/window.rb +407 -0
- data/lib/zaniah/platform/windows.rb +3 -0
- data/lib/zaniah/platform.rb +34 -0
- data/lib/zaniah/png.rb +118 -0
- data/lib/zaniah/point.rb +8 -0
- data/lib/zaniah/process_pool/worker.rb +7 -0
- data/lib/zaniah/process_pool.rb +239 -0
- data/lib/zaniah/process_wire.rb +127 -0
- data/lib/zaniah/quad_packer.rb +15 -0
- data/lib/zaniah/scene/sprite_batch.rb +7 -0
- data/lib/zaniah/scene.rb +167 -0
- data/lib/zaniah/scene_renderer.rb +8 -0
- data/lib/zaniah/size.rb +5 -0
- data/lib/zaniah/subscription.rb +14 -0
- data/lib/zaniah/svg/path.rb +134 -0
- data/lib/zaniah/svg/rasterizable.rb +186 -0
- data/lib/zaniah/svg.rb +295 -0
- data/lib/zaniah/task/cancelled.rb +7 -0
- data/lib/zaniah/task/timeout.rb +7 -0
- data/lib/zaniah/task.rb +51 -0
- data/lib/zaniah/task_executor.rb +105 -0
- data/lib/zaniah/text.rb +24 -0
- data/lib/zaniah/text_system/atlas/entry.rb +9 -0
- data/lib/zaniah/text_system/atlas.rb +102 -0
- data/lib/zaniah/text_system/atlas_cache.rb +146 -0
- data/lib/zaniah/text_system/font_db/face.rb +9 -0
- data/lib/zaniah/text_system/font_db.rb +193 -0
- data/lib/zaniah/text_system/glyph.rb +7 -0
- data/lib/zaniah/text_system/line_layout.rb +20 -0
- data/lib/zaniah/text_system/renderer.rb +196 -0
- data/lib/zaniah/text_system/shaper.rb +430 -0
- data/lib/zaniah/text_system/typesetter/copy_error.rb +9 -0
- data/lib/zaniah/text_system/typesetter.rb +168 -0
- data/lib/zaniah/text_system.rb +9 -0
- data/lib/zaniah/unicode.rb +13 -0
- data/lib/zaniah/uniform_list.rb +30 -0
- data/lib/zaniah/version.rb +5 -0
- data/lib/zaniah/view.rb +13 -0
- data/lib/zaniah.rb +29 -0
- data/sig/app.rbs +80 -0
- data/sig/elements.rbs +129 -0
- data/sig/gpu.rbs +108 -0
- data/sig/input.rbs +114 -0
- data/sig/layout.rbs +53 -0
- data/sig/native.rbs +88 -0
- data/sig/platform.rbs +94 -0
- data/sig/scene.rbs +52 -0
- data/sig/shaper.rbs +8 -0
- data/sig/text.rbs +137 -0
- data/sig/zaniah.rbs +120 -0
- data/tools/check_dependencies.rb +52 -0
- metadata +249 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 83f0e9d7ba2790764f55bc1e0eda5e21e31b22a54cd0a92668b24df9000d351c
|
|
4
|
+
data.tar.gz: 37a96934f06671e69989c1e2e91e30917e0b6902458a09fc05fd5ef3999030f3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3feda2d8ba35caa3b73821eb9cae80c64d8b549e87c125dd6f55c4382d9789451bd91e0ae2e019709e1f923b662b498265085c6c0fcd268dad3a94ce7ab6c4c3
|
|
7
|
+
data.tar.gz: ef1e4f76251e22c1a8317677cccc3b80885aec8f3bb917d09df7a363a9dbfb6976ed19e38565a75eb5bf656b2a2f3276daada27156d79eb250d6e734ff1ad62b
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yudai Takada
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Zaniah
|
|
2
|
+
|
|
3
|
+
A Ruby UI toolkit with native GPU windows, a headless renderer, and a terminal
|
|
4
|
+
backend. CRuby 3.1+; YJIT recommended. Runtime dependencies are Alhena, REXML
|
|
5
|
+
and unicode-display_width.
|
|
6
|
+
|
|
7
|
+
The name comes from the white ground laid before paint or gold leaf: this
|
|
8
|
+
toolkit is the drawing surface beneath the editor.
|
|
9
|
+
|
|
10
|
+
## Try it locally
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
bundle install
|
|
14
|
+
bundle exec rake test
|
|
15
|
+
ruby --yjit examples/native_smoke.rb
|
|
16
|
+
gem build zaniah.gemspec
|
|
17
|
+
gem install --local zaniah-0.1.0.gem
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The gem name is not published or reserved. Install the locally built artifact
|
|
21
|
+
rather than an unverified public gem.
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
require "zaniah"
|
|
25
|
+
|
|
26
|
+
window = Zaniah::Platform.open_window(backend: :headless, width: 480, height: 240)
|
|
27
|
+
window.text_system = Zaniah::TextSystem::Renderer.new
|
|
28
|
+
window.draw do
|
|
29
|
+
Zaniah::Div.new.flex_col.p(24).gap(12).bg("#161b22")
|
|
30
|
+
.child(Zaniah::Text.new("Hello, Ruby", size: 24))
|
|
31
|
+
.child(Zaniah::Text.new("A native toolkit, written in Ruby."))
|
|
32
|
+
end
|
|
33
|
+
window.tick
|
|
34
|
+
window.write_png("hello.png")
|
|
35
|
+
window.close
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Choose `backend: :mac`, `:linux`, `:windows`, or `:tui` and call
|
|
39
|
+
`window.run` for an interactive window. Native rendering uses OS libraries via
|
|
40
|
+
Ruby's Fiddle, without extension compilation or a rendering subprocess.
|
|
41
|
+
|
|
42
|
+
## Toolkit and platforms
|
|
43
|
+
|
|
44
|
+
Flex layouts, constraints, absolute positioning, wrapping, baseline alignment,
|
|
45
|
+
clipping, retained element state, and uniform/variable-height virtual lists are
|
|
46
|
+
provided. Generational entities and subscriptions connect state to frames;
|
|
47
|
+
foreground Fibers can await background tasks without blocking the UI.
|
|
48
|
+
Scene primitives include rounded rectangles, images, static SVG, shadows and
|
|
49
|
+
glyph atlases. OpenType ligatures and horizontal positioning use the Ruby shaper.
|
|
50
|
+
Pure Ruby rasterization is the default; explicit CoreText/FreeType providers and
|
|
51
|
+
a licensed Abel fallback font are included.
|
|
52
|
+
|
|
53
|
+
## Development
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
bundle exec rake test
|
|
57
|
+
ruby --yjit bench/instances.rb
|
|
58
|
+
ruby --yjit bench/list.rb
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`BUDGET=1` enables benchmark assertions.
|
|
62
|
+
|
|
63
|
+
See [native details](docs/native.md), [text configuration and shaping](docs/text.md),
|
|
64
|
+
[SVG and lists](docs/vector_and_list.md), [portable CPU workers](docs/process_pool.md),
|
|
65
|
+
and [decisions](docs/adr).
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
MIT; see [LICENSE.txt](LICENSE.txt). Included font notices remain alongside the
|
|
70
|
+
font files.
|
|
Binary file
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Copyright (c) 2011, Matthew Desmond (http://www.madtype.com | mattdesmond@gmail.com),with Reserved Font Name Abel.
|
|
2
|
+
|
|
3
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
4
|
+
This license is copied below, and is also available with a FAQ at:
|
|
5
|
+
http://scripts.sil.org/OFL
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
-----------------------------------------------------------
|
|
9
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
10
|
+
-----------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
PREAMBLE
|
|
13
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
14
|
+
development of collaborative font projects, to support the font creation
|
|
15
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
16
|
+
open framework in which fonts may be shared and improved in partnership
|
|
17
|
+
with others.
|
|
18
|
+
|
|
19
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
20
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
21
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
22
|
+
redistributed and/or sold with any software provided that any reserved
|
|
23
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
24
|
+
however, cannot be released under any other type of license. The
|
|
25
|
+
requirement for fonts to remain under this license does not apply
|
|
26
|
+
to any document created using the fonts or their derivatives.
|
|
27
|
+
|
|
28
|
+
DEFINITIONS
|
|
29
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
30
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
31
|
+
include source files, build scripts and documentation.
|
|
32
|
+
|
|
33
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
34
|
+
copyright statement(s).
|
|
35
|
+
|
|
36
|
+
"Original Version" refers to the collection of Font Software components as
|
|
37
|
+
distributed by the Copyright Holder(s).
|
|
38
|
+
|
|
39
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
40
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
41
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
42
|
+
new environment.
|
|
43
|
+
|
|
44
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
45
|
+
writer or other person who contributed to the Font Software.
|
|
46
|
+
|
|
47
|
+
PERMISSION & CONDITIONS
|
|
48
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
49
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
50
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
51
|
+
Software, subject to the following conditions:
|
|
52
|
+
|
|
53
|
+
1) Neither the Font Software nor any of its individual components,
|
|
54
|
+
in Original or Modified Versions, may be sold by itself.
|
|
55
|
+
|
|
56
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
57
|
+
redistributed and/or sold with any software, provided that each copy
|
|
58
|
+
contains the above copyright notice and this license. These can be
|
|
59
|
+
included either as stand-alone text files, human-readable headers or
|
|
60
|
+
in the appropriate machine-readable metadata fields within text or
|
|
61
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
62
|
+
|
|
63
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
64
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
65
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
66
|
+
presented to the users.
|
|
67
|
+
|
|
68
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
69
|
+
Software shall not be used to promote, endorse or advertise any
|
|
70
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
71
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
72
|
+
permission.
|
|
73
|
+
|
|
74
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
75
|
+
must be distributed entirely under this license, and must not be
|
|
76
|
+
distributed under any other license. The requirement for fonts to
|
|
77
|
+
remain under this license does not apply to any document created
|
|
78
|
+
using the Font Software.
|
|
79
|
+
|
|
80
|
+
TERMINATION
|
|
81
|
+
This license becomes null and void if any of the above conditions are
|
|
82
|
+
not met.
|
|
83
|
+
|
|
84
|
+
DISCLAIMER
|
|
85
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
86
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
87
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
88
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
89
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
90
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
91
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
92
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
93
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# ADR NNN: Implementation decision title
|
|
2
|
+
|
|
3
|
+
- Status: Proposed
|
|
4
|
+
- Date: YYYY-MM-DD
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Describe the concrete implementation question and its compatibility, data,
|
|
9
|
+
runtime, or component constraints. Operational workflow and release policy do
|
|
10
|
+
not belong in this directory; see [the scope](README.md).
|
|
11
|
+
|
|
12
|
+
Name the credible alternatives. If there was no meaningful alternative, record
|
|
13
|
+
the behavior in reference documentation instead of creating an ADR.
|
|
14
|
+
|
|
15
|
+
## Decision
|
|
16
|
+
|
|
17
|
+
Describe the durable boundary or architecture choice. Leave command syntax,
|
|
18
|
+
field-by-field formats, test evidence, benchmark results, and migration steps
|
|
19
|
+
in their authoritative documents.
|
|
20
|
+
|
|
21
|
+
## Consequences
|
|
22
|
+
|
|
23
|
+
Describe the important positive and negative trade-offs, including what would
|
|
24
|
+
make this decision worth revisiting.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# ADR 001: Use Metal on macOS and OpenGL on Linux and Windows
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2026-09-10
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Native windows need GPU rendering while keeping the gem free of compiled
|
|
9
|
+
extensions. Rendering must preserve scene order, clipping, and alpha blending,
|
|
10
|
+
and Ruby callbacks must not enter the VM from unsupported operating-system
|
|
11
|
+
threads.
|
|
12
|
+
|
|
13
|
+
The credible alternatives were one OpenGL backend for every desktop, native
|
|
14
|
+
Metal on macOS with OpenGL elsewhere, software rendering only, or a compiled
|
|
15
|
+
bridge that could safely receive display-link callbacks.
|
|
16
|
+
|
|
17
|
+
## Decision
|
|
18
|
+
|
|
19
|
+
macOS defaults to a `CAMetalLayer` and runtime-compiled Metal shaders, with
|
|
20
|
+
OpenGL as an explicit alternative. Linux uses OpenGL through GLX or EGL, and
|
|
21
|
+
Windows uses OpenGL through WGL. Headless rendering remains a separate software
|
|
22
|
+
backend.
|
|
23
|
+
|
|
24
|
+
Compatible adjacent scene commands are batched without reordering them. The Ruby
|
|
25
|
+
main thread polls native events and presents synchronized frames; native display
|
|
26
|
+
threads do not call Ruby through Fiddle.
|
|
27
|
+
|
|
28
|
+
## Consequences
|
|
29
|
+
|
|
30
|
+
Applications get native GPU windows without compiling repository code, and each
|
|
31
|
+
platform can use a supported presentation path. The project must maintain both
|
|
32
|
+
Metal and OpenGL renderers and test native behavior on each target platform.
|
|
33
|
+
|
|
34
|
+
Revisit this decision if a compiled bridge becomes acceptable, Ruby gains a safe
|
|
35
|
+
foreign-thread callback boundary, or maintaining parallel rendering APIs costs
|
|
36
|
+
more than the platform-specific benefits.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# ADR 002: Describe Objective-C aggregate values with system libffi
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2026-09-10
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
AppKit and related frameworks pass values such as `NSRect` and `NSRange` by
|
|
9
|
+
value. Fiddle function signatures expose scalar types but cannot describe these
|
|
10
|
+
aggregate arguments and return values accurately. Their calling conventions also
|
|
11
|
+
differ between arm64 and x86_64.
|
|
12
|
+
|
|
13
|
+
The credible alternatives were treating aggregates as pointers, writing a C
|
|
14
|
+
extension or generated trampoline for each signature, or describing the native
|
|
15
|
+
ABI through the system libffi library already used by Fiddle.
|
|
16
|
+
|
|
17
|
+
## Decision
|
|
18
|
+
|
|
19
|
+
`FFI::Struct::Signature` describes aggregate layouts to system libffi and invokes
|
|
20
|
+
them with `ffi_call`. The same mechanism creates closures with aggregate
|
|
21
|
+
arguments and returns. Objective-C dispatch signatures are cached; arm64 uses
|
|
22
|
+
its native aggregate ABI, while x86_64 uses `objc_msgSend_stret` where required.
|
|
23
|
+
|
|
24
|
+
Native pointers and Ruby closures have explicit owners, and callbacks are retained
|
|
25
|
+
for as long as native code may invoke them.
|
|
26
|
+
|
|
27
|
+
## Consequences
|
|
28
|
+
|
|
29
|
+
Aggregate calls follow each platform ABI without a compiler or C extension. The
|
|
30
|
+
trade-off is a small internal FFI layer that must model architecture-specific
|
|
31
|
+
dispatch and object lifetimes correctly.
|
|
32
|
+
|
|
33
|
+
Revisit this decision if Fiddle gains complete aggregate signatures or if the
|
|
34
|
+
project adopts a compiled native boundary for other reasons.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# ADR 003: Batch frame work across the Fiddle boundary
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2026-09-10
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Crossing from Ruby into native functions has fixed conversion and dispatch cost.
|
|
9
|
+
A renderer that makes one native call per primitive scales that cost with scene
|
|
10
|
+
size. Objective-C calls may also reenter Ruby on the current thread, while
|
|
11
|
+
terminal reads may block for an unbounded period.
|
|
12
|
+
|
|
13
|
+
The credible alternatives were per-primitive native calls, cached wrappers with
|
|
14
|
+
batched frame data, or moving frame encoding into a compiled extension.
|
|
15
|
+
|
|
16
|
+
## Decision
|
|
17
|
+
|
|
18
|
+
Native function wrappers are cached by their full signature. Compatible adjacent
|
|
19
|
+
GPU commands are packed into instance buffers and submitted in batches rather
|
|
20
|
+
than one primitive at a time.
|
|
21
|
+
|
|
22
|
+
Objective-C dispatch keeps the GVL because callbacks may reenter Ruby. Blocking
|
|
23
|
+
ConPTY reads release it so other Ruby threads can continue.
|
|
24
|
+
|
|
25
|
+
## Consequences
|
|
26
|
+
|
|
27
|
+
Native-call overhead depends primarily on batch boundaries instead of primitive
|
|
28
|
+
count, while draw order and clipping remain explicit. Batching adds packing code,
|
|
29
|
+
and material, texture, or clip changes still split batches.
|
|
30
|
+
|
|
31
|
+
Revisit this decision if profiling shows boundary calls are no longer material,
|
|
32
|
+
batch construction dominates frame time, or a compiled renderer becomes an
|
|
33
|
+
accepted dependency.
|
data/docs/adr/README.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Architecture Decision Records
|
|
2
|
+
|
|
3
|
+
This directory records durable implementation choices that affect component or
|
|
4
|
+
runtime boundaries and had credible alternatives. API reference, command syntax,
|
|
5
|
+
field formats, test evidence, benchmark results, migrations, release policy, and
|
|
6
|
+
operational workflow belong in their authoritative documentation instead.
|
|
7
|
+
|
|
8
|
+
Copy [ADR 000](000-template.md) when recording a decision. Decision records use
|
|
9
|
+
three-digit numbers starting at `001`, assigned without gaps. Keep accepted,
|
|
10
|
+
rejected, and superseded records so later entries do not need renumbering.
|
data/docs/native.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Native backends
|
|
2
|
+
|
|
3
|
+
Zaniah provides native windows on macOS, Linux, and Windows. Headless and
|
|
4
|
+
terminal backends are available when a graphical window is unnecessary.
|
|
5
|
+
|
|
6
|
+
```ruby
|
|
7
|
+
window = Zaniah::Platform.open_window(
|
|
8
|
+
backend: :mac,
|
|
9
|
+
gpu: :metal,
|
|
10
|
+
width: 800,
|
|
11
|
+
height: 600,
|
|
12
|
+
title: "Zaniah"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
window.on_input { |event| p event }
|
|
16
|
+
window.draw { Zaniah::Div.new.bg("#123") }
|
|
17
|
+
window.run
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
| Backend | Renderer | Notes |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| `:mac` | Metal (default) or OpenGL | Requires a graphical macOS session |
|
|
23
|
+
| `:linux` | Wayland/EGL or X11/GLX | Selected from `WAYLAND_DISPLAY` and `DISPLAY` |
|
|
24
|
+
| `:windows` | Win32/WGL | Requires 64-bit Ruby and Windows 10 APIs |
|
|
25
|
+
| `:headless` | Ruby software renderer | Deterministic rendering and PNG output |
|
|
26
|
+
| `:tui` | ANSI terminal | Text-grid rendering and terminal input |
|
|
27
|
+
|
|
28
|
+
On Linux, pass `display_server: :wayland` or `:x11` to require one display
|
|
29
|
+
server. On macOS, pass `gpu: :opengl` to use OpenGL instead of Metal.
|
|
30
|
+
|
|
31
|
+
Native backends require Ruby's Fiddle library and the platform graphics
|
|
32
|
+
libraries. Fiddle is not a default gem on Ruby 4, so applications using native
|
|
33
|
+
windows must install it. Linux file dialogs and URL opening use `zenity` and
|
|
34
|
+
`xdg-open` when available.
|
|
35
|
+
|
|
36
|
+
Native windows support input and IME events, resizing, display scale, clipboard,
|
|
37
|
+
file drops, fullscreen, cursors, URL opening, file dialogs, appearance changes,
|
|
38
|
+
and PNG capture. Platform availability differs; see [the RBS declarations](../sig/native.rbs)
|
|
39
|
+
and [platform declarations](../sig/platform.rbs) for the exact API.
|
|
40
|
+
|
|
41
|
+
## Displays, file watching, and terminals
|
|
42
|
+
|
|
43
|
+
`Zaniah::Platform.displays` returns the available displays for a backend.
|
|
44
|
+
`Zaniah::Platform.watch(paths, latency:)` creates a native filesystem watcher;
|
|
45
|
+
call `poll(timeout:)` for events and `close` when finished. Overflow events mean
|
|
46
|
+
the application should rescan the watched paths.
|
|
47
|
+
|
|
48
|
+
On Windows, require `zaniah/platform/windows/terminal` to use
|
|
49
|
+
`Zaniah::Platform::Windows::Terminal`, which wraps ConPTY. It supports starting
|
|
50
|
+
a child process, nonblocking reads, writes, resizing, liveness checks, and
|
|
51
|
+
explicit cleanup. ConPTY requires Windows 10 version 1809 or later.
|
|
52
|
+
|
|
53
|
+
## Development checks
|
|
54
|
+
|
|
55
|
+
Run checks on the matching host and display server:
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
ruby examples/native_smoke.rb --check /tmp/zaniah.png
|
|
59
|
+
ruby examples/native_smoke.rb --gl --check /tmp/zaniah-gl.png
|
|
60
|
+
ruby examples/linux_smoke.rb
|
|
61
|
+
ruby examples/linux_smoke.rb --wayland
|
|
62
|
+
ruby examples/native_watch.rb
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Native input, IME, dialogs, and terminal integration also require manual checks
|
|
66
|
+
on the target operating system.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# CPU process workers
|
|
2
|
+
|
|
3
|
+
`ProcessPool` runs trusted CPU work in reusable Ruby child processes. For
|
|
4
|
+
portable spawning, put the handler in a loadable file and expose `.call(data)`:
|
|
5
|
+
|
|
6
|
+
```ruby
|
|
7
|
+
# /absolute/path/word_count.rb
|
|
8
|
+
module WordCount
|
|
9
|
+
def self.call(data)
|
|
10
|
+
{"words" => data.fetch("text").split.length}
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
require "zaniah"
|
|
17
|
+
require "zaniah/process_pool"
|
|
18
|
+
|
|
19
|
+
pool = Zaniah::ProcessPool.new(
|
|
20
|
+
workers: 2,
|
|
21
|
+
handler: "WordCount",
|
|
22
|
+
requires: [File.expand_path("word_count.rb")],
|
|
23
|
+
max_pending: 4
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
task = pool.submit("text" => "Hello Ruby")
|
|
27
|
+
result = task.await(timeout: 2)
|
|
28
|
+
pool.shutdown
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`requires:` accepts existing absolute Ruby filenames and `load_paths:` accepts
|
|
32
|
+
absolute directories. Workers start lazily, may keep process-local caches, and
|
|
33
|
+
do not guarantee that related requests use the same process. Failed workers are
|
|
34
|
+
replaced; lost work is not replayed.
|
|
35
|
+
|
|
36
|
+
## Data and limits
|
|
37
|
+
|
|
38
|
+
Arguments and results must be plain JSON values: nil, booleans, integers, finite
|
|
39
|
+
floats, valid UTF-8 strings, arrays, and string-keyed hashes. Values are copied
|
|
40
|
+
at submission and received values are frozen. Custom objects, symbols, cycles,
|
|
41
|
+
excessive nesting, and invalid strings are rejected.
|
|
42
|
+
|
|
43
|
+
The default message limit is 16 MiB. `workers:` accepts 1–32 and `max_pending:`
|
|
44
|
+
defaults to twice the worker count. Submitting to a full or shut-down pool raises
|
|
45
|
+
`Zaniah::Error` immediately. Handlers are application code: the pool is not a
|
|
46
|
+
sandbox for untrusted Ruby.
|
|
47
|
+
|
|
48
|
+
## Cancellation and cleanup
|
|
49
|
+
|
|
50
|
+
`Task#cancel` cancels queued work or terminates the child running that task. An
|
|
51
|
+
`await` timeout does not cancel work, so call `cancel` when abandoning a task.
|
|
52
|
+
Cancellation cannot undo external side effects already performed by a handler.
|
|
53
|
+
|
|
54
|
+
`shutdown(timeout: 2)` rejects new submissions, cancels outstanding tasks, and
|
|
55
|
+
cleans up workers. It is idempotent. If cleanup misses the deadline it raises
|
|
56
|
+
`Task::Timeout`, and shutdown may be retried.
|
|
57
|
+
|
|
58
|
+
The block form remains available on platforms with `fork`, but named handlers
|
|
59
|
+
are preferred for portability and for applications that own threads or native
|
|
60
|
+
resources. Both forms use the same JSON-only data contract.
|
|
61
|
+
|
|
62
|
+
See [the RBS declarations](../sig/app.rbs) for the complete API. Run the worker
|
|
63
|
+
tests with:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
ruby -Itest test/process_pool_test.rb
|
|
67
|
+
```
|
data/docs/text.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Text system
|
|
2
|
+
|
|
3
|
+
`TextSystem::Renderer` provides layout, glyph rasterization, atlas management, and
|
|
4
|
+
painting. `TextSystem::Typesetter` provides layout and caret measurements without
|
|
5
|
+
allocating glyph textures.
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
db = Zaniah::TextSystem::FontDB.new
|
|
9
|
+
font = db.find(family: "Menlo", weight: 700, width: 5, style: :normal)
|
|
10
|
+
system = Zaniah::TextSystem::Renderer.new(font: font, font_db: db)
|
|
11
|
+
window.text_system = system
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`FontDB` discovers TTF, OTF, TTC, and OTC fonts in platform directories. It
|
|
15
|
+
selects faces by family, weight (`1..1000`), width (`1..9`), and style
|
|
16
|
+
(`:normal`, `:italic`, or `:oblique`). A bundled Abel font is the final fallback.
|
|
17
|
+
Call `refresh` to clear cached discovery results.
|
|
18
|
+
|
|
19
|
+
## Providers and shaping
|
|
20
|
+
|
|
21
|
+
Defaults can be configured globally or supplied to a constructor:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
Zaniah.configure do |config|
|
|
25
|
+
config.font_db = :native
|
|
26
|
+
config.shaper = :native
|
|
27
|
+
config.segmenter = :native
|
|
28
|
+
config.font_raster = :native
|
|
29
|
+
end
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Provider objects may be supplied instead of symbols:
|
|
33
|
+
|
|
34
|
+
| Setting | Required method | Result |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| `font_db` | `find(...)`, `fallback(codepoint, primary_font)` | A font compatible with Alhena |
|
|
37
|
+
| `shaper` | `shape(glyphs, size:, text:)` | `Array[TextSystem::Glyph]` with UTF-8 byte clusters |
|
|
38
|
+
| `segmenter` | `grapheme_clusters(text)` | Strings that exactly partition the input |
|
|
39
|
+
| `font_raster` | `rasterize(font, glyph_id, size:, subpixel_x:)` | A glyph bitmap |
|
|
40
|
+
|
|
41
|
+
Providers may implement `close`; the text system closes them when it closes.
|
|
42
|
+
Custom mutable providers used by `fork` must also implement `layout_copy` and
|
|
43
|
+
return an independent provider.
|
|
44
|
+
|
|
45
|
+
The default Ruby shaper supports horizontal Latin, CJK, and kana text, including
|
|
46
|
+
common OpenType ligatures, contextual substitutions, pair positioning, and
|
|
47
|
+
legacy kerning. It is not a replacement for full complex-script shaping:
|
|
48
|
+
Arabic/Indic reordering, AAT `morx`, vertical text, complete mark positioning,
|
|
49
|
+
and variation-index positioning are unsupported.
|
|
50
|
+
|
|
51
|
+
Built-in rasterizers are `:native`/`:alhena`, `:freetype`, and `:coretext`
|
|
52
|
+
(`:core_text` is an alias). FreeType and CoreText require their platform library.
|
|
53
|
+
Unimplemented symbolic adapters raise `ArgumentError`; pass a provider object to
|
|
54
|
+
integrate another shaper, segmenter, font database, or rasterizer.
|
|
55
|
+
|
|
56
|
+
## Background layout and startup cache
|
|
57
|
+
|
|
58
|
+
Create an independent layout-only typesetter for background work:
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
typesetter = system.fork(capacity: 32)
|
|
62
|
+
# use typesetter on one background worker
|
|
63
|
+
typesetter.close
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Do not mutate one text system or atlas concurrently. Font bytes may be shared,
|
|
67
|
+
but mutable providers and caches must be copied.
|
|
68
|
+
|
|
69
|
+
Disk-backed atlas caching is opt-in. Prewarm only after the window scale is known:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
system = Zaniah::TextSystem::Renderer.new(cache_dir: "/path/to/app-cache/glyphs")
|
|
73
|
+
system.scale_factor = window.scale_factor
|
|
74
|
+
system.prewarm(size: 14)
|
|
75
|
+
window.text_system = system
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
A missing, corrupt, or read-only cache does not prevent rendering. Applications
|
|
79
|
+
own cache-directory retention. See [the RBS declarations](../sig/text.rbs) for
|
|
80
|
+
the complete API.
|
|
81
|
+
|
|
82
|
+
## Development checks
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
BUDGET=1 ruby --yjit bench/text_frame.rb
|
|
86
|
+
ruby --yjit bench/atlas_startup.rb
|
|
87
|
+
ruby --yjit -Ilib script/shaper_oracle assets/fonts/Abel-Regular.ttf /path/to/font.ttf
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The shaping oracle is optional and requires `hb-shape`; it is not used at runtime.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Vector icons and variable-height lists
|
|
2
|
+
|
|
3
|
+
`SVG` and `List` are normal elements and participate in layout, clipping, hit
|
|
4
|
+
testing, and scene painting.
|
|
5
|
+
|
|
6
|
+
## Static SVG icons
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
icon = Zaniah::SVG.open("assets/search.svg", color: "#d8e4f0").w(24).h(24)
|
|
10
|
+
icon = Zaniah::SVG.parse(
|
|
11
|
+
'<svg viewBox="0 0 24 24"><path d="M4 12H20" stroke="currentColor"/></svg>'
|
|
12
|
+
)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The renderer supports SVG paths, basic shapes, groups, transforms, view boxes,
|
|
16
|
+
inherited fill and stroke, `currentColor`, opacity, local `defs`/`use`, and
|
|
17
|
+
user-space clip paths. It is intended for static icons, not arbitrary web SVG.
|
|
18
|
+
|
|
19
|
+
Scripts, external references, CSS stylesheets, gradients, filters, masks, images,
|
|
20
|
+
text, markers, dash arrays, nested viewports, and object-bounding-box clips are
|
|
21
|
+
unsupported and raise `ArgumentError`. Input is bounded to 2 MiB, 10,000 XML
|
|
22
|
+
nodes, 64 levels, and 100,000 path operations; each output texture is limited to
|
|
23
|
+
one megapixel.
|
|
24
|
+
|
|
25
|
+
## Variable-height lists
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
rows = Zaniah::List.new(count: 1_000_000, estimated_height: 24, overscan: 2) do |index|
|
|
29
|
+
Zaniah::Div.new
|
|
30
|
+
.h(index.even? ? 20 : 28)
|
|
31
|
+
.child(Zaniah::Text.new("Row #{index}"))
|
|
32
|
+
end.w(600).h(400)
|
|
33
|
+
|
|
34
|
+
rows.scroll_to(500_000, align: :center)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Give the list a viewport height. Only visible rows and the overscan margin are
|
|
38
|
+
instantiated; other rows retain estimated heights until measured. Persistent row
|
|
39
|
+
state belongs in keyed window state or the application model.
|
|
40
|
+
|
|
41
|
+
`scroll_y=` changes the pixel offset. `scroll_to(index, align:)` accepts `:start`,
|
|
42
|
+
`:center`, `:end`, and `:nearest`. `update_height` supplies a measurement before
|
|
43
|
+
rendering, `visible_range` returns the instantiated exclusive-end range, and
|
|
44
|
+
`total_height` includes estimates. A list's item count is fixed; create a new
|
|
45
|
+
list when the count changes.
|
|
46
|
+
|
|
47
|
+
See [the element declarations](../sig/elements.rbs) for the complete API. Run:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
ruby -Ilib -Itest test/svg_and_list_test.rb
|
|
51
|
+
ruby --yjit -Ilib bench/list.rb
|
|
52
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fiddle"
|
|
4
|
+
require "benchmark"
|
|
5
|
+
|
|
6
|
+
library = Fiddle.dlopen(RUBY_PLATFORM.match?(/mingw|mswin/) ? "msvcrt.dll" : nil)
|
|
7
|
+
count = Integer(ENV.fetch("N", "1000000"))
|
|
8
|
+
text = "a" * 16
|
|
9
|
+
pointer = Fiddle::Pointer[text]
|
|
10
|
+
[true, false].each do |gvl|
|
|
11
|
+
function = Fiddle::Function.new(library["strlen"], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_SIZE_T, need_gvl: gvl)
|
|
12
|
+
[text, pointer].each do |value|
|
|
13
|
+
elapsed = Benchmark.realtime { count.times { function.call(value) } }
|
|
14
|
+
puts "strlen gvl=#{gvl} #{value.class}: #{(elapsed * 1e6 / count).round(3)}us/call"
|
|
15
|
+
end
|
|
16
|
+
end
|