Rendering¶
Drawing a layout as a picture, for looking at rather than for loading. Layout defects are visual and coordinates are not: a sizes that divides wrongly, a gap that eats a row and a labelled that mis-splits are otherwise assertable one frame at a time.
Nothing reads SVG back, which is why this is render rather than a convert target: convert means the same layout in another encoding and implies the reverse works.
What it promises is that a reader can see where every control is, how big it is and what kind it is -- not that it matches a screenshot. TouchOSC cannot be scripted, so there is no oracle for pixel fidelity. A Lua script can set any property at runtime, so what is drawn is the document rather than the running state.
Rules live in one <style> inside the SVG, so the file travels alone and draws through an <img src>. Classes are prefixed p2t- so an inline SVG cannot leak selectors into the page. Per-control values are emitted as custom properties rather than baked into coordinates: a fader's bar is driven by --v.
to_svg(doc, clip=True) cuts a control off at the edge of the one holding it, as TouchOSC does. Off by default: an overflowing control is a defect the picture is being drawn to find.
to_html wraps the same SVG in a page carrying the layout's name, its control count and whatever validate found. The output extension picks the wrapper.
Pages of a pager are laid out side by side in HTML as well, not behind tabs. pagers.tosc holds four pagers and :target matches one element per document, so independent tabs would need radio inputs positioned over the SVG at each pager's coordinates plus a second rendering mode where pages stack.
Provisional under the stability policy, like py2tosc.ui.
to_svg ¶
Draw one layout as SVG.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
doc
|
Document
|
The layout. Resolve it first if it was built by |
required |
clip
|
bool
|
Whether a control is cut off at the edge of the one holding it, the way TouchOSC draws it. Off by default, and the default is the decision: a control that overflows its parent is exactly the kind of defect a picture is being drawn to find, and clipping hides it behind a layout that looks correct. Turn it on to see what the device will actually show. A pager is never clipped either way, since its pages are laid out past its own frame on purpose. |
False
|
Returns:
| Type | Description |
|---|---|
str
|
The SVG, as text. |
Source code in src/py2tosc/render.py
to_html ¶
Draw one layout as a page with the picture in it.
The same SVG to_svg produces, with a page around it -- not a second
renderer. That is what keeps anything added later, a runtime included,
additive rather than a second code path to keep in step.
What the page adds is the context a bare picture has nowhere to put: what
the layout is called, how many controls it holds, and what validate says
about it. A layout with a control the editor will refuse is worth knowing
about while looking at the picture rather than afterwards.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
doc
|
Document
|
The layout, resolved. |
required |
clip
|
bool
|
As |
False
|
Returns:
| Type | Description |
|---|---|
str
|
A complete HTML document, as text. |
Source code in src/py2tosc/render.py
STYLESHEET
module-attribute
¶
STYLESHEET = f'
.{PREFIX}-shape {
fill: var(--fill, none);
fill-opacity: 0.45;
stroke: var(--line, none);
}
.{PREFIX}-shape.{PREFIX}-outlined { stroke-width: 2; }
.{PREFIX}-shape.{PREFIX}-track { fill-opacity: 0.15; }
.{PREFIX}-mark { stroke: var(--line, #888); stroke-width: 1; fill: none; }
.{PREFIX}-bar {
fill: var(--line, #888);
fill-opacity: 0.55;
transform-box: fill-box;
transform-origin: bottom;
transform: scaleY(var(--v, 0));
}
.{PREFIX}-bar.{PREFIX}-across {
transform-origin: left;
transform: scaleX(var(--v, 0));
}
/* The handle, and the reason a fader at zero is still legible: it has real
thickness and rides `--v` up the track, where a bar scaled to nothing
leaves an empty box. `--span` is the travel, which is static. */
.{PREFIX}-cursor {
fill: var(--line, #888);
transform: translateY(calc((1 - var(--v, 0)) * var(--span, 0px)));
}
.{PREFIX}-cursor.{PREFIX}-across {
transform: translateX(calc(var(--v, 0) * var(--span, 0px)));
}
.{PREFIX}-rule { stroke: var(--line, #888); stroke-width: 1; opacity: 0.35; }
.{PREFIX}-step { fill: var(--line, #888); fill-opacity: 0.55; }
.{PREFIX}-page-name { fill: #888; }
.{PREFIX}-cap { fill: none; stroke: var(--line, #888); stroke-width: 1.5; }
.{PREFIX}-dial { fill: none; stroke: var(--line, #888); stroke-width: 2; }
.{PREFIX}-cross { stroke: var(--line, #888); stroke-width: 1; opacity: 0.8; }
.{PREFIX}-text {
fill: var(--ink, #111);
font-family: var(--face, system-ui, sans-serif);
dominant-baseline: middle;
}
.{PREFIX}-hidden { opacity: 0.25; }
.{PREFIX}-hidden .{PREFIX}-shape { stroke-dasharray: 4 3; }
'