Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion ltml/SYNTAX.md
Original file line number Diff line number Diff line change
Expand Up @@ -1169,6 +1169,15 @@ not automatically change paragraph shaping or bidi behavior inside text widgets.
- Use `align-self="start"`, `align-self="center"`, or `align-self="end"` to control horizontal placement within the vbox row.
- In `dir="rtl"`, `align-self="start"` means right and `align-self="end"` means left.
- In `dir="rtl"`, children are flush against the right edge plus padding instead of the left.
- When at least one child uses `height="auto"` and a height-constrained vbox
fragment has true surplus height beyond the specified, percent, and preferred
heights of the children on that fragment plus `layout.vpadding`, omitted
heights keep their preferred heights and the `auto` children split the
leftover height evenly.
- In natural-height vboxes, and in constrained vboxes without surplus,
`height="auto"` behaves the same as an omitted height.
- When a vbox splits across pages, `height="auto"` is evaluated separately for
each fragment page based only on the children present on that fragment.

### HBox Details

Expand All @@ -1178,6 +1187,12 @@ not automatically change paragraph shaping or bidi behavior inside text widgets.
- Use `align-self="start"`, `align-self="center"`, or `align-self="end"` to control vertical placement within the hbox track.
- In `hbox`, `align-self="start"` means top and `align-self="end"` means bottom.
- Unaligned children share remaining width equally unless `width` is specified.
- When at least one child uses `width="auto"` and the hbox has true surplus
width beyond the preferred widths of the remaining unsized children plus
hpadding, omitted widths keep their preferred widths and the `auto` children
split the leftover space evenly.
- In constrained hboxes, and in layout managers without their own `auto`
sizing policy, `width="auto"` behaves the same as an omitted width.
- In `dir="rtl"`, stacking order reverses: children flow right to left, `align="left"` pins to the right side, and `align="right"` pins to the left side.

### Table Details
Expand All @@ -1186,7 +1201,25 @@ not automatically change paragraph shaping or bidi behavior inside text widgets.
- Set `rows` for column-major order (`order="cols"`).
- Use `colspan` and `rowspan` attributes on cells to span multiple slots.
- Column widths can be fixed (`width="120pt"`), percentage (`width="40%"`), or
automatic (equal share of remaining space).
omitted. Omitted columns keep the historical table behavior: they share the
remaining width equally.
- When at least one single-column cell uses `width="auto"` and the table has
true surplus width beyond the preferred widths of omitted and auto columns,
omitted columns keep their preferred widths and auto columns split the
remaining width evenly. In constrained tables where omitted preferred widths
can still fit, omitted columns keep those preferred widths and auto columns
split the remaining width. Only when omitted preferred widths cannot fit does
the table fall back to equal sharing.
- Cells with `colspan > 1` receive the resolved width of their spanned columns
but do not drive auto column sizing.
- When at least one row contains a `height="auto"` cell and a height-constrained
table has true surplus height beyond fixed and preferred row heights, omitted
rows keep their preferred heights and auto rows split the remaining height
evenly.
- In natural-height tables, and in constrained tables without surplus,
`height="auto"` behaves the same as an omitted height.
- When a direct page-child table splits across pages, auto row height is
evaluated separately for each fragment page.
- In `dir="rtl"`, columns are placed right to left (column 0 at the right edge).

### Flow Details
Expand Down Expand Up @@ -1353,6 +1386,7 @@ Measurements can be expressed in several forms:
| With unit | `1in`, `2.5cm`, `14pt` | Explicit unit overrides the current `units`. |
| Percentage | `50%` | Percentage of the container's content width or height. |
| Relative | `+10`, `-5` | Offset from the container's content dimension. |
| Auto | `auto` | Automatic layout-managed size. Supported by `hbox` width, `vbox` height, table column width, and table row height; elsewhere it behaves like omitting the dimension. |

**Supported units:** `pt` (points), `in` (inches, 72pt), `cm` (centimeters, 28.35pt).

