slikui-python 0.1.1__cp311-cp311-win32.whl

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.
slikui/__init__.py ADDED
@@ -0,0 +1,115 @@
1
+ """slikui — Python bindings for the slikui C++ UI library.
2
+
3
+ Distribution `slikui`, import package `slikui`, compiled module `slikui._slikui`.
4
+ Layering: `_slikui` binds the C++ API 1:1; `slikui/_py` adds a thin Pythonic
5
+ surface (kwargs → setters, App shell, pixel helpers). See
6
+ docs/python-bindings-plan.md.
7
+ """
8
+
9
+ from slikui._slikui import ( # noqa: F401 — re-exported
10
+ Accordion,
11
+ Button,
12
+ ButtonVariant,
13
+ Checkbox,
14
+ Color,
15
+ Column,
16
+ Container,
17
+ Context,
18
+ CrossMode,
19
+ DrawCmd,
20
+ DrawCmdType,
21
+ DrawList,
22
+ Divider,
23
+ Dropdown,
24
+ Expander,
25
+ IRenderSink,
26
+ Image,
27
+ ImageFit,
28
+ Label,
29
+ ListView,
30
+ Panel,
31
+ ProgressBar,
32
+ Radio,
33
+ RenderSink,
34
+ Row,
35
+ ScrollArea,
36
+ Sink,
37
+ Slider,
38
+ Spacer,
39
+ Spinner,
40
+ Swatch,
41
+ Switch,
42
+ Tabs,
43
+ Target,
44
+ TextArea,
45
+ TexFormat,
46
+ TextField,
47
+ Theme,
48
+ ThemePreset,
49
+ Time,
50
+ Vec2,
51
+ Widget,
52
+ apply_preset,
53
+ find_system_font,
54
+ theme_presets,
55
+ use_theme,
56
+ )
57
+ from slikui._py import builder
58
+ from slikui._py.app import App
59
+ from slikui._py.renderer import CanvasSink
60
+ from slikui._py.events import (
61
+ focus_lost,
62
+ frame_resize,
63
+ key_press,
64
+ key_release,
65
+ mods,
66
+ mouse_click,
67
+ mouse_move,
68
+ mouse_press,
69
+ mouse_release,
70
+ scroll,
71
+ text_input,
72
+ text_input_str,
73
+ )
74
+ from slikui._py.pixels import to_rgba
75
+ from slikui._slikui import ( # noqa: F401 — input enums
76
+ EventType,
77
+ Key,
78
+ Mod,
79
+ MouseButton,
80
+ set_leak_warnings,
81
+ )
82
+
83
+ from slikui._slikui import __version__ # from include/slikui/version.hpp
84
+
85
+ # Pythonic sugar on the bound classes: `container.add(slikui.Label, "hi")` and
86
+ # `ctx.set_root(slikui.Column)`. nanobind classes are heap types, so type-level
87
+ # assignment is allowed; if a future build forbids it, use the module-level
88
+ # helpers `slikui.add(container, ...)` / `slikui.set_root(context, ...)`.
89
+ _PATCHED = builder.try_patch()
90
+
91
+ __all__ = [
92
+ "Accordion", "App", "Button", "ButtonVariant", "Checkbox", "Color",
93
+ "Column", "Container", "Context", "CrossMode", "Divider", "Dropdown",
94
+ "Expander", "Image", "ImageFit", "Label", "ListView", "Panel",
95
+ "ProgressBar", "Radio", "Row", "ScrollArea", "Sink", "Slider",
96
+ "Spacer", "Spinner", "Swatch", "Switch", "Tabs", "Target", "TextArea",
97
+ "TextField", "Theme", "ThemePreset", "Time", "Vec2", "Widget",
98
+ "__version__", "add", "apply_preset", "find_system_font",
99
+ "set_root", "theme_presets", "to_rgba", "use_theme",
100
+ # events
101
+ "EventType", "Key", "Mod", "MouseButton",
102
+ "focus_lost", "frame_resize", "key_press", "key_release", "mods",
103
+ "mouse_click", "mouse_move", "mouse_press", "mouse_release",
104
+ "scroll", "text_input", "text_input_str",
105
+ ]
106
+
107
+
108
+ def add(container, cls, *args, **props):
109
+ """Fallback for `container.add(cls, *args, **props)` if type patching is off."""
110
+ return builder.container_add(container, cls, *args, **props)
111
+
112
+
113
+ def set_root(context, cls, *args, **props):
114
+ """Fallback for `context.set_root(cls, *args, **props)` if type patching is off."""
115
+ return builder.context_set_root(context, cls, *args, **props)
slikui/_py/__init__.py ADDED
File without changes
slikui/_py/app.py ADDED
@@ -0,0 +1,112 @@
1
+ """Headless app shell: Target + Sink + Context with conveniences.
2
+
3
+ `App` holds every owner (target, sink, context), so the C++ side never sees a
4
+ dangling backend even though the bindings are non-owning.
5
+ """
6
+
7
+ import atexit
8
+ import weakref
9
+
10
+ from slikui._py.builder import context_set_root
11
+ from slikui._slikui import Context, Sink, Target, find_system_font
12
+
13
+ # Weakrefs of Apps waiting for an atexit close; pruned as refs die.
14
+ _exit_close_refs = []
15
+
16
+
17
+ def _register_exit_close(app):
18
+ """Run app.close() during interpreter shutdown, before module teardown.
19
+
20
+ Python-level atexit callbacks run while every module (slikui's extension
21
+ included) is still healthy. Waiting for App.__del__ is too late: nanobind
22
+ starts releasing its internals as soon as its module is freed during
23
+ module clearing, before __main__'s globals are collected. The weakref
24
+ keeps the App collectable; close() unregisters.
25
+ """
26
+ _exit_close_refs[:] = [r for r in _exit_close_refs if r() is not None]
27
+ ref = weakref.ref(app)
28
+ _exit_close_refs.append(ref)
29
+
30
+ def _close():
31
+ a = ref()
32
+ if a is not None:
33
+ try:
34
+ a.close()
35
+ except Exception:
36
+ pass
37
+
38
+ app._exit_close = _close
39
+ atexit.register(_close)
40
+
41
+
42
+ class App:
43
+ def __init__(self, width, height, *, font=None, scale=1.0, theme=None):
44
+ self.width = int(width)
45
+ self.height = int(height)
46
+ self.scale = float(scale)
47
+ # Logical frame is width x height; the framebuffer carries the scale.
48
+ self.target = Target(int(self.width * self.scale), int(self.height * self.scale))
49
+ self.sink = Sink(self.target)
50
+ self.sink.set_scale(self.scale)
51
+ self.ctx = Context(self.sink)
52
+ self.ctx.set_frame_size(float(self.width), float(self.height))
53
+ if font is not None:
54
+ self.ctx.load_font_file(font)
55
+ elif not self.ctx.load_font_file(find_system_font()):
56
+ # No system font (minimal containers, e.g. manylinux): fall back
57
+ # to the bundled DejaVu Sans subset so text still renders.
58
+ self.ctx.load_default_font()
59
+ if theme is not None:
60
+ self.ctx.theme = theme
61
+ _register_exit_close(self)
62
+
63
+ def set_root(self, cls, *args, **props):
64
+ return context_set_root(self.ctx, cls, *args, **props)
65
+
66
+ def resize(self, width, height):
67
+ self.width, self.height = int(width), int(height)
68
+ self.target.resize(int(self.width * self.scale), int(self.height * self.scale))
69
+ self.ctx.set_frame_size(float(self.width), float(self.height))
70
+
71
+ def tick(self, now=0.0, dt=0.0):
72
+ self.ctx.tick(now, dt)
73
+
74
+ def handle(self, event):
75
+ self.ctx.handle(event)
76
+
77
+ def render(self, clear=True):
78
+ self.ctx.render(clear)
79
+ return self.target.pixels() # zero-copy (H, W) uint32, ABGR packing
80
+
81
+ def close(self):
82
+ """Destroy the widget tree now, releasing the Python callbacks it holds.
83
+
84
+ A callback capturing a widget wrapper closes a cycle through C++
85
+ ownership that the GC cannot see; destroying the tree while the
86
+ interpreter is alive is what makes shutdown leak-free. Called
87
+ automatically by __exit__/__del__/atexit; safe to call twice.
88
+ """
89
+ self.ctx.clear_tree()
90
+ ec = getattr(self, "_exit_close", None)
91
+ if ec is not None:
92
+ atexit.unregister(ec)
93
+ self._exit_close = None
94
+
95
+ def __enter__(self):
96
+ return self
97
+
98
+ def __exit__(self, *exc):
99
+ self.close()
100
+ return False
101
+
102
+ def __del__(self):
103
+ # Best effort: App is not part of any reference cycle (the cycle lives
104
+ # between the context wrapper and callbacks inside the C++ tree), so in
105
+ # the normal case this runs mid-interpreter and releases everything.
106
+ # At interpreter shutdown the same call is harmless but too late; GUI
107
+ # apps that keep their UI until exit can silence the report with
108
+ # slikui.set_leak_warnings(False).
109
+ try:
110
+ self.close()
111
+ except Exception:
112
+ pass
slikui/_py/builder.py ADDED
@@ -0,0 +1,37 @@
1
+ """kwargs → setters, and the `add`/`set_root` factories.
2
+
3
+ No registry to keep in sync: a kwarg name `foo` resolves to the method
4
+ `set_foo`, or to `foo` itself when there is no setter (e.g. `on_click=`).
5
+ A wrong name is a loud AttributeError, not a silent no-op.
6
+ """
7
+
8
+
9
+ def apply_props(widget, props):
10
+ for name, value in props.items():
11
+ fn = getattr(widget, f"set_{name}", None) or getattr(widget, name)
12
+ fn(value)
13
+ return widget
14
+
15
+
16
+ def container_add(self, cls, *args, **props):
17
+ """self.add(slikui.Label, "hi", font_size=26) → construct + apply kwargs."""
18
+ fn = getattr(self, f"add_{cls.__name__.lower()}")
19
+ return apply_props(fn(*args), props)
20
+
21
+
22
+ def context_set_root(self, cls, *args, **props):
23
+ """ctx.set_root(slikui.Column, padding=16) → construct + apply kwargs."""
24
+ fn = getattr(self, f"set_root_{cls.__name__.lower()}")
25
+ return apply_props(fn(*args), props)
26
+
27
+
28
+ def try_patch():
29
+ """Attach `add`/`set_root` to the bound classes. True on success."""
30
+ try:
31
+ from slikui._slikui import Context, Widget
32
+
33
+ Widget.add = container_add # inherited by every widget, incl. ScrollArea
34
+ Context.set_root = context_set_root
35
+ return True
36
+ except (AttributeError, TypeError):
37
+ return False
slikui/_py/events.py ADDED
@@ -0,0 +1,103 @@
1
+ """Friendly slikui::Event constructors — for tests, apps and window bridges.
2
+
3
+ `Context.handle()` consumes these directly. Positions are logical points
4
+ (Vec2-compatible pairs); `mods` accepts an int mask or a string like
5
+ "ctrl+shift". A typical click is `mouse_press(p)` then `mouse_release(p)`.
6
+ """
7
+
8
+ from slikui._slikui import Event, EventType, Key, Mod, MouseButton, Vec2
9
+
10
+ _MOD_NAMES = {
11
+ "shift": int(Mod.ModShift),
12
+ "ctrl": int(Mod.ModCtrl),
13
+ "control": int(Mod.ModCtrl),
14
+ "alt": int(Mod.ModAlt),
15
+ "option": int(Mod.ModAlt),
16
+ "super": int(Mod.ModSuper),
17
+ "cmd": int(Mod.ModSuper),
18
+ "meta": int(Mod.ModSuper),
19
+ }
20
+
21
+
22
+ def mods(value) -> int:
23
+ """Int mask, or a string like "ctrl+shift" / "alt|cmd"."""
24
+ if value is None:
25
+ return 0
26
+ if isinstance(value, int):
27
+ return value
28
+ mask = 0
29
+ for part in str(value).replace("|", "+").split("+"):
30
+ name = part.strip().lower()
31
+ if name:
32
+ mask |= _MOD_NAMES[name]
33
+ return mask
34
+
35
+
36
+ def mouse_move(pos, delta=(0.0, 0.0)):
37
+ e = Event(EventType.MouseMove)
38
+ e.pos = Vec2(*pos)
39
+ e.delta = Vec2(*delta)
40
+ return e
41
+
42
+
43
+ def mouse_press(pos, button=MouseButton.MouseLeft, mods_=0):
44
+ e = Event(EventType.MousePress)
45
+ e.pos = Vec2(*pos)
46
+ e.button = int(button)
47
+ e.mods = mods(mods_)
48
+ return e
49
+
50
+
51
+ def mouse_release(pos, button=MouseButton.MouseLeft, mods_=0):
52
+ e = Event(EventType.MouseRelease)
53
+ e.pos = Vec2(*pos)
54
+ e.button = int(button)
55
+ e.mods = mods(mods_)
56
+ return e
57
+
58
+
59
+ def mouse_click(pos, button=MouseButton.MouseLeft, mods_=0):
60
+ """Press + release pair at `pos`."""
61
+ return (mouse_press(pos, button, mods_), mouse_release(pos, button, mods_))
62
+
63
+
64
+ def scroll(pos, ticks):
65
+ e = Event(EventType.Scroll)
66
+ e.pos = Vec2(*pos)
67
+ e.scroll = Vec2(*ticks)
68
+ return e
69
+
70
+
71
+ def key_press(key, mods_=0):
72
+ e = Event(EventType.KeyPress)
73
+ e.key = key
74
+ e.mods = mods(mods_)
75
+ return e
76
+
77
+
78
+ def key_release(key, mods_=0):
79
+ e = Event(EventType.KeyRelease)
80
+ e.key = key
81
+ e.mods = mods(mods_)
82
+ return e
83
+
84
+
85
+ def text_input(char: str):
86
+ e = Event(EventType.TextInput)
87
+ e.codepoint = ord(char)
88
+ return e
89
+
90
+
91
+ def text_input_str(text: str):
92
+ """One TextInput event per Unicode scalar."""
93
+ return [text_input(ch) for ch in text]
94
+
95
+
96
+ def focus_lost():
97
+ return Event(EventType.FocusLost)
98
+
99
+
100
+ def frame_resize(width, height):
101
+ e = Event(EventType.FrameResize)
102
+ e.pos = Vec2(float(width), float(height))
103
+ return e
slikui/_py/pixels.py ADDED
@@ -0,0 +1,17 @@
1
+ """Pixel helpers.
2
+
3
+ slikui frames are `(H, W) uint32` in ABGR packing — 0xAABBGGRR values, i.e.
4
+ little-endian byte order B, G, R, A. `to_rgba` converts to the conventional
5
+ (H, W, 4) uint8 RGBA layout (a copy; the raw view is the zero-cost path).
6
+ """
7
+
8
+ import numpy as np
9
+
10
+
11
+ def to_rgba(frame):
12
+ frame = np.asarray(frame)
13
+ r = frame & 0xFF
14
+ g = (frame >> 8) & 0xFF
15
+ b = (frame >> 16) & 0xFF
16
+ a = (frame >> 24) & 0xFF
17
+ return np.stack([r, g, b, a], axis=-1).astype(np.uint8, copy=False)
slikui/_py/renderer.py ADDED
@@ -0,0 +1,329 @@
1
+ """CanvasSink — a pure-Python (numpy) renderer implementing the slikui contract.
2
+
3
+ This is the "your renderer" seam: subclass `slikui.RenderSink`, and slikui hands
4
+ you every frame as a flat command list (Rect/Line/Glyph/Image/ClipPush/ClipPop)
5
+ plus texture uploads. CanvasSink rasterizes that list into a numpy uint32
6
+ buffer with the exact same math as the C++ software backend in
7
+ `include/slikui/backends/software/software.hpp` — same blend formula, same SDF
8
+ coverage, same sampling — so frames come out pixel-identical.
9
+
10
+ Pixel format: 0xAABBGGRR uint32, straight alpha, row-major (H, W).
11
+
12
+ Usage:
13
+ sink = CanvasSink(320, 240)
14
+ ctx = slikui.Context(sink)
15
+ ctx.set_frame_size(320, 240)
16
+ ctx.load_font_file(slikui.find_system_font())
17
+ ...
18
+ ctx.tick(0.0, 1 / 60)
19
+ ctx.render() # slikui calls sink.upload/render with the draw list
20
+ frame = sink.buffer # (240, 320) uint32 view
21
+
22
+ Coordinates: DrawList commands are logical points; `set_scale(s)` maps them
23
+ to device pixels exactly like software.Sink (HiDPI).
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import numpy as np
29
+
30
+ from slikui._slikui import DrawCmdType, RenderSink, TexFormat
31
+
32
+ _F32 = np.float32
33
+ _U32 = np.uint32
34
+
35
+
36
+ def _clamp01(v):
37
+ # C++ clamp01: v < 0 ? 0 : (v > 1 ? 1 : v)
38
+ return np.where(v < _F32(0), _F32(0), np.where(v > _F32(1), _F32(1), v))
39
+
40
+
41
+ def _sd_rounded_box(px, py, bx, by, r):
42
+ """Approximate SDF of a rounded box, half-extents (bx, by), radius r.
43
+
44
+ Mirrors detail::sd_rounded_box operation-for-operation (float32) so edge
45
+ coverage matches the C++ rasterizer bit for bit.
46
+ """
47
+ qx = np.abs(px) - (bx - r)
48
+ qy = np.abs(py) - (by - r)
49
+ ax = np.maximum(qx, _F32(0))
50
+ ay = np.maximum(qy, _F32(0))
51
+ outside = np.sqrt(ax * ax + ay * ay)
52
+ return (outside + np.minimum(np.maximum(qx, qy), _F32(0))) - r
53
+
54
+
55
+ def _intersect(ax, ay, aw, ah, bx, by, bw, bh):
56
+ # Rect::intersect in float32
57
+ nx = np.maximum(ax, bx)
58
+ ny = np.maximum(ay, by)
59
+ nr = np.minimum(ax + aw, bx + bw)
60
+ nb = np.minimum(ay + ah, by + bh)
61
+ return nx, ny, np.maximum(_F32(0), nr - nx), np.maximum(_F32(0), nb - ny)
62
+
63
+
64
+ class CanvasSink(RenderSink):
65
+ """Software renderer in numpy. Subclass of slikui.RenderSink."""
66
+
67
+ def __init__(self, width, height, *, scale=1.0):
68
+ super().__init__()
69
+ self._scale = _F32(scale)
70
+ self._buf = np.zeros((int(height), int(width)), np.uint32)
71
+ self._textures = {} # id -> (h, w) or (h, w, 4) uint8
72
+ self._clips = [] # list of (x, y, w, h) float32
73
+
74
+ # -- surface ------------------------------------------------------------
75
+
76
+ @property
77
+ def buffer(self):
78
+ """The (H, W) uint32 framebuffer, 0xAABBGGRR. Zero-copy view."""
79
+ return self._buf
80
+
81
+ @property
82
+ def width(self):
83
+ return self._buf.shape[1]
84
+
85
+ @property
86
+ def height(self):
87
+ return self._buf.shape[0]
88
+
89
+ def resize(self, width, height):
90
+ self._buf = np.zeros((int(height), int(width)), np.uint32)
91
+
92
+ def set_scale(self, scale):
93
+ self._scale = _F32(scale)
94
+
95
+ @property
96
+ def scale(self):
97
+ return float(self._scale)
98
+
99
+ # -- IRenderSink ---------------------------------------------------------
100
+
101
+ def upload(self, up):
102
+ n = up.width * up.height * (4 if up.format == TexFormat.Rgba8 else 1)
103
+ data = np.frombuffer(bytes(up.pixels), np.uint8, n).copy()
104
+ if up.format == TexFormat.Rgba8:
105
+ data = data.reshape(up.height, up.width, 4)
106
+ else:
107
+ data = data.reshape(up.height, up.width)
108
+ self._textures[up.id] = data
109
+
110
+ def render(self, cmds, framebuffer_size):
111
+ self._clips = [(_F32(0), _F32(0), _F32(self.width), _F32(self.height))]
112
+ for c in cmds:
113
+ self._execute(c)
114
+
115
+ # -- command dispatch -----------------------------------------------------
116
+
117
+ def _execute(self, c):
118
+ s = self._scale
119
+ x = _F32(c.rect.x) * s
120
+ y = _F32(c.rect.y) * s
121
+ w = _F32(c.rect.w) * s
122
+ h = _F32(c.rect.h) * s
123
+ t = c.type
124
+ if t == DrawCmdType.ClipPush:
125
+ self._clips.append(_intersect(x, y, w, h, *self._clips[-1]))
126
+ elif t == DrawCmdType.ClipPop:
127
+ if len(self._clips) > 1:
128
+ self._clips.pop()
129
+ elif t == DrawCmdType.Rect:
130
+ if c.hollow:
131
+ self._stroke_rect(x, y, w, h, c.color,
132
+ _F32(c.thickness) * s, _F32(c.radius) * s)
133
+ else:
134
+ self._fill_rect(x, y, w, h, c.color, _F32(c.radius) * s)
135
+ elif t == DrawCmdType.Line:
136
+ self._stroke_line(x, y, w, h, c.color, _F32(c.thickness) * s)
137
+ elif t == DrawCmdType.Glyph:
138
+ self._draw_glyph(c, x, y, w, h)
139
+ elif t == DrawCmdType.Image:
140
+ self._draw_image(c, x, y, w, h)
141
+
142
+ # -- blending --------------------------------------------------------------
143
+
144
+ def _blend(self, X, Y, mask, sr, sg, sb, sa):
145
+ """C++ blend() over selected pixels.
146
+
147
+ sr..sa: scalar _U32 or full-grid arrays (per-pixel src color/alpha);
148
+ anything array-valued is masked down to the selected pixels here.
149
+ """
150
+ mask = mask & (X >= 0) & (Y >= 0) & (X < self.width) & (Y < self.height)
151
+ if not mask.any():
152
+ return
153
+ ys = np.broadcast_to(Y, mask.shape)[mask]
154
+ xs = np.broadcast_to(X, mask.shape)[mask]
155
+ dst = self._buf[ys, xs]
156
+ dr = dst & _U32(0xFF)
157
+ dg = (dst >> _U32(8)) & _U32(0xFF)
158
+ db = (dst >> _U32(16)) & _U32(0xFF)
159
+ da = dst >> _U32(24)
160
+ sr, sg, sb, sa = (_v[mask] if np.ndim(_v) else _v
161
+ for _v in (sr, sg, sb, sa))
162
+ ia = _U32(255) - sa
163
+ # (s*a + d*ia + 127) / 255 with uint32 floor division == C++ integer math
164
+ out_r = (sr * sa + dr * ia + _U32(127)) // _U32(255)
165
+ out_g = (sg * sa + dg * ia + _U32(127)) // _U32(255)
166
+ out_b = (sb * sa + db * ia + _U32(127)) // _U32(255)
167
+ out_a = sa + da * ia // _U32(255)
168
+ self._buf[ys, xs] = (
169
+ (out_a << _U32(24)) | (out_b << _U32(16)) |
170
+ (out_g << _U32(8)) | out_r)
171
+
172
+ def _faded_alpha(self, color_a, f):
173
+ """Color::faded: with_alpha(clamp01(a/255 * f)) -> uint8 (truncating)."""
174
+ a = (_F32(color_a) / _F32(255)) * f
175
+ a = np.minimum(np.maximum(a, _F32(0)), _F32(1))
176
+ return (_F32(255) * a).astype(np.uint8)
177
+
178
+ # -- primitives --------------------------------------------------------------
179
+
180
+ def _grid(self, y0, y1, x0, x1):
181
+ """Inclusive integer pixel grid as broadcastable (Y, X) int64 arrays."""
182
+ Y = np.arange(int(y0), int(y1) + 1, dtype=np.int64)[:, None]
183
+ X = np.arange(int(x0), int(x1) + 1, dtype=np.int64)[None, :]
184
+ return Y, X
185
+
186
+ def _clip_mask(self, X, Y):
187
+ cx, cy, cw, ch = self._clips[-1]
188
+ return (X >= cx) & (X < cx + cw) & (Y >= cy) & (Y < cy + ch)
189
+
190
+ def _full_mask(self, X, Y):
191
+ return np.ones(np.broadcast_shapes(Y.shape, X.shape), dtype=bool)
192
+
193
+ def _fill_rect(self, x, y, w, h, color, radius):
194
+ clip = self._clips[-1]
195
+ x, y, w, h = _intersect(x, y, w, h, *clip)
196
+ if w <= 0 or h <= 0:
197
+ return
198
+ if radius <= 0:
199
+ # NOTE: matches C++ exactly — the fast path loops from int(r.x)
200
+ # with no per-pixel clip check, so a fractional origin paints the
201
+ # pixel below clip.x too. Quirk kept on purpose.
202
+ Y, X = self._grid(y, np.ceil(y + h) - 1, x, np.ceil(x + w) - 1)
203
+ self._blend(X, Y, self._full_mask(X, Y),
204
+ _U32(color.r), _U32(color.g), _U32(color.b),
205
+ _U32(color.a))
206
+ return
207
+ # Per-pixel SDF coverage (this path does check the clip per pixel).
208
+ Y, X = self._grid(y - 1, np.ceil(y + h), x - 1, np.ceil(x + w))
209
+ px = (X.astype(_F32) + _F32(0.5) - x) - w * _F32(0.5)
210
+ py = (Y.astype(_F32) + _F32(0.5) - y) - h * _F32(0.5)
211
+ d = _sd_rounded_box(px, py, w * _F32(0.5), h * _F32(0.5), radius)
212
+ cov = _clamp01(_F32(0.5) - d)
213
+ mask = (cov > _F32(0)) & self._clip_mask(X, Y)
214
+ sa = self._faded_alpha(color.a, cov).astype(_U32)
215
+ self._blend(X, Y, mask, _U32(color.r), _U32(color.g), _U32(color.b), sa)
216
+
217
+ def _stroke_rect(self, x, y, w, h, color, thickness, radius):
218
+ inner_r = np.maximum(radius - thickness, _F32(0))
219
+ Y, X = self._grid(y - 1, np.ceil(y + h), x - 1, np.ceil(x + w))
220
+ px = (X.astype(_F32) + _F32(0.5) - x) - w * _F32(0.5)
221
+ py = (Y.astype(_F32) + _F32(0.5) - y) - h * _F32(0.5)
222
+ d_out = _sd_rounded_box(px, py, w * _F32(0.5), h * _F32(0.5), radius)
223
+ bi_x = np.maximum(w * _F32(0.5) - thickness, _F32(0))
224
+ bi_y = np.maximum(h * _F32(0.5) - thickness, _F32(0))
225
+ d_in = _sd_rounded_box(px, py, bi_x, bi_y, inner_r)
226
+ cov = _clamp01(_F32(0.5) - d_out) * _clamp01(_F32(0.5) + d_in)
227
+ mask = (cov > _F32(0)) & self._clip_mask(X, Y)
228
+ sa = self._faded_alpha(color.a, cov).astype(_U32)
229
+ self._blend(X, Y, mask, _U32(color.r), _U32(color.g), _U32(color.b), sa)
230
+
231
+ def _stroke_line(self, x, y, dx, dy, color, thickness):
232
+ ax, ay = x, y
233
+ bx = x + dx
234
+ by = y + dy
235
+ half = thickness * _F32(0.5)
236
+ bx0 = np.minimum(ax, bx) - half - _F32(1)
237
+ by0 = np.minimum(ay, by) - half - _F32(1)
238
+ bw = np.abs(bx - ax) + _F32(2) * half + _F32(2)
239
+ bh = np.abs(by - ay) + _F32(2) * half + _F32(2)
240
+ cx, cy, cw, ch = self._clips[-1]
241
+ bx0, by0, bw, bh = _intersect(bx0, by0, bw, bh, cx, cy, cw, ch)
242
+ Y, X = self._grid(by0, np.ceil(by0 + bh), bx0, np.ceil(bx0 + bw))
243
+ px = X.astype(_F32) + _F32(0.5)
244
+ py = Y.astype(_F32) + _F32(0.5)
245
+ ddx = bx - ax
246
+ ddy = by - ay
247
+ len2 = ddx * ddx + ddy * ddy
248
+ with np.errstate(divide="ignore", invalid="ignore"):
249
+ t = ((px - ax) * ddx + (py - ay) * ddy) / len2
250
+ t = np.where(len2 > _F32(0), t, _F32(0))
251
+ t = _clamp01(t)
252
+ proj_x = ax + ddx * t
253
+ proj_y = ay + ddy * t
254
+ dist = np.sqrt((px - proj_x) * (px - proj_x) +
255
+ (py - proj_y) * (py - proj_y))
256
+ cov = _clamp01((half + _F32(0.5)) - dist)
257
+ mask = cov > _F32(0)
258
+ sa = self._faded_alpha(color.a, cov).astype(_U32)
259
+ self._blend(X, Y, mask, _U32(color.r), _U32(color.g), _U32(color.b), sa)
260
+
261
+ def _uv_maps(self, c, x, y, w, h, X, Y):
262
+ # shared uv math for glyph/image: sample point from pixel center
263
+ fx = X.astype(_F32) + _F32(0.5)
264
+ fy = Y.astype(_F32) + _F32(0.5)
265
+ u = _F32(c.uv.x) + ((fx - x) / w) * _F32(c.uv.w)
266
+ v = _F32(c.uv.y) + ((fy - y) / h) * _F32(c.uv.h)
267
+ return u, v
268
+
269
+ def _draw_glyph(self, c, x, y, w, h):
270
+ tex = self._textures.get(c.tex_id)
271
+ if tex is None or c.uv.w <= 0 or c.uv.h <= 0:
272
+ return
273
+ clip = self._clips[-1]
274
+ cx, cy, cw, ch = _intersect(x, y, w, h, *clip)
275
+ if cw <= 0 or ch <= 0:
276
+ return
277
+ Y, X = self._grid(cy, np.ceil(cy + ch) - 1, cx, np.ceil(cx + cw) - 1)
278
+ u, v = self._uv_maps(c, x, y, w, h, X, Y)
279
+ alpha = self._sample_alpha(tex, u, v)
280
+ # Color c = cmd.color.faded(a.a / 255); blend only where a > 0.
281
+ f = alpha.astype(_F32) / _F32(255)
282
+ sa = self._faded_alpha(c.color.a, f).astype(_U32)
283
+ mask = sa > _U32(0)
284
+ self._blend(X, Y, mask, _U32(c.color.r), _U32(c.color.g),
285
+ _U32(c.color.b), sa)
286
+
287
+ def _sample_alpha(self, tex, u, v):
288
+ """detail::sample_alpha — bilinear alpha, clamped, rounded (+0.5)."""
289
+ th, tw = tex.shape
290
+ fx = np.maximum(np.minimum(u, _F32(tw - 1)), _F32(0))
291
+ fy = np.maximum(np.minimum(v, _F32(th - 1)), _F32(0))
292
+ x0 = fx.astype(np.int64)
293
+ y0 = fy.astype(np.int64)
294
+ x1 = np.minimum(x0 + 1, tw - 1)
295
+ y1 = np.minimum(y0 + 1, th - 1)
296
+ tx = fx - x0.astype(_F32)
297
+ ty = fy - y0.astype(_F32)
298
+ a00 = tex[y0, x0].astype(_F32)
299
+ a10 = tex[y0, x1].astype(_F32)
300
+ a01 = tex[y1, x0].astype(_F32)
301
+ a11 = tex[y1, x1].astype(_F32)
302
+ a = (a00 * (_F32(1) - tx) + a10 * tx) * (_F32(1) - ty) + \
303
+ (a01 * (_F32(1) - tx) + a11 * tx) * ty
304
+ return (a + _F32(0.5)).astype(np.uint8)
305
+
306
+ def _draw_image(self, c, x, y, w, h):
307
+ tex = self._textures.get(c.tex_id)
308
+ if tex is None:
309
+ return
310
+ clip = self._clips[-1]
311
+ cx, cy, cw, ch = _intersect(x, y, w, h, *clip)
312
+ if cw <= 0 or ch <= 0:
313
+ return
314
+ Y, X = self._grid(cy, np.ceil(cy + ch) - 1, cx, np.ceil(cx + cw) - 1)
315
+ u, v = self._uv_maps(c, x, y, w, h, X, Y)
316
+ if tex.ndim == 3:
317
+ # Rgba8: nearest sample, blend the texel's own color/alpha.
318
+ th, tw = tex.shape[:2]
319
+ sx = np.clip(u.astype(np.int64), 0, tw - 1)
320
+ sy = np.clip(v.astype(np.int64), 0, th - 1)
321
+ px = tex[sy, sx]
322
+ self._blend(X, Y, self._full_mask(X, Y),
323
+ px[..., 0].astype(_U32), px[..., 1].astype(_U32),
324
+ px[..., 2].astype(_U32), px[..., 3].astype(_U32))
325
+ else:
326
+ # Alpha8 image: white with the sampled alpha (sample_alpha path).
327
+ alpha = self._sample_alpha(tex, u, v)
328
+ self._blend(X, Y, alpha > 0, _U32(255), _U32(255), _U32(255),
329
+ alpha.astype(_U32))
Binary file
slikui/py.typed ADDED
File without changes
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.4
2
+ Name: slikui-python
3
+ Version: 0.1.1
4
+ Summary: Python bindings for slikui — a header-only C++20 UI library
5
+ Author: slikui contributors
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ License-File: third_party/fonts/LICENSE-DejaVu.txt
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: C++
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Topic :: Software Development :: User Interfaces
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Project-URL: Homepage, https://github.com/jamesgregson/slikui
17
+ Project-URL: Documentation, https://github.com/jamesgregson/slikui/tree/main/python
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: numpy>=1.24
20
+ Provides-Extra: test
21
+ Requires-Dist: pytest>=8; extra == "test"
22
+ Provides-Extra: demo
23
+ Requires-Dist: glfw>=2.5; extra == "demo"
24
+ Requires-Dist: PyOpenGL; extra == "demo"
25
+ Description-Content-Type: text/markdown
26
+
27
+ # slikui — Python bindings for slikui
28
+
29
+ Install from PyPI · import `slikui` · compiled module `slikui._slikui`.
30
+
31
+ ```sh
32
+ pip install slikui-python
33
+ ```
34
+
35
+ pip picks the wheel matching your OS, CPU and Python (3.10–3.14 on Linux
36
+ x86_64/aarch64, macOS arm64, Windows x64). No wheel for your platform (e.g.
37
+ Intel macOS)? pip builds from the sdist instead — that needs a C++20 compiler.
38
+
39
+ The same wheels and sdist are attached to every
40
+ [GitHub Release](https://github.com/jamesgregson/slikui/releases).
41
+
42
+ Headless, testable UI: the software rasterizer renders into a `numpy`
43
+ framebuffer — no window, no GPU, no event loop required.
44
+
45
+ ```python
46
+ import slikui
47
+
48
+ ui = slikui.App(320, 200) # Target + Sink + Context + font (system, else bundled)
49
+ root = ui.set_root(slikui.Column, padding=16, gap=8)
50
+ root.add(slikui.Label, "Hello, slikui!", font_size=26)
51
+ root.add(slikui.Button, "Save", variant=slikui.ButtonVariant.Primary)
52
+
53
+ ui.tick(0.0, 1 / 60) # layout + animation
54
+ frame = ui.render() # zero-copy np.ndarray (H, W) uint32
55
+ ```
56
+
57
+ `App.close()` — also run by `with App(...)` and automatically at exit —
58
+ destroys the tree and releases Python callbacks it holds, so shutdown is
59
+ leak-free even when callbacks capture widget wrappers.
60
+
61
+ Build from source (the C++ side is header-only, nothing to link):
62
+
63
+ ```
64
+ pip install -e . # editable, into your venv — from the repo root
65
+ python -m pytest python/tests # 38 tests: widgets, events, renderer parity
66
+ python python/examples/hello.py # writes hello.ppm
67
+ ```
68
+
69
+ Events are first-class: build slikui events in Python and drive the real
70
+ dispatch path (see `python/tests/test_events.py`):
71
+
72
+ ```python
73
+ ui.handle(slikui.mouse_press((60, 40))) # click through hit-testing
74
+ ui.handle(slikui.text_input("h")) # into the focused TextField
75
+ ui.handle(slikui.scroll(pos, (0, -1))) # wheel, "ctrl+shift" mods supported
76
+ ```
77
+
78
+ Interactive demo — a Python-native GLFW + OpenGL host with the slikui software
79
+ rasterizer as the UI layer (`pip install glfw PyOpenGL`):
80
+
81
+ ```
82
+ python python/examples/glfw_demo.py
83
+ ```
84
+
85
+ Lit cube/sphere/torus over a procedural sky; the controls panel is rasterized
86
+ on the CPU into a `uint32` buffer and blended over the GL scene as one
87
+ texture. Drag orbits, wheel zooms, the panel edits material/sky/light/camera.
88
+
89
+ **Your own renderer.** `slikui.RenderSink` is a trampoline over C++
90
+ `IRenderSink`: subclass it, implement `upload(tex)` and `render(commands,
91
+ fb_size)`, and slikui hands you every frame as a flat command list — point it at
92
+ pygame, moderngl, PIL, anything. `slikui.CanvasSink` is a worked example: a
93
+ pure-numpy rasterizer that produces **pixel-identical** output to the C++
94
+ software backend (same blend/SDF/sampling math, verified by test):
95
+
96
+ ```python
97
+ sink = slikui.CanvasSink(320, 240)
98
+ ctx = slikui.Context(sink) # any RenderSink subclass works here
99
+ ctx.set_frame_size(320, 240)
100
+ ctx.load_default_font() # bundled DejaVu Sans subset; or load_font_file(path)
101
+ # ... build the tree, tick ...
102
+ ctx.render()
103
+ frame = sink.buffer # (240, 320) uint32, 0xAABBGGRR
104
+ ```
105
+
106
+ See it side by side: `python python/examples/pyrenderer_demo.py` renders one
107
+ tree through both renderers and reports the pixel diff (it is 0).
108
+
109
+ Design and milestones: `docs/python-bindings-plan.md` — M0 (values, software
110
+ backend, core widgets), M1 (full widget surface + themes) and M2 (events,
111
+ popups, tooltips, simulated-input tests) are done; M3 (wheels/CI/docs) and
112
+ M4 (GLFW+GL backend bindings) are tabled.
@@ -0,0 +1,14 @@
1
+ slikui/__init__.py,sha256=LsRYckcm820yzbx1cR5y2WpbblL4NcrWSOM421sPnFg,3230
2
+ slikui/_py/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ slikui/_py/app.py,sha256=-1mLfkHDRHoFeD4_h4AjpbmswOJ_iaTLc77KZdNwg00,4211
4
+ slikui/_py/builder.py,sha256=gv5cyd3iT0msvDYlxxqsK_RYZ8u0a4BhiiJn-OHDfdk,1305
5
+ slikui/_py/events.py,sha256=AyRu0G8zkr6vYSMLn1RTROzmMWasOVuW5YzpiQR0ziw,2584
6
+ slikui/_py/pixels.py,sha256=wAjVtVxoYFbP-qGj-CtWdCak4bIhXawBTSGqQafO2Qw,519
7
+ slikui/_py/renderer.py,sha256=21dfP0uXuMCu_Ya51qu3-6Xu8c7m5iRMuMfFc0dBm5A,13801
8
+ slikui/_slikui.cp311-win32.pyd,sha256=6KlDqNwhBM7yyOwn6PHDKFheKpxl1vFmAmXq56I47fk,435712
9
+ slikui/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ slikui_python-0.1.1.dist-info/METADATA,sha256=TwSE-2_hNCPt8oepRBOmwPO5zUIPEPfH-IZAltCP478,4583
11
+ slikui_python-0.1.1.dist-info/WHEEL,sha256=dUEQb7_W0miDd4HxZswkHFIJJlnD7VWnP0QRT2YoTpE,101
12
+ slikui_python-0.1.1.dist-info/licenses/LICENSE,sha256=aBfC36-nE3my79yfCCxAgso5fE_miKCKPmwr2aWLKtU,1097
13
+ slikui_python-0.1.1.dist-info/licenses/third_party/fonts/LICENSE-DejaVu.txt,sha256=PxoRvJmiHdti0OufPtLhtTDtEyMPx55apovioKGoCC8,9003
14
+ slikui_python-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: scikit-build-core 1.1.2
3
+ Root-Is-Purelib: false
4
+ Tag: cp311-cp311-win32
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 slikui contributors
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 all
13
+ 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 THE
21
+ SOFTWARE.
@@ -0,0 +1,187 @@
1
+ Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
2
+ Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
3
+
4
+
5
+ Bitstream Vera Fonts Copyright
6
+ ------------------------------
7
+
8
+ Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
9
+ a trademark of Bitstream, Inc.
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of the fonts accompanying this license ("Fonts") and associated
13
+ documentation files (the "Font Software"), to reproduce and distribute the
14
+ Font Software, including without limitation the rights to use, copy, merge,
15
+ publish, distribute, and/or sell copies of the Font Software, and to permit
16
+ persons to whom the Font Software is furnished to do so, subject to the
17
+ following conditions:
18
+
19
+ The above copyright and trademark notices and this permission notice shall
20
+ be included in all copies of one or more of the Font Software typefaces.
21
+
22
+ The Font Software may be modified, altered, or added to, and in particular
23
+ the designs of glyphs or characters in the Fonts may be modified and
24
+ additional glyphs or characters may be added to the Fonts, only if the fonts
25
+ are renamed to names not containing either the words "Bitstream" or the word
26
+ "Vera".
27
+
28
+ This License becomes null and void to the extent applicable to Fonts or Font
29
+ Software that has been modified and is distributed under the "Bitstream
30
+ Vera" names.
31
+
32
+ The Font Software may be sold as part of a larger software package but no
33
+ copy of one or more of the Font Software typefaces may be sold by itself.
34
+
35
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
36
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
37
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
38
+ TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
39
+ FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
40
+ ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
41
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
42
+ THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
43
+ FONT SOFTWARE.
44
+
45
+ Except as contained in this notice, the names of Gnome, the Gnome
46
+ Foundation, and Bitstream Inc., shall not be used in advertising or
47
+ otherwise to promote the sale, use or other dealings in this Font Software
48
+ without prior written authorization from the Gnome Foundation or Bitstream
49
+ Inc., respectively. For further information, contact: fonts at gnome dot
50
+ org.
51
+
52
+ Arev Fonts Copyright
53
+ ------------------------------
54
+
55
+ Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
56
+
57
+ Permission is hereby granted, free of charge, to any person obtaining
58
+ a copy of the fonts accompanying this license ("Fonts") and
59
+ associated documentation files (the "Font Software"), to reproduce
60
+ and distribute the modifications to the Bitstream Vera Font Software,
61
+ including without limitation the rights to use, copy, merge, publish,
62
+ distribute, and/or sell copies of the Font Software, and to permit
63
+ persons to whom the Font Software is furnished to do so, subject to
64
+ the following conditions:
65
+
66
+ The above copyright and trademark notices and this permission notice
67
+ shall be included in all copies of one or more of the Font Software
68
+ typefaces.
69
+
70
+ The Font Software may be modified, altered, or added to, and in
71
+ particular the designs of glyphs or characters in the Fonts may be
72
+ modified and additional glyphs or characters may be added to the
73
+ Fonts, only if the fonts are renamed to names not containing either
74
+ the words "Tavmjong Bah" or the word "Arev".
75
+
76
+ This License becomes null and void to the extent applicable to Fonts
77
+ or Font Software that has been modified and is distributed under the
78
+ "Tavmjong Bah Arev" names.
79
+
80
+ The Font Software may be sold as part of a larger software package but
81
+ no copy of one or more of the Font Software typefaces may be sold by
82
+ itself.
83
+
84
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
85
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
86
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
87
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
88
+ TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
89
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
90
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
91
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
92
+ OTHER DEALINGS IN THE FONT SOFTWARE.
93
+
94
+ Except as contained in this notice, the name of Tavmjong Bah shall not
95
+ be used in advertising or otherwise to promote the sale, use or other
96
+ dealings in this Font Software without prior written authorization
97
+ from Tavmjong Bah. For further information, contact: tavmjong @ free
98
+ . fr.
99
+
100
+ TeX Gyre DJV Math
101
+ -----------------
102
+ Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
103
+
104
+ Math extensions done by B. Jackowski, P. Strzelczyk and P. Pianowski
105
+ (on behalf of TeX users groups) are in public domain.
106
+
107
+ Letters imported from Euler Fraktur from AMSfonts are (c) American
108
+ Mathematical Society (see below).
109
+ Bitstream Vera Fonts Copyright
110
+ Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera
111
+ is a trademark of Bitstream, Inc.
112
+
113
+ Permission is hereby granted, free of charge, to any person obtaining a copy
114
+ of the fonts accompanying this license (“Fonts”) and associated
115
+ documentation
116
+ files (the “Font Software”), to reproduce and distribute the Font Software,
117
+ including without limitation the rights to use, copy, merge, publish,
118
+ distribute,
119
+ and/or sell copies of the Font Software, and to permit persons to whom
120
+ the Font Software is furnished to do so, subject to the following
121
+ conditions:
122
+
123
+ The above copyright and trademark notices and this permission notice
124
+ shall be
125
+ included in all copies of one or more of the Font Software typefaces.
126
+
127
+ The Font Software may be modified, altered, or added to, and in particular
128
+ the designs of glyphs or characters in the Fonts may be modified and
129
+ additional
130
+ glyphs or characters may be added to the Fonts, only if the fonts are
131
+ renamed
132
+ to names not containing either the words “Bitstream” or the word “Vera”.
133
+
134
+ This License becomes null and void to the extent applicable to Fonts or
135
+ Font Software
136
+ that has been modified and is distributed under the “Bitstream Vera”
137
+ names.
138
+
139
+ The Font Software may be sold as part of a larger software package but
140
+ no copy
141
+ of one or more of the Font Software typefaces may be sold by itself.
142
+
143
+ THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
144
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
145
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
146
+ TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
147
+ FOUNDATION
148
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL,
149
+ SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN
150
+ ACTION
151
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR
152
+ INABILITY TO USE
153
+ THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
154
+ Except as contained in this notice, the names of GNOME, the GNOME
155
+ Foundation,
156
+ and Bitstream Inc., shall not be used in advertising or otherwise to promote
157
+ the sale, use or other dealings in this Font Software without prior written
158
+ authorization from the GNOME Foundation or Bitstream Inc., respectively.
159
+ For further information, contact: fonts at gnome dot org.
160
+
161
+ AMSFonts (v. 2.2) copyright
162
+
163
+ The PostScript Type 1 implementation of the AMSFonts produced by and
164
+ previously distributed by Blue Sky Research and Y&Y, Inc. are now freely
165
+ available for general use. This has been accomplished through the
166
+ cooperation
167
+ of a consortium of scientific publishers with Blue Sky Research and Y&Y.
168
+ Members of this consortium include:
169
+
170
+ Elsevier Science IBM Corporation Society for Industrial and Applied
171
+ Mathematics (SIAM) Springer-Verlag American Mathematical Society (AMS)
172
+
173
+ In order to assure the authenticity of these fonts, copyright will be
174
+ held by
175
+ the American Mathematical Society. This is not meant to restrict in any way
176
+ the legitimate use of the fonts, such as (but not limited to) electronic
177
+ distribution of documents containing these fonts, inclusion of these fonts
178
+ into other public domain or commercial font collections or computer
179
+ applications, use of the outline data to create derivative fonts and/or
180
+ faces, etc. However, the AMS does require that the AMS copyright notice be
181
+ removed from any derivative versions of the fonts which have been altered in
182
+ any way. In addition, to ensure the fidelity of TeX documents using Computer
183
+ Modern fonts, Professor Donald Knuth, creator of the Computer Modern faces,
184
+ has requested that any alterations which yield different font metrics be
185
+ given a different name.
186
+
187
+ $Id$