Compare commits
3 Commits
0018f122a5
...
ceb8f3a1c8
Author | SHA1 | Date |
---|---|---|
Emery Hemingway | ceb8f3a1c8 | |
Emery Hemingway | c9511f99db | |
Emery Hemingway | 6ec86cfa27 |
|
@ -0,0 +1,101 @@
|
|||
# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway
|
||||
# SPDX-License-Identifier: Unlicense
|
||||
|
||||
import std/sequtils
|
||||
import pixie, pixie/systemtypefaces
|
||||
import svui
|
||||
|
||||
type Typesetting* = object
|
||||
h1, h2, h3, p: Font
|
||||
|
||||
proc newFont(typeface: Typeface, size: float32): Font =
|
||||
result = newFont(typeface)
|
||||
result.size = size
|
||||
|
||||
proc initTypesetting*(): Typesetting =
|
||||
let typeface = readTypeface findSystemTypeface(family = "Gentium Plus")
|
||||
result.h1 = newFont(typeface, 24)
|
||||
result.h2 = newFont(typeface, 20)
|
||||
result.h3 = newFont(typeface, 16)
|
||||
result.p = newFont(typeface, 12)
|
||||
|
||||
proc body(xhtml: Xhtml): XmlElement =
|
||||
for e in xhtml.elements:
|
||||
if e.orKind == XmlNodeKind.`XmlElement` and e.xmlElement.name == "body":
|
||||
return e.xmlElement
|
||||
raise newException(ValueError, "no body in XHTML document")
|
||||
|
||||
proc name(node: XmlNode): string = node.xmlElement.name
|
||||
|
||||
proc text(node: XmlNode): string =
|
||||
case node.orKind
|
||||
of XmlNodeKind.XmlElement:
|
||||
for e in node.xmlElement.elements:
|
||||
result.add e.text
|
||||
of XmlNodeKind.XmlText:
|
||||
result.add node.xmlText.data
|
||||
|
||||
func height(arr: Arrangement): float =
|
||||
if arr.positions.len > 0: result = arr.positions[arr.positions.high].y
|
||||
|
||||
proc add(result: var Arrangement; other: sink Arrangement) =
|
||||
if result.lines.len == 0:
|
||||
result = other
|
||||
else:
|
||||
let runeOff = result.runes.len
|
||||
add(result.lines, map(other.lines,
|
||||
proc (x: (int, int)): (int, int) = (x[0]+runeOff, x[1]+runeOff)))
|
||||
add(result.spans, map(other.spans,
|
||||
proc (x: (int, int)): (int, int) = (x[0]+runeOff, x[1]+runeOff)))
|
||||
add(result.fonts, other.fonts)
|
||||
add(result.runes, other.runes)
|
||||
let yOff = result.positions[result.positions.high].y
|
||||
add(result.positions,
|
||||
map(other.positions,
|
||||
proc(pos: Vec2): Vec2 = vec2(pos.x, pos.y + yOff)))
|
||||
add(result.selectionRects,
|
||||
map(other.selectionRects,
|
||||
proc(rect: Rect): Rect = rect(rect.x, rect.y + yOff, rect.w, rect.h)))
|
||||
|
||||
proc render*(ts: Typesetting; xhtml: Xhtml): Image =
|
||||
# TODO: render by font size, not by wh
|
||||
var wh = computeBounds(ts.p, "X")
|
||||
wh.x = wh.x * 80
|
||||
wh.y = wh.y * 50
|
||||
let margin = wh / 9.0
|
||||
var
|
||||
printSpace = wh * (7.0 / 9.0)
|
||||
pages = @[Arrangement()]
|
||||
|
||||
proc pageEnd(): float =
|
||||
let
|
||||
pi = pages.high
|
||||
li = pages[pi].lines.high
|
||||
ci = pages[pi].lines[li][1]
|
||||
pages[pi].positions[ci].y
|
||||
|
||||
proc append(span: Span) =
|
||||
var arr = typeset(@[span], printSpace)
|
||||
if pages[pages.high].height + arr.height < printSpace.y:
|
||||
pages[pages.high].add arr
|
||||
else:
|
||||
discard
|
||||
|
||||
for e in xhtml.body.elements:
|
||||
let tag = e.name
|
||||
case tag
|
||||
of "h1":
|
||||
append newSpan(e.text & "\n", ts.h1)
|
||||
of "h2":
|
||||
append newSpan("\n" & e.text & "\n", ts.h2)
|
||||
of "h3":
|
||||
append newSpan("\n" & e.text & "\n", ts.h3)
|
||||
of "p":
|
||||
append newSpan(" " & e.text & "\n\n", ts.p)
|
||||
else:
|
||||
discard
|
||||
# raise newException(ValueError, "unhandled element in XHTML :" & tag)
|
||||
|
||||
result = newImage(int wh.x, int wh.y)
|
||||
fill(result, rgba(255, 255, 255, 255))
|
||||
fillText(result, pages[0], translate(margin))
|
147
src/zua.nim
147
src/zua.nim
|
@ -1,8 +1,8 @@
|
|||
# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway
|
||||
# SPDX-License-Identifier: Unlicense
|
||||
|
||||
import std/[asyncdispatch, hashes, os, tables]
|
||||
import preserves
|
||||
import std/[asyncdispatch, hashes, options, os, tables, xmltree]
|
||||
import preserves, preserves/parse, preserves/xmlhooks
|
||||
import syndicate,
|
||||
syndicate/[actors, capabilities, dataspaces, patterns, relay],
|
||||
syndicate/protocols/[simpleChatProtocol]
|
||||
|
@ -11,14 +11,16 @@ import nimsvg
|
|||
import bumpy, pixie, pixie/fileformats/svg
|
||||
|
||||
import sdl2
|
||||
|
||||
import svui
|
||||
import ./private/render/xhtml
|
||||
|
||||
type
|
||||
Svui = svui.Svui[Ref]
|
||||
SdlError = object of CatchableError
|
||||
Attrs = Table[string, Assertion]
|
||||
|
||||
func toVec2(p: Point): Vec2 = vec2(float p.x, float p.y)
|
||||
|
||||
template check(res: cint) =
|
||||
if res != 0:
|
||||
let msg = $sdl2.getError()
|
||||
|
@ -36,10 +38,16 @@ const
|
|||
bmask = uint32 0x00ff0000
|
||||
|
||||
type
|
||||
PaneKind = enum pkSvg, pkXhtml
|
||||
|
||||
Pane = ref object
|
||||
svg: string
|
||||
texture: TexturePtr
|
||||
rect: bumpy.Rect
|
||||
case kind: PaneKind
|
||||
of pkSvg:
|
||||
svg: string
|
||||
of pkXhtml:
|
||||
xhtml: Xhtml
|
||||
|
||||
App = ref object
|
||||
screen: Image
|
||||
|
@ -48,39 +56,56 @@ type
|
|||
panes: Table[Hash, Pane]
|
||||
viewPoint: Vec2
|
||||
zoomFactor: float
|
||||
typesetting: Typesetting
|
||||
|
||||
proc newPane(app: App; svg: string): Pane =
|
||||
let (w, h) = app.window.getSize
|
||||
result = Pane(svg: svg, rect: rect(0, 0, float w, float h))
|
||||
try:
|
||||
var
|
||||
image = decodeSvg(svg, w, h)
|
||||
dataPtr = image.data[0].addr
|
||||
surface = createRGBSurfaceFrom(
|
||||
dataPtr, cint w, h, cint 32, cint 4*w,
|
||||
rmask, gmask, bmask, amask)
|
||||
result.texture = createTextureFromSurface(app.renderer, surface)
|
||||
destroy surface
|
||||
#[
|
||||
result.texture = createTexture(
|
||||
app.renderer,
|
||||
SDL_PIXELFORMAT_ARGB8888,
|
||||
SDL_TEXTUREACCESS_STREAMING,
|
||||
w div 2, h div 2)
|
||||
updateTexture(result.texture, nil, image.data[0].addr, cint 32)
|
||||
]#
|
||||
except Exception as e:
|
||||
destroyTexture(result.texture)
|
||||
stderr.writeLine "Failed to render SVG, ", e.msg
|
||||
result = Pane(kind: pkSvg, svg: svg, rect: rect(0, 0, float w, float h))
|
||||
var
|
||||
image = decodeSvg(svg, w, h)
|
||||
dataPtr = image.data[0].addr
|
||||
surface = createRGBSurfaceFrom(
|
||||
dataPtr, cint w, h, cint 32, cint 4*w,
|
||||
rmask, gmask, bmask, amask)
|
||||
result.texture = createTextureFromSurface(app.renderer, surface)
|
||||
destroy surface
|
||||
#[
|
||||
result.texture = createTexture(
|
||||
app.renderer,
|
||||
SDL_PIXELFORMAT_ARGB8888,
|
||||
SDL_TEXTUREACCESS_STREAMING,
|
||||
w div 2, h div 2)
|
||||
updateTexture(result.texture, nil, image.data[0].addr, cint 32)
|
||||
]#
|
||||
|
||||
func rect(img: Image): bumpy.Rect =
|
||||
result.w = float img.width
|
||||
result.h = float img.height
|
||||
|
||||
proc newPane(app: App; xhtml: Xhtml): Pane =
|
||||
var
|
||||
image = render(app.typesetting, xhtml)
|
||||
dataPtr = image.data[0].addr
|
||||
surface = createRGBSurfaceFrom(
|
||||
dataPtr,
|
||||
cint image.width, cint image.height,
|
||||
cint 32, cint 4*image.width,
|
||||
rmask, gmask, bmask, amask)
|
||||
result = Pane(
|
||||
kind: pkXhtml,
|
||||
xhtml: xhtml,
|
||||
rect: image.rect,
|
||||
texture: createTextureFromSurface(app.renderer, surface))
|
||||
destroy surface
|
||||
assert(result.rect.w != 0)
|
||||
assert(result.rect.h != 0)
|
||||
|
||||
proc newApp(length: cint): App =
|
||||
## Create a new square plane of `length` pixels.
|
||||
var wh = vec2(float length, float length)
|
||||
result = App(zoomFactor: 1.0)
|
||||
result = App(zoomFactor: 1.0, typesetting: initTypesetting())
|
||||
discard createWindowAndRenderer(
|
||||
length, length,
|
||||
SDL_WINDOW_SHOWN,
|
||||
# SDL_WINDOW_RESIZABLE,
|
||||
SDL_WINDOW_RESIZABLE,
|
||||
result.window, result.renderer)
|
||||
var info: RendererInfo
|
||||
check getRendererInfo(result.renderer, addr info)
|
||||
|
@ -100,7 +125,7 @@ proc redraw(app: App) =
|
|||
(w, h) = app.window.getSize
|
||||
sdlViewPort = rect(-float(w shr 1), -float(h shr 1), float w, float h)
|
||||
viewPort = app.viewPort(sdlViewPort.wh)
|
||||
app.renderer.setDrawColor(0xff, 0xff, 0xff)
|
||||
app.renderer.setDrawColor(0x40, 0x40, 0x40)
|
||||
app.renderer.clear()
|
||||
for pane in app.panes.values:
|
||||
if overlaps(viewPort, pane.rect):
|
||||
|
@ -108,7 +133,8 @@ proc redraw(app: App) =
|
|||
overlap = viewPort and pane.rect
|
||||
src = rect(overlap.xy - pane.rect.xy, overlap.wh)
|
||||
dst: bumpy.Rect
|
||||
dst.xy = (overlap.xy - viewPort.xy) * (sdlViewPort.w / viewPort.w)
|
||||
dst.x = (overlap.x - viewPort.x) * (sdlViewPort.w / viewPort.w)
|
||||
dst.y = (overlap.y - viewPort.y) * (sdlViewPort.h / viewPort.h)
|
||||
dst.wh =
|
||||
if app.zoomFactor == 1.0:
|
||||
overlap.wh # correct
|
||||
|
@ -120,6 +146,10 @@ proc redraw(app: App) =
|
|||
app.renderer.copy(pane.texture, addr sdlSrc, addr sdlDst)
|
||||
app.renderer.present()
|
||||
|
||||
proc resize(app: App) =
|
||||
## Resize to new dimensions of the SDL window.
|
||||
redraw(app)
|
||||
|
||||
proc zoom(app: App; change: float) =
|
||||
app.zoomFactor = app.zoomFactor * (1.0 + ((1 / 8) * change))
|
||||
app.redraw()
|
||||
|
@ -145,19 +175,46 @@ proc main() =
|
|||
connectUnix(turn, "/run/syndicate/ds", cap) do (turn: var Turn; a: Assertion) -> TurnAction:
|
||||
let ds = unembed a
|
||||
|
||||
onPublish(turn, ds, Svui ? {0: drop(), 1: grab()}) do (svg: string):
|
||||
onRetract:
|
||||
app.panes.del(hash svg)
|
||||
app.redraw()
|
||||
# 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(app, svg)
|
||||
app.panes[hash svg] = pane
|
||||
else:
|
||||
pane.svg = svg
|
||||
app.redraw() # TODO: wait til end of turn
|
||||
onPublish(turn, ds, Svui ? {0: drop(), 1: grab()}) do (content: Content):
|
||||
case content.orKind
|
||||
of ContentKind.Xhtml:
|
||||
# content.xhtml
|
||||
onRetract:
|
||||
app.panes.del(hash content.xhtml)
|
||||
app.redraw()
|
||||
# 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 content.xhtml)
|
||||
if pane.isNil:
|
||||
#try:
|
||||
block:
|
||||
pane = newPane(app, content.xhtml)
|
||||
app.panes[hash content.xhtml] = pane
|
||||
#except:
|
||||
# let msg = getCurrentExceptionMsg()
|
||||
# stderr.writeLine "failed to render XHMTL: ", msg
|
||||
else:
|
||||
pane.xhtml = content.xhtml
|
||||
app.redraw() # TODO: wait til end of turn
|
||||
of ContentKind.Svg:
|
||||
let xn = preserveTo(toPreserve(content.xhtml), xmltree.XmlNode)
|
||||
let svg = $(get xn)
|
||||
onRetract:
|
||||
app.panes.del(hash svg)
|
||||
app.redraw()
|
||||
# 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:
|
||||
try:
|
||||
pane = newPane(app, svg)
|
||||
app.panes[hash svg] = pane
|
||||
except:
|
||||
let msg = getCurrentExceptionMsg()
|
||||
stderr.writeLine "failed to render SVG: ", msg
|
||||
else:
|
||||
pane.svg = svg
|
||||
app.redraw() # TODO: wait til end of turn
|
||||
|
||||
const
|
||||
sdlTimeout = 500
|
||||
|
@ -174,7 +231,7 @@ proc main() =
|
|||
app.zoom(evt.wheel.y.float)
|
||||
of WindowEvent:
|
||||
if evt.window.event == WindowEvent_Resized:
|
||||
app.redraw()
|
||||
app.resize()
|
||||
of MouseMotion:
|
||||
if mousePanning:
|
||||
var xy = vec2(evt.motion.xrel.float, evt.motion.yrel.float)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# SPDX-License-Identifier: Unlicense
|
||||
|
||||
import std/[asyncdispatch, os, parseopt, xmlparser, xmltree]
|
||||
import preserves
|
||||
import preserves, preserves/parse, preserves/xmlhooks
|
||||
import syndicate,
|
||||
syndicate/[actors, capabilities, relay]
|
||||
|
||||
|
@ -26,11 +26,12 @@ proc main() =
|
|||
if megs > 1:
|
||||
stderr.writeLine arg, " is ", megs, " MiB, not publishing"
|
||||
else:
|
||||
let xml = loadXml(arg)
|
||||
var xml = loadXml(arg)
|
||||
if xml.tag != "svg":
|
||||
stderr.writeLine arg, " not recognized as SVG"
|
||||
else:
|
||||
discard publish(turn, ds, Svui(svg: $xml))
|
||||
var pr = toPreserve(Svui(), Ref)
|
||||
pr[1] = toPreserve(xml, Ref)
|
||||
discard publish(turn, ds, pr)
|
||||
|
||||
stderr.writeLine "done"
|
||||
|
||||
|
|
|
@ -10,4 +10,4 @@ bin = @["zua"]
|
|||
|
||||
# Dependencies
|
||||
|
||||
requires "nim >= 1.6.0"
|
||||
requires "nim >= 1.6.0", "syndicate", "nimsvg", "pixie", "sdl2", "https://git.sr.ht/~ehmry/svui"
|
||||
|
|
Loading…
Reference in New Issue