Expand Down
2 changes: 2 additions & 0 deletions ltml/canvas_capture.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ func resetCanvasWidgetRenderState(root Widget) {
widget.SetPrinted(false)
widget.SetVisible(true)
widget.SetDisabled(false)
widget.ClearResolvedWidth()
widget.ClearResolvedHeight()
switch value := widget.(type) {
case *StdContainer:
value.activeChildren = nil
Expand Down
203 changes: 179 additions & 24 deletions ltml/dimensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,44 @@ import (
"fmt"
"regexp"
"strconv"
"strings"
)

type DimensionMode int8

const (
DimUnspecified DimensionMode = iota
DimLiteral
DimPct
DimRel
DimAuto
)

type Dimensions struct {
sides Sides
margin Sides
padding Sides
corners Corners
width float32
widthPct float32
widthRel float32
height float32
heightPct float32
heightRel float32
widthSet bool
heightSet bool
sides Sides
margin Sides
padding Sides
corners Corners
width float32
height float32
widthValue float32
heightValue float32
widthMode DimensionMode
heightMode DimensionMode
widthValid bool
heightValid bool
}

type dimensionState struct {
resolved float32
value float32
mode DimensionMode
valid bool
}

type dimensionsState struct {
width dimensionState
height dimensionState
}

var (
Expand Down Expand Up @@ -83,7 +106,10 @@ func (d *Dimensions) SetAttrs(attrs map[string]string, units Units) {
}

if width, ok := attrs["width"]; ok {
if rePct.MatchString(width) {
width = strings.TrimSpace(width)
if width == "auto" {
d.SetWidthAuto()
} else if rePct.MatchString(width) {
widthPct, _ := strconv.ParseFloat(width[:len(width)-1], 64)
d.SetWidthPct(widthPct)
} else if reRel.MatchString(width) {
Expand All @@ -95,7 +121,10 @@ func (d *Dimensions) SetAttrs(attrs map[string]string, units Units) {
}
}
if height, ok := attrs["height"]; ok {
if rePct.MatchString(height) {
height = strings.TrimSpace(height)
if height == "auto" {
d.SetHeightAuto()
} else if rePct.MatchString(height) {
heightPct, _ := strconv.ParseFloat(height[:len(height)-1], 64)
d.SetHeightPct(heightPct)
} else if reRel.MatchString(height) {
Expand All @@ -109,19 +138,64 @@ func (d *Dimensions) SetAttrs(attrs map[string]string, units Units) {
}

func (d *Dimensions) SetHeight(value float64) {
d.height, d.heightPct, d.heightRel, d.heightSet = float32(value), 0, 0, true
d.height = float32(value)
d.heightValue = float32(value)
d.heightMode = DimLiteral
d.heightValid = true
}

func (d *Dimensions) SetHeightAuto() {
d.height = 0
d.heightValue = 0
d.heightMode = DimAuto
d.heightValid = false
}

func (d *Dimensions) ClearHeight() {
d.height = 0
d.heightValue = 0
d.heightMode = DimUnspecified
d.heightValid = false
}

func (d *Dimensions) SetHeightPct(value float64) {
d.heightPct, d.height, d.heightRel, d.heightSet = float32(value), 0, 0, true
d.height = 0
d.heightValue = float32(value)
d.heightMode = DimPct
d.heightValid = false
}

func (d *Dimensions) SetHeightRel(value float64) {
d.heightRel, d.height, d.heightPct, d.heightSet = float32(value), 0, 0, true
d.height = 0
d.heightValue = float32(value)
d.heightMode = DimRel
d.heightValid = false
}

func (d *Dimensions) ResolveHeight(value float64) {
d.height = float32(value)
d.heightValid = true
}

func (d *Dimensions) ClearResolvedHeight() {
if d.heightMode == DimLiteral {
d.height = d.heightValue
} else {
d.height = 0
}
d.heightValid = false
}

func (d *Dimensions) HeightIsSet() bool {
return d.heightSet
if d.heightValid {
return true
}
switch d.heightMode {
case DimLiteral, DimPct, DimRel:
return true
default:
return false
}
}

func (d *Dimensions) SetTop(value float64) {
Expand All @@ -141,15 +215,52 @@ func (d *Dimensions) SetLeft(value float64) {
}

func (d *Dimensions) SetWidth(value float64) {
d.width, d.widthPct, d.widthRel, d.widthSet = float32(value), 0, 0, true
d.width = float32(value)
d.widthValue = float32(value)
d.widthMode = DimLiteral
d.widthValid = true
}

func (d *Dimensions) SetWidthAuto() {
d.width = 0
d.widthValue = 0
d.widthMode = DimAuto
d.widthValid = false
}

func (d *Dimensions) ClearWidth() {
d.width = 0
d.widthValue = 0
d.widthMode = DimUnspecified
d.widthValid = false
}

func (d *Dimensions) SetWidthPct(value float64) {
d.widthPct, d.widthRel, d.width, d.widthSet = float32(value), 0, 0, true
d.width = 0
d.widthValue = float32(value)
d.widthMode = DimPct
d.widthValid = false
}

func (d *Dimensions) SetWidthRel(value float64) {
d.widthRel, d.widthPct, d.width, d.widthSet = float32(value), 0, 0, true
d.width = 0
d.widthValue = float32(value)
d.widthMode = DimRel
d.widthValid = false
}

func (d *Dimensions) ResolveWidth(value float64) {
d.width = float32(value)
d.widthValid = true
}

func (d *Dimensions) ClearResolvedWidth() {
if d.widthMode == DimLiteral {
d.width = d.widthValue
} else {
d.width = 0
}
d.widthValid = false
}

func (d *Dimensions) String() string {
Expand All @@ -158,13 +269,57 @@ func (d *Dimensions) String() string {
}

func (d *Dimensions) WidthPctIsSet() bool {
return d.widthPct > 0
return d.widthMode == DimPct
}

func (d *Dimensions) WidthRelIsSet() bool {
return d.widthRel != 0
return d.widthMode == DimRel
}

func (d *Dimensions) WidthIsSet() bool {
return d.widthSet
if d.widthValid {
return true
}
switch d.widthMode {
case DimLiteral, DimPct, DimRel:
return true
default:
return false
}
}

func (d *Dimensions) WidthMode() DimensionMode {
return d.widthMode
}

func (d *Dimensions) HeightMode() DimensionMode {
return d.heightMode
}

func (d *Dimensions) SaveState() dimensionsState {
return dimensionsState{
width: dimensionState{
resolved: d.width,
value: d.widthValue,
mode: d.widthMode,
valid: d.widthValid,
},
height: dimensionState{
resolved: d.height,
value: d.heightValue,
mode: d.heightMode,
valid: d.heightValid,
},
}
}

func (d *Dimensions) RestoreState(state dimensionsState) {
d.width = state.width.resolved
d.widthValue = state.width.value
d.widthMode = state.width.mode
d.widthValid = state.width.valid
d.height = state.height.resolved
d.heightValue = state.height.value
d.heightMode = state.height.mode
d.heightValid = state.height.valid
}
Loading
Loading