defmodule Pdf.Component.Box do @moduledoc """ Box component for PDF documents. Renders a rectangular container with optional padding, margin, border, border_radius, and background. Operates at Document level. ## Example doc |> Pdf.Component.Box.render({50, 700}, {300, 200}, %{ padding: 10, border: 1, border_radius: 8, background: {0.95, 0.95, 1.0} }, fn doc, area -> Pdf.text_at(doc, {area.x + 5, area.y - 14}, "Inside the box") end) """ alias Pdf.Style @doc """ Render a box at `{x, y}` with size `{w, h}`, applying `style` options, then invoke `callback.(doc, inner_area)`. ## Style options - `:padding` — inner spacing (CSS shorthand: number, `{v, h}`, `{t, r, b, l}`) - `:margin` — outer spacing (CSS shorthand) - `:border` — border width (CSS shorthand) - `:border_color` — border color (default `:black`) - `:border_radius` — corner radius (default `0`) - `:background` — fill color (default `nil`) """ def render(doc, {x, y}, {w, h}, style \\ %{}, callback) when is_function(callback, 2) do style = Style.new(style) {mt, mr, mb, ml} = style.margin {pt, pr, pb, pl} = style.padding {bt, br, bb, bl} = style.border r = style.border_radius outer_x = x + ml outer_y = y - mt outer_w = w - ml - mr outer_h = h - mt - mb # Draw background doc = if style.background do doc |> Pdf.save_state() |> Pdf.set_fill_color(style.background) |> draw_rect({outer_x, outer_y - outer_h}, {outer_w, outer_h}, r) |> Pdf.fill() |> Pdf.restore_state() else doc end # Draw border doc = draw_border(doc, {outer_x, outer_y, outer_w, outer_h}, style.border, style.border_color, r) # Compute inner area inner_x = outer_x + bl + pl inner_y = outer_y - bt - pt inner_w = outer_w - bl - br - pl - pr inner_h = outer_h - bt - bb - pt - pb callback.(doc, %{x: inner_x, y: inner_y, width: inner_w, height: inner_h}) end defp draw_rect(doc, {x, y}, {w, h}, r) when r > 0 do Pdf.rounded_rectangle(doc, {x, y}, {w, h}, r) end defp draw_rect(doc, {x, y}, {w, h}, _r) do Pdf.rectangle(doc, {x, y}, {w, h}) end defp draw_border(doc, {x, y, w, h}, {bt, br, bb, bl}, color, r) do has_border = bt > 0 or br > 0 or bb > 0 or bl > 0 if has_border do # Use uniform border width (max of all sides) for rounded rects border_w = max(max(bt, br), max(bb, bl)) doc |> Pdf.save_state() |> Pdf.set_stroke_color(color) |> Pdf.set_line_width(border_w) |> draw_rect({x, y - h}, {w, h}, r) |> Pdf.stroke() |> Pdf.restore_state() else doc end end end