Refactor style logic
Some checks failed
test / test (push) Has been cancelled

This commit is contained in:
Lily Rose 2025-07-28 18:45:10 +10:00
parent 71ffecc2d5
commit ff704930c8
7 changed files with 308 additions and 355 deletions

View file

@ -9,10 +9,11 @@ import lustre/attribute.{style} as _attribute
import lustre/element.{type Element, fragment, to_readable_string} as _element
import lustre/element/html.{svg}
import simplifile
import svg_attribute as attribute
import svg_element as element
import svg_path as path
import svg_transform as transform
import svg/attribute
import svg/element
import svg/path
import svg/styles
import svg/transform
pub fn get_footprint(
library_location: String,
@ -122,71 +123,31 @@ pub fn main() -> Nil {
const bg_col = "#001021"
fn create_svg(footprint: Footprint) -> String {
let fill = None
let stroke = None
let stroke_width = None
let styles = styles.new()
let transform = None
svg([attribute.view_box(-4.0, -4.0, 8.0, 8.0), style("background", bg_col)], [
element.group(
fill:,
stroke: Some("#BBBBBB"),
stroke_width: Some(0.01),
styles: styles
|> styles.with_stroke("#BBBBBB")
|> styles.with_stroke_width(0.01),
transform:,
children: [
element.line(
fill:,
stroke:,
stroke_width:,
transform:,
x1: -4.0,
y1: 0.0,
x2: 4.0,
y2: 0.0,
),
element.line(
fill:,
stroke:,
stroke_width:,
transform:,
x1: 0.0,
y1: -4.0,
x2: 0.0,
y2: 4.0,
),
element.line(styles:, transform:, x1: -4.0, y1: 0.0, x2: 4.0, y2: 0.0),
element.line(styles:, transform:, x1: 0.0, y1: -4.0, x2: 0.0, y2: 4.0),
],
),
element.group(
fill:,
stroke: Some("#FF00FF"),
stroke_width: Some(0.01),
styles: styles
|> styles.with_stroke("#FF00FF")
|> styles.with_stroke_width(0.01),
transform:,
children: [
element.line(
fill:,
stroke:,
stroke_width:,
transform:,
x1: -0.05,
y1: 0.0,
x2: 0.05,
y2: 0.0,
),
element.line(
fill:,
stroke:,
stroke_width:,
transform:,
x1: 0.0,
y1: -0.05,
x2: 0.0,
y2: 0.05,
),
element.line(styles:, transform:, x1: -0.05, y1: 0.0, x2: 0.05, y2: 0.0),
element.line(styles:, transform:, x1: 0.0, y1: -0.05, x2: 0.0, y2: 0.05),
],
),
element.group(
fill: Some("#CE3431"),
stroke:,
stroke_width:,
styles: styles |> styles.with_fill("#CE3431"),
transform:,
children: footprint.pads
|> list.filter(fn(pad) {
@ -206,9 +167,7 @@ fn pad_to_element(pad: Pad) -> Element(a) {
let nh = -1.0 *. h
let hw = w /. 2.0
let hh = h /. 2.0
let fill = None
let stroke = None
let stroke_width = None
let styles = styles.new()
let transform =
angle
|> option.map(fn(angle) {
@ -227,29 +186,12 @@ fn pad_to_element(pad: Pad) -> Element(a) {
}
let elem = case pad.shape {
token.CirclePadShape ->
element.circle(
fill:,
stroke:,
stroke_width:,
transform:,
cx: ox,
cy: oy,
r: hw,
)
element.circle(styles:, transform:, cx: ox, cy: oy, r: hw)
token.OvalPadShape ->
case float.compare(w, h) {
order.Eq ->
element.circle(
fill:,
stroke:,
stroke_width:,
transform:,
cx: ox,
cy: oy,
r: hw,
)
order.Eq -> element.circle(styles:, transform:, cx: ox, cy: oy, r: hw)
order.Lt ->
element.path(fill:, stroke:, stroke_width:, transform:, path: [
element.path(styles:, transform:, path: [
path.MoveAbsolute(x: ox +. hw, y: oy +. hw -. hh),
path.ArcCurveRelative(
rx: hw,
@ -273,7 +215,7 @@ fn pad_to_element(pad: Pad) -> Element(a) {
path.ClosePath,
])
order.Gt ->
element.path(fill:, stroke:, stroke_width:, transform:, path: [
element.path(styles:, transform:, path: [
path.MoveAbsolute(x: ox +. hh -. hw, y: oy -. hh),
path.ArcCurveRelative(
rx: hh,
@ -317,9 +259,7 @@ fn pad_to_element(pad: Pad) -> Element(a) {
case rounding, pad.chamfer, chamfering {
None, Some([]), _ | None, None, _ | None, _, None ->
element.centered_rect(
fill:,
stroke:,
stroke_width:,
styles:,
transform:,
x: ox,
y: oy,
@ -332,9 +272,7 @@ fn pad_to_element(pad: Pad) -> Element(a) {
-> {
let radius = rounding *. float.min(w, h)
element.centered_rounded_rect(
fill:,
stroke:,
stroke_width:,
styles:,
transform:,
x: ox,
y: oy,
@ -393,7 +331,7 @@ fn pad_to_element(pad: Pad) -> Element(a) {
]
False -> [path.MoveAbsolute(x: ox -. hw, y: oy -. hh), ..path]
}
element.path(fill:, stroke:, stroke_width:, transform:, path:)
element.path(styles:, transform:, path:)
}
Some(rounding), Some(corners), Some(chamfering) -> {
let #(tl, tr, bl, br) = chamfered_corners(corners)
@ -494,7 +432,7 @@ fn pad_to_element(pad: Pad) -> Element(a) {
..path
]
}
element.path(fill:, stroke:, stroke_width:, transform:, path:)
element.path(styles:, transform:, path:)
}
}
}
@ -510,7 +448,7 @@ fn pad_to_element(pad: Pad) -> Element(a) {
path.LineRelative(dx: no, dy: nh),
path.ClosePath,
]
element.path(fill:, stroke:, stroke_width:, transform:, path:)
element.path(styles:, transform:, path:)
}
Some(token.XY(x: o, y: 0.0)) -> {
let no = -1.0 *. o
@ -522,13 +460,11 @@ fn pad_to_element(pad: Pad) -> Element(a) {
path.LineRelative(dx: nw, dy: no),
path.ClosePath,
]
element.path(fill:, stroke:, stroke_width:, transform:, path:)
element.path(styles:, transform:, path:)
}
_ ->
element.centered_rect(
fill:,
stroke:,
stroke_width:,
styles:,
transform:,
x: ox,
y: oy,
@ -539,21 +475,11 @@ fn pad_to_element(pad: Pad) -> Element(a) {
token.CustomPadShape ->
case pad.custom_options {
Some(token.CustomPadOptions(anchor: token.CircleAnchorPadShape, ..)) ->
element.circle(
fill:,
stroke:,
stroke_width:,
transform:,
cx: ox,
cy: oy,
r: hw,
)
element.circle(styles:, transform:, cx: ox, cy: oy, r: hw)
Some(token.CustomPadOptions(anchor: token.RectangleAnchorPadShape, ..))
| None ->
element.centered_rect(
fill:,
stroke:,
stroke_width:,
styles:,
transform:,
x: ox,
y: oy,
@ -562,25 +488,22 @@ fn pad_to_element(pad: Pad) -> Element(a) {
)
}
}
let #(fill, stroke, stroke_width) = case pad.type_ {
token.ThroughHolePadType -> #(Some(bg_col), Some("#E7B629"), Some(0.03333))
token.NpThroughHolePadType -> #(Some("#18C7D6"), None, None)
_ -> #(None, None, None)
let styles = case pad.type_ {
token.ThroughHolePadType ->
styles.new()
|> styles.with_fill(bg_col)
|> styles.with_stroke("#E7B629")
|> styles.with_stroke_width(0.06666)
|> styles.with_paint_order([styles.StrokeComponent])
token.NpThroughHolePadType -> styles.new() |> styles.with_fill("#18C7D6")
_ -> styles
}
case pad.drill {
Some(token.PadDrillDefinition(False, Some(d), ..))
| Some(token.PadDrillDefinition(True, Some(d), None, ..)) ->
fragment([
elem,
element.circle(
fill:,
stroke:,
stroke_width:,
transform:,
cx: x,
cy: y,
r: d /. 2.0,
),
element.circle(styles:, transform:, cx: x, cy: y, r: d /. 2.0),
])
Some(token.PadDrillDefinition(True, Some(w), Some(h), ..)) -> {
let hw = w /. 2.0
@ -590,18 +513,9 @@ fn pad_to_element(pad: Pad) -> Element(a) {
fragment([
elem,
case float.compare(w, h) {
order.Eq ->
element.circle(
fill:,
stroke:,
stroke_width:,
transform:,
cx: x,
cy: y,
r: hw,
)
order.Eq -> element.circle(styles:, transform:, cx: x, cy: y, r: hw)
order.Lt ->
element.path(fill:, stroke:, stroke_width:, transform:, path: [
element.path(styles:, transform:, path: [
path.MoveAbsolute(x: x +. hw, y: y +. hw -. hh),
path.ArcCurveRelative(
rx: hw,
@ -625,7 +539,7 @@ fn pad_to_element(pad: Pad) -> Element(a) {
path.ClosePath,
])
order.Gt ->
element.path(fill:, stroke:, stroke_width:, transform:, path: [
element.path(styles:, transform:, path: [
path.MoveAbsolute(x: x +. hh -. hw, y: y -. hh),
path.ArcCurveRelative(
rx: hh,

View file

@ -2,8 +2,9 @@ import gleam/float
import gleam/list
import gleam/string
import lustre/attribute.{type Attribute, attribute}
import svg_path.{type Path}
import svg_transform.{type Transform}
import svg/path.{type Path}
import svg/styles.{type PaintOrder}
import svg/transform.{type Transform}
pub fn view_box(
min_x: Float,
@ -35,6 +36,10 @@ pub fn stroke_width(value: Float) -> Attribute(a) {
float_attribute("stroke-width", value)
}
pub fn paint_order(value: PaintOrder) -> Attribute(a) {
attribute("paint-order", styles.paint_order_to_string(value))
}
pub fn width(value: Float) -> Attribute(a) {
float_attribute("width", value)
}
@ -96,9 +101,9 @@ pub fn r(value: Float) -> Attribute(a) {
}
pub fn d(value: Path) -> Attribute(a) {
attribute("d", svg_path.to_string(value))
attribute("d", path.to_string(value))
}
pub fn transform(value: Transform) -> Attribute(a) {
attribute("transform", svg_transform.to_string(value))
attribute("transform", transform.to_string(value))
}

202
src/svg/element.gleam Normal file
View file

@ -0,0 +1,202 @@
import gleam/option.{type Option, None, Some}
import lustre/attribute.{type Attribute} as _attribute
import lustre/element.{type Element, element}
import svg/attribute
import svg/path.{type Path}
import svg/styles.{type Styles}
import svg/transform.{type Transform}
fn styles_transform(
styles styles: Styles,
transform transform: Option(Transform),
) -> List(Attribute(a)) {
let attrs = []
let attrs = case transform {
Some(value) -> [attribute.transform(value), ..attrs]
None -> attrs
}
let attrs = case styles.paint_order {
Some(value) -> [attribute.paint_order(value), ..attrs]
None -> attrs
}
let attrs = case styles.stroke_width {
Some(value) -> [attribute.stroke_width(value), ..attrs]
None -> attrs
}
let attrs = case styles.stroke {
Some(value) -> [attribute.stroke(value), ..attrs]
None -> attrs
}
let attrs = case styles.fill {
Some(value) -> [attribute.fill(value), ..attrs]
None -> attrs
}
attrs
}
pub fn group(
styles styles: Styles,
transform transform: Option(Transform),
children children: List(Element(a)),
) -> Element(a) {
element("g", styles_transform(styles:, transform:), children)
}
pub fn line(
styles styles: Styles,
transform transform: Option(Transform),
x1 x1: Float,
y1 y1: Float,
x2 x2: Float,
y2 y2: Float,
) -> Element(a) {
element(
"line",
[
attribute.x1(x1),
attribute.y1(y1),
attribute.x2(x2),
attribute.y2(y2),
..styles_transform(styles:, transform:)
],
[],
)
}
pub fn circle(
styles styles: Styles,
transform transform: Option(Transform),
cx cx: Float,
cy cy: Float,
r r: Float,
) -> Element(a) {
element(
"circle",
[
attribute.cx(cx),
attribute.cy(cy),
attribute.r(r),
..styles_transform(styles:, transform:)
],
[],
)
}
pub fn ellipse(
styles styles: Styles,
transform transform: Option(Transform),
cx cx: Float,
cy cy: Float,
rx rx: Float,
ry ry: Float,
) -> Element(a) {
element(
"ellipse",
[
attribute.cx(cx),
attribute.cy(cy),
attribute.rx(rx),
attribute.ry(ry),
..styles_transform(styles:, transform:)
],
[],
)
}
pub fn rect(
styles styles: Styles,
transform transform: Option(Transform),
x x: Float,
y y: Float,
width width: Float,
height height: Float,
) -> Element(a) {
element(
"rect",
[
attribute.x(x),
attribute.y(y),
attribute.width(width),
attribute.height(height),
..styles_transform(styles:, transform:)
],
[],
)
}
pub fn rounded_rect(
styles styles: Styles,
transform transform: Option(Transform),
x x: Float,
y y: Float,
rx rx: Float,
ry ry: Float,
width width: Float,
height height: Float,
) -> Element(a) {
element(
"rect",
[
attribute.x(x),
attribute.y(y),
attribute.rx(rx),
attribute.ry(ry),
attribute.width(width),
attribute.height(height),
..styles_transform(styles:, transform:)
],
[],
)
}
pub fn centered_rect(
styles styles: Styles,
transform transform: Option(Transform),
x x: Float,
y y: Float,
width width: Float,
height height: Float,
) -> Element(a) {
rect(
styles:,
transform:,
x: x -. width /. 2.0,
y: y -. height /. 2.0,
width:,
height:,
)
}
pub fn centered_rounded_rect(
styles styles: Styles,
transform transform: Option(Transform),
x x: Float,
y y: Float,
rx rx: Float,
ry ry: Float,
width width: Float,
height height: Float,
) -> Element(a) {
rounded_rect(
styles:,
transform:,
x: x -. width /. 2.0,
y: y -. height /. 2.0,
rx:,
ry:,
width:,
height:,
)
}
pub fn path(
styles styles: Styles,
transform transform: Option(Transform),
path path: Path,
) -> Element(a) {
element(
"path",
[attribute.d(path), ..styles_transform(styles:, transform:)],
[],
)
}

53
src/svg/styles.gleam Normal file
View file

@ -0,0 +1,53 @@
import gleam/list
import gleam/option.{type Option, None, Some}
import gleam/string
pub type PaintComponent {
FillComponent
StrokeComponent
MarkerComponent
}
pub type PaintOrder =
List(PaintComponent)
pub fn paint_order_to_string(value: PaintOrder) -> String {
value
|> list.map(fn(comp) {
case comp {
FillComponent -> "fill"
StrokeComponent -> "stroke"
MarkerComponent -> "marker"
}
})
|> string.join(" ")
}
pub type Styles {
Styles(
fill: Option(String),
stroke: Option(String),
stroke_width: Option(Float),
paint_order: Option(List(PaintComponent)),
)
}
pub fn new() {
Styles(fill: None, stroke: None, stroke_width: None, paint_order: None)
}
pub fn with_fill(styles: Styles, fill: String) {
Styles(..styles, fill: Some(fill))
}
pub fn with_stroke(styles: Styles, stroke: String) {
Styles(..styles, stroke: Some(stroke))
}
pub fn with_stroke_width(styles: Styles, stroke_width: Float) {
Styles(..styles, stroke_width: Some(stroke_width))
}
pub fn with_paint_order(styles: Styles, paint_order: PaintOrder) {
Styles(..styles, paint_order: Some(paint_order))
}

View file

@ -1,221 +0,0 @@
import gleam/option.{type Option, None, Some}
import lustre/attribute.{type Attribute}
import lustre/element.{type Element, element}
import svg_attribute
import svg_path.{type Path}
import svg_transform.{type Transform}
fn styles(
fill fill: Option(String),
stroke stroke: Option(String),
stroke_width stroke_width: Option(Float),
transform transform: Option(Transform),
) -> List(Attribute(a)) {
let attrs = []
let attrs = case transform {
Some(value) -> [svg_attribute.transform(value), ..attrs]
None -> attrs
}
let attrs = case stroke_width {
Some(value) -> [svg_attribute.stroke_width(value), ..attrs]
None -> attrs
}
let attrs = case stroke {
Some(value) -> [svg_attribute.stroke(value), ..attrs]
None -> attrs
}
let attrs = case fill {
Some(value) -> [svg_attribute.fill(value), ..attrs]
None -> attrs
}
attrs
}
pub fn group(
fill fill: Option(String),
stroke stroke: Option(String),
stroke_width stroke_width: Option(Float),
transform transform: Option(Transform),
children children: List(Element(a)),
) -> Element(a) {
element("g", styles(fill:, stroke:, stroke_width:, transform:), children)
}
pub fn line(
fill fill: Option(String),
stroke stroke: Option(String),
stroke_width stroke_width: Option(Float),
transform transform: Option(Transform),
x1 x1: Float,
y1 y1: Float,
x2 x2: Float,
y2 y2: Float,
) -> Element(a) {
element(
"line",
[
svg_attribute.x1(x1),
svg_attribute.y1(y1),
svg_attribute.x2(x2),
svg_attribute.y2(y2),
..styles(fill:, stroke:, stroke_width:, transform:)
],
[],
)
}
pub fn circle(
fill fill: Option(String),
stroke stroke: Option(String),
stroke_width stroke_width: Option(Float),
transform transform: Option(Transform),
cx cx: Float,
cy cy: Float,
r r: Float,
) -> Element(a) {
element(
"circle",
[
svg_attribute.cx(cx),
svg_attribute.cy(cy),
svg_attribute.r(r),
..styles(fill:, stroke:, stroke_width:, transform:)
],
[],
)
}
pub fn ellipse(
fill fill: Option(String),
stroke stroke: Option(String),
stroke_width stroke_width: Option(Float),
transform transform: Option(Transform),
cx cx: Float,
cy cy: Float,
rx rx: Float,
ry ry: Float,
) -> Element(a) {
element(
"ellipse",
[
svg_attribute.cx(cx),
svg_attribute.cy(cy),
svg_attribute.rx(rx),
svg_attribute.ry(ry),
..styles(fill:, stroke:, stroke_width:, transform:)
],
[],
)
}
pub fn rect(
fill fill: Option(String),
stroke stroke: Option(String),
stroke_width stroke_width: Option(Float),
transform transform: Option(Transform),
x x: Float,
y y: Float,
width width: Float,
height height: Float,
) -> Element(a) {
element(
"rect",
[
svg_attribute.x(x),
svg_attribute.y(y),
svg_attribute.width(width),
svg_attribute.height(height),
..styles(fill:, stroke:, stroke_width:, transform:)
],
[],
)
}
pub fn rounded_rect(
fill fill: Option(String),
stroke stroke: Option(String),
stroke_width stroke_width: Option(Float),
transform transform: Option(Transform),
x x: Float,
y y: Float,
rx rx: Float,
ry ry: Float,
width width: Float,
height height: Float,
) -> Element(a) {
element(
"rect",
[
svg_attribute.x(x),
svg_attribute.y(y),
svg_attribute.rx(rx),
svg_attribute.ry(ry),
svg_attribute.width(width),
svg_attribute.height(height),
..styles(fill:, stroke:, stroke_width:, transform:)
],
[],
)
}
pub fn centered_rect(
fill fill: Option(String),
stroke stroke: Option(String),
stroke_width stroke_width: Option(Float),
transform transform: Option(Transform),
x x: Float,
y y: Float,
width width: Float,
height height: Float,
) -> Element(a) {
rect(
fill:,
stroke:,
stroke_width:,
transform:,
x: x -. width /. 2.0,
y: y -. height /. 2.0,
width:,
height:,
)
}
pub fn centered_rounded_rect(
fill fill: Option(String),
stroke stroke: Option(String),
stroke_width stroke_width: Option(Float),
transform transform: Option(Transform),
x x: Float,
y y: Float,
rx rx: Float,
ry ry: Float,
width width: Float,
height height: Float,
) -> Element(a) {
rounded_rect(
fill:,
stroke:,
stroke_width:,
transform:,
x: x -. width /. 2.0,
y: y -. height /. 2.0,
rx:,
ry:,
width:,
height:,
)
}
pub fn path(
fill fill: Option(String),
stroke stroke: Option(String),
stroke_width stroke_width: Option(Float),
transform transform: Option(Transform),
path path: Path,
) -> Element(a) {
element(
"path",
[svg_attribute.d(path), ..styles(fill:, stroke:, stroke_width:, transform:)],
[],
)
}