💾 Archived View for friends.riverside.camp › ~clarity › wiki › layout_lang.gmi captured on 2024-05-26 at 14:58:42. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2021-12-03)

-=-=-=-=-=-=-

Language for Document Layout

I'm thinking about making a lisp-y language for laying out complicated documents like rpg character sheets, so I can get the advantage of programmatic sizing and the ability to make reusable components. This is probably impractically large in scope.

First, take a lispy syntax

(Grid [columns (Repeat [times 2] 1fr)] [gap 24pt]
  (Column
    (Grid [columns 1fr 48pt]
      (Field "Names")
      (Field "Pronouns"))))

And then add semantic whitespace that you can optionally use instead of parentheses.

Grid [columns (Repeat [times 2] 1fr)] [gap 24pt]
  Column
    Grid [columns 1fr 48pt]
      Field "Pronouns"
      FIeld "Names"

Named arguments maybe can also use semantic whitespace

Grid 
  columns: (Repeat [times 2] 1fr)
  gap: 24pt
  Column
    Grid [columns 1fr 48pt]
      Field [name "Names"]
      Field [name "Pronouns"]

S-Expressions

S-expressions use angle brackets. Bare strings inside S-expressions are treated as symbols.

<hello <<name "Clarity">> "world">

The whitespace version of S-expressions use tildes.

~ hello
  ~ ~ name "Clarity
  "world"

It's possible to call functions and reference variables inside of S-expressions:

Eq?
  <number (Add 1 1)>
  <number 2>
Eq?
  (Set 'hello 3)
  <number (hello)>
  <number 3>

Quoting

Quoting a function turns it into a S-expression with the function names replaced with symbols.

Eq?
  Quote (MyFn [name "world"] "hello")
  <MyFn <<name "world">> "hello">
Eq?
  Quote(Hello "world")
  <Hello <> "world">

Rendering

The renderer probably takes s-expressions that represent a subset of the svg spec

~ path
  ~ ~ stroke 30
    ~ color "blue"
  ~ move-abs 10 30
  ~ arc-abs 20 20 0 0 0 50 30
  ~ arc-abs 20 20 0 0 0 90 30
  ~ quad-abs 90 60 50 90
  ~ quad-abs 10 60 10 30
  ~ close-path

# same as...
<path 
  <<stroke 30> <color "blue">>
  <move-abs 10 30>
  <arc-abs 20 20 0 0 0 50 30>
  <arc-abs 20 20 0 0 0 90 30>
  <quad-abs 90 60 50 90>
  <quad-abs 10 60 10 30>
  <close-path>>

And then layout functions like Row go through and modify any absolute positions to reflect their actual position.

Row
  gap: 10pt
  justify: 'space-between
  align: 'center
  max-width: 100pt
  Circle [r 5pt]
  Circle [r 7pt]
Row
  gap: 10pt
  justify: 'space-between
  align: 'center
  max-width: 100pt
  Circle [r 5pt]
  Circle [r 7pt]

<circle <<cx 7> <cy 7> <r 5>>>
<circle <<cx 93> <cy 7> <r 7>>>

Hmm.. probably a "component" has to actually produce two functions, one for sizing and one for rendering.

So a naive circle function might be defined as

Define [r Measurement?] 'Circle
  Pair
    # Given a size, return the min size and max size of this 
    Lambda [w Measurement?] [h Measurement?]
      Pair (* 2 r) (* 2 r)
      Pair (* 2 r) (* 2 r)
    Lambda 
      [x Measurement?] [y Measurement?]
      [w Measurement?] [h Measurement?]
      ~ circle 
        ~ ~ cx (+ x r)
          ~ cy (+ x r)
          ~ r (r)

And... god, okay, the row function might be

Define [gap] [max-width] [_ children]
  
  Pair
    λ [w] [h]
      Define measurements
        children<-Map
          λ [component]
            Call (component->First) w h
      Pair
        +
          Map (λ [m] m->First->First)) measurements
          If (Empty? measurements)
            0
            * (- measurements->Count 1) (Or gap 0)
        measurements<-Map (λ [m] m->First->last)
    λ [x] [y] [w] [h]
      # fuck idk

Syntax Brainstorming

Thinking through other syntax things...

Define Field [^name text]
  Column [pad 8 0 0 0]
    HorizontalLine
    String name
Define Title
  "My Font"
Define Dot
  # Maybe you can include arbitrary svg? or maybe I should define my own vector format???
Define Approach [^name text]
  Repeat [times 4] (Dot)
  String name
Define Attribute [name text] [^appr1 text] [^appr2 text] [^appr3 text]
  Column
    String [size 24pt] [font (Title)] name
    Row
      Grid [rows 4 8 8 8] [cols 8 8 8 8 8 auto] [merge 0 1 0 3]
        Map
          Lambda [value] (String (value))
          "d4" "d6" "d8" "d10" "d12"
        Nothing
        VerticalLine
        Approach appr1
        Approach appr2
        Approach appr3
Grid [columns 1fr 1fr] [gap 24pt]
  Column
    Box
      Right [size 48pt] (Field "Pronouns")
      FIeld "Names"
    Box
      Left [pad 0 8pt 0 0]
        Column
          Attribute [name "Strength"] "Battle" "Kindle" "Scramble"
          Attribute [name "Poise"] "Charm" "Hunt" "Pass"
          Attribute [name "Insight"] "Cobble" "Connect" "Study"
      Grid [columns (Repeat [times 12] 1fr)] [rows 2] [column-gap 8pt]
        Header
          Column
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Written November 30, 2021