import css/cssvalues
import css/lunit
import html/dom
import types/bitmap
import types/refstring
type
DimensionType* = enum
dtHorizontal, dtVertical
Offset* = array[DimensionType, LUnit]
Size* = array[DimensionType, LUnit]
InlineImageState* = object
offset*: Offset
size*: Size
TextRun* = ref object
offset*: Offset
str*: string
BoxLayoutState* = object
# offset relative to parent
offset*: Offset
# padding size
size*: Size
# intrinsic minimum size (e.g. longest word)
intr*: Size
# baseline of the first line box of all descendants
firstBaseline*: LUnit
# baseline of the last line box of all descendants
baseline*: LUnit
# Bottom margin of the box, collapsed with the margin of children.
# This is already added to size, and only used by flex layout.
marginBottom*: LUnit
Area* = object
offset*: Offset
size*: Size
InlineBoxState* = object
startOffset*: Offset # offset of the first word, for position: absolute
areas*: seq[Area] # background that should be painted by box
Span* = object
start*: LUnit
send*: LUnit
RelativeRect* = array[DimensionType, Span]
StackItem* = ref object
box*: CSSBox
index*: int32
children*: seq[StackItem]
ClipBox* = object
start*: Offset
send*: Offset
BoxRenderState* = object
# Whether the following two variables have been initialized.
#TODO find a better name that doesn't conflict with box.positioned
positioned*: bool
offset*: Offset
clipBox*: ClipBox
# min-content: box width is longest word's width
# max-content: box width is content width without wrapping
# stretch: box width is n px wide
# fit-content: also known as shrink-to-fit, box width is
# min(max-content, stretch(availableWidth))
# in other words, as wide as needed, but wrap if wider than allowed
# (note: I write width here, but it can apply for any constraint)
SizeConstraintType* = enum
scStretch, scFitContent, scMinContent, scMaxContent
SizeConstraint* = object
t*: SizeConstraintType
u*: LUnit
AvailableSpace* = array[DimensionType, SizeConstraint]
Bounds* = object
a*: array[DimensionType, Span] # width clamp
mi*: array[DimensionType, Span] # intrinsic clamp
ResolvedSizes* = object
margin*: RelativeRect
padding*: RelativeRect
space*: AvailableSpace
bounds*: Bounds
CSSBox* = ref object of RootObj
parent* {.cursor.}: CSSBox
firstChild*: CSSBox
next*: CSSBox
positioned*: bool # set if we participate in positioned layout
render*: BoxRenderState # render output
computed*: CSSValues
element*: Element
BlockBox* = ref object of CSSBox
sizes*: ResolvedSizes # tree builder output -> layout input
state*: BoxLayoutState # layout output -> render input
InlineBox* = ref object of CSSBox
state*: InlineBoxState
InlineTextBox* = ref object of InlineBox
runs*: seq[TextRun] # state
text*: RefString
InlineNewLineBox* = ref object of InlineBox
InlineImageBox* = ref object of InlineBox
imgstate*: InlineImageState
bmp*: NetworkBitmap
InlineBlockBox* = ref object of InlineBox
# InlineBlockBox always has one block child.
LayoutResult* = ref object
stack*: StackItem
func offset*(x, y: LUnit): Offset =
return [dtHorizontal: x, dtVertical: y]
func x*(offset: Offset): LUnit {.inline.} =
return offset[dtHorizontal]
func x*(offset: var Offset): var LUnit {.inline.} =
return offset[dtHorizontal]
func `x=`*(offset: var Offset; x: LUnit) {.inline.} =
offset[dtHorizontal] = x
func y*(offset: Offset): LUnit {.inline.} =
return offset[dtVertical]
func y*(offset: var Offset): var LUnit {.inline.} =
return offset[dtVertical]
func `y=`*(offset: var Offset; y: LUnit) {.inline.} =
offset[dtVertical] = y
func size*(w, h: LUnit): Size =
return [dtHorizontal: w, dtVertical: h]
func w*(size: Size): LUnit {.inline.} =
return size[dtHorizontal]
func w*(size: var Size): var LUnit {.inline.} =
return size[dtHorizontal]
func `w=`*(size: var Size; w: LUnit) {.inline.} =
size[dtHorizontal] = w
func h*(size: Size): LUnit {.inline.} =
return size[dtVertical]
func h*(size: var Size): var LUnit {.inline.} =
return size[dtVertical]
func `h=`*(size: var Size; h: LUnit) {.inline.} =
size[dtVertical] = h
func `+`*(a, b: Offset): Offset =
return offset(x = a.x + b.x, y = a.y + b.y)
func `-`*(a, b: Offset): Offset =
return offset(x = a.x - b.x, y = a.y - b.y)
proc `+=`*(a: var Offset; b: Offset) =
a.x += b.x
a.y += b.y
proc `-=`*(a: var Offset; b: Offset) =
a.x -= b.x
a.y -= b.y
func left*(s: RelativeRect): LUnit =
return s[dtHorizontal].start
func right*(s: RelativeRect): LUnit =
return s[dtHorizontal].send
func top*(s: RelativeRect): LUnit =
return s[dtVertical].start
func bottom*(s: RelativeRect): LUnit =
return s[dtVertical].send
func topLeft*(s: RelativeRect): Offset =
return offset(x = s.left, y = s.top)
proc `+=`*(span: var Span; u: LUnit) =
span.start += u
span.send += u
func `<`*(a, b: Offset): bool =
return a.x < b.x and a.y < b.y
iterator children*(box: CSSBox): CSSBox =
var it {.cursor.} = box.firstChild
while it != nil:
yield it
it = it.next
proc resetState(box: CSSBox) =
box.render = BoxRenderState()
proc resetState*(ibox: InlineBox) =
CSSBox(ibox).resetState()
ibox.state = InlineBoxState()
proc resetState*(box: BlockBox) =
CSSBox(box).resetState()
box.state = BoxLayoutState()
const DefaultClipBox* = ClipBox(send: offset(LUnit.high, LUnit.high))
when defined(debug):
import chame/tags
proc `$`*(box: CSSBox; pass2 = true): string =
if box.positioned and not pass2:
return ""
result = "<"
let name = if box.computed{"display"} != DisplayInline:
if box.element.tagType in {TAG_HTML, TAG_BODY}:
$box.element.tagType
else:
"div"
elif box of InlineNewLineBox:
"br"
else:
"span"
result &= name
let computed = box.computed.copyProperties()
if computed{"display"} == DisplayBlock:
computed{"display"} = DisplayInline
var style = $computed.serializeEmpty()
if style != "":
if style[^1] == ';':
style.setLen(style.high)
result &= " style='" & style & "'"
result &= ">"
if box of InlineNewLineBox:
return
if box of BlockBox:
result &= '\n'
for it in box.children:
result &= `$`(it, pass2 = false)
if box of InlineTextBox:
for run in InlineTextBox(box).runs:
result &= run.str
if box of BlockBox:
result &= '\n'
result &= "" & name & ">"
proc `$`*(stack: StackItem): string =
result = "\n"
result &= `$`(stack.box, pass2 = true)
result &= "\n"
for child in stack.children:
result &= "\n"
result &= $child
result &= "\n"
result &= "\n"