Initial rendering

This commit is contained in:
Emery Hemingway 2021-12-04 12:58:35 +00:00
commit cfdaa6dd27
10 changed files with 387 additions and 0 deletions

7
README.md Normal file
View File

@ -0,0 +1,7 @@
# Zooming User Agent
## Goals
- Zoomable
- Network transparent
- Reimplementable
- Display device agnostic (LCD, E-ink, ocular-bypass)

59
flake.lock Normal file
View File

@ -0,0 +1,59 @@
{
"nodes": {
"nimble": {
"inputs": {
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1633263146,
"narHash": "sha256-rbWhymf0OXVla79UE+sx8GKV1aK8QsOL2COwJLsvRZk=",
"owner": "nix-community",
"repo": "flake-nimble",
"rev": "e50410e3e559d4384bd0bbb47280188532d55f63",
"type": "github"
},
"original": {
"id": "nimble",
"type": "indirect"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1633193040,
"narHash": "sha256-by9sL3A+Bpv84dOcBJ0AJwDF4x+Xt5vSqvpsyG0PPMc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2edf68513fcc329d391a6268cebc77043c9f341c",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1638592813,
"narHash": "sha256-842jUCj8fTZKJHygEu75V0mQw/SmxnM9MllSM5xTLm0=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "66c4ae20a3b6fffd54fa240701af3f3d8834c7da",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "release-21.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nimble": "nimble",
"nixpkgs": "nixpkgs_2"
}
}
},
"root": "root",
"version": 7
}

47
flake.nix Normal file
View File

@ -0,0 +1,47 @@
{
description = "Zooming User Agent";
inputs.nixpkgs.url = "github:nixos/nixpkgs/release-21.11";
outputs = { self, nixpkgs, nimble }:
let
systems = [ "aarch64-linux" "x86_64-linux" ];
forAllSystems = nixpkgs.lib.genAttrs systems;
in {
overlay = final: prev: {
zua = with prev;
nimPackages.buildNimPackage rec {
pname = "zua";
version = "unstable";
nimBinOnly = true;
src = self;
nativeBuildInputs = [ pkg-config ];
buildInputs = [ fontconfig ] ++ (with nimPackages; [
bumpy
chroma
flatty
nimsimd
pixie
sdl2
typography
vmath
zippy
]);
meta = with lib; {
description = "Simple RSVP speed-reading utility";
license = licenses.unlicense;
homepage = "https://git.sr.ht/~ehmry/hottext";
maintainers = with maintainers; [ ehmry ];
};
};
};
defaultPackage = forAllSystems (system:
let pkgs = nixpkgs.legacyPackages.${system}.extend self.overlay;
in pkgs.zua);
};
}

3
protocol.prs Normal file
View File

@ -0,0 +1,3 @@
version 1.
Pane = <svg @svg string>.

151
src/zua.nim Normal file
View File

@ -0,0 +1,151 @@
# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[asyncdispatch, hashes, os, strutils, tables, xmlparser, xmltree]
import preserves, preserves/parse
import syndicate,
syndicate/[actors, capabilities, dataspaces, patterns, relay],
syndicate/protocols/[simpleChatProtocol]
import nimsvg
import pixie, pixie/fileformats/svg
import math, sdl2, sdl2/gfx
import ./zua/protocol
const
rmask = uint32 0x000000ff
gmask = uint32 0x0000ff00
bmask = uint32 0x00ff0000
amask = uint32 0xff000000
type
Pane = ref object
svg: string
image: Image
type
App = ref object
screen: Image
# ctx: Context
window: WindowPtr
renderer: RendererPtr
surface: SurfacePtr
texture: TexturePtr
panes: Table[Hash, Pane]
proc newPane(svg: string): Pane =
Pane(svg: svg)
proc newApp(width, height: int): App =
new result
discard createWindowAndRenderer(
cint width, cint height,
SDL_WINDOW_RESIZABLE,
result.window, result.renderer)
proc equalDimensions(a, b: Image): bool =
a.width == b.width and a.height == b.height
proc update(app: App; pane: Pane) =
try:
assert(not app.screen.isNil)
pane.image = decodeSvg(pane.svg) # , app.screen.width, app.screen.height)
# TODO: determine the width and height from zoom level
draw(app.screen, pane.image)
except Exception as e:
stderr.writeLine "Failed to render SVG, ", e.msg
proc update(app: App) =
let (w, h) = app.window.getSize
if app.screen.isNil or app.screen.width != w or app.screen.height != h:
app.screen = newImage(w, h)
app.screen.fill(rgba(255, 255, 255, 255))
for pane in app.panes.values:
app.update(pane)
var dataPtr = app.screen.data[0].addr
var mainSurface = createRGBSurfaceFrom(
dataPtr, cint w, cint h, cint 32, cint 4*w,
rmask, gmask, bmask, amask)
var mainTexture = app.renderer.createTextureFromSurface(mainSurface)
destroy(mainSurface)
app.renderer.clear()
app.renderer.copy(mainTexture, nil, nil)
destroy(mainTexture)
app.renderer.present()
discard sdl2.init(INIT_TIMER or INIT_VIDEO or INIT_EVENTS)
const svgTextSimple = """
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="10cm" height="3cm" viewBox="0 0 1000 300"
xmlns="http://www.w3.org/2000/svg" version="1.1">
<desc>Example text01 - 'Hello, out there' in blue</desc>
<text x="250" y="150"
font-family="Verdana" font-size="55" fill="blue" >
Hello, out there
</text>
<!-- Show outline of canvas using 'rect' element -->
<rect x="1" y="1" width="998" height="298"
fill="none" stroke="blue" stroke-width="2" />
</svg>
"""
proc main() =
let app = newApp(1024, 768)
app.update()
let cap = mint()
asyncCheck runActor("chat") do (turn: var Turn):
connectUnix(turn, "/run/syndicate/ds", cap) do (turn: var Turn; a: Assertion) -> TurnAction:
let ds = unembed a
onPublish(turn, ds, protocol.Pane ? {0: `?*`()}) do (svg: string):
echo "an SVG was published"
onRetract:
echo "an SVG was retracted"
app.panes.del(hash svg)
app.update()
# TODO: keep dead panes around until they are cleaned up.
# If something crashes then the last state should be visable.
var pane = app.panes.getOrDefault(hash svg)
if pane.isNil:
pane = newPane(svg)
app.panes[hash svg] = pane
else:
pane.svg = svg
app.update(pane)
app.update() # TODO: wait til end of turn
var
svgHandle: Handle
groups: Table[Hash, Nodes]
const
sdlTimeout = 500
asyncPollTimeout = 500
var evt = sdl2.defaultEvent
asyncdispatch.poll(0)
while true:
# TODO asyncdispatch.poll(0) or make an SDL event thread
if not waitEventTimeout(evt, sdlTimeout):
asyncdispatch.poll(0)
else:
case evt.kind
of QuitEvent:
quit(0)
of WindowEvent:
if evt.window.event == WindowEvent_Resized:
app.update()
else: discard
main()

1
src/zua/Tupfile Normal file
View File

@ -0,0 +1 @@
: ../../protocol.prs |> preserves_schema_nim %f |> protocol.nim

13
src/zua/protocol.nim Normal file
View File

@ -0,0 +1,13 @@
import
std/typetraits, preserves
type
Pane* {.preservesRecord: "svg".} = object
`svg`*: string
proc `$`*(x: Pane): string =
`$`(toPreserve(x))
proc encode*(x: Pane): seq[byte] =
encode(toPreserve(x))

38
tests/client.nim Normal file
View File

@ -0,0 +1,38 @@
# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[asyncdispatch, os, parseopt, xmlparser, xmltree]
import preserves
import syndicate,
syndicate/[actors, capabilities, relay]
import ../src/zua/protocol
proc quitHook() {.noconv.} =
quit()
proc main() =
setControlCHook(quitHook)
waitFor runActor("client") do (turn: var Turn):
let cap = mint()
connectUnix(turn, "/run/syndicate/ds", cap) do (turn: var Turn; a: Assertion) -> TurnAction:
let ds = unembed a
for kind, arg, _ in getopt():
if kind == cmdArgument:
let megs = getFileSize(arg) shr 20
if megs > 1:
stderr.writeLine arg, " is ", megs, " MiB, not publishing"
else:
let xml = loadXml(arg)
if xml.tag != "svg":
stderr.writeLine arg, " not recognized as SVG"
else:
discard publish(turn, ds, Pane(svg: $xml))
stderr.writeLine "done"
main()

55
tests/gemsvg.nim Normal file
View File

@ -0,0 +1,55 @@
# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[asyncdispatch, os, parseopt, strutils, xmltree]
import preserves, preserves/xmlhooks
import syndicate, syndicate/[actors, capabilities, relay]
import parsegemini
import gemclient
import nimsvg
import ../src/zua/protocol
proc quitHook() {.noconv.} =
quit()
proc main() =
setControlCHook(quitHook)
waitFor runActor("client") do (turn: var Turn):
let cap = mint()
connectUnix(turn, "/run/syndicate/ds", cap) do (turn: var Turn; a: Assertion) -> TurnAction:
let ds = unembed a
let client = newGeminiClient()
for kind, arg, _ in getopt():
if kind == cmdArgument and arg.startsWith"gemini":
var
nodes: seq[Nodes]
p: GeminiParser
let resp = client.fetch(arg)
open(p, resp.bodyStream)
# TODO: append to a text block until running into verbatim text,
# then push the current text and open a new block
while true:
p.next()
case p.kind
of gmiEof: break
else:
let n = buildSvg:
t: xmltree.escape p.text
nodes.add n
close(p)
let doc = buildSvg:
svg(viewBox="0 0 600 800", `font-family` = "Gentium Plus", `font-size`="20"):
text:
for n in nodes: embed n
let buf = render doc
stdout.writeLine buf
discard publish(turn, ds, Pane(svg: buf))
stderr.writeLine "done"
main()

13
zua.nimble Normal file
View File

@ -0,0 +1,13 @@
# Package
version = "0.1.0"
author = "Emery Hemingway"
description = "Zooming User Agent"
license = "Unlicense"
srcDir = "src"
bin = @["zua"]
# Dependencies
requires "nim >= 1.6.0"