Skip to main content

lib/pdf/component/badge.ex

defmodule Pdf.Component.Badge do
  @moduledoc """
  Badge component for PDF documents.

  Renders a small circle or pill-shaped label, typically used to show
  counts, notifications, or status indicators overlaid on another element.

  Inspired by Material UI's Badge component.

  ## Examples

      # Simple notification badge
      doc |> Pdf.Component.Badge.render({120, 710}, %{content: "3"})

      # Custom styled badge
      doc |> Pdf.Component.Badge.render({200, 700}, %{
        content: "NEW",
        background: {0.18, 0.72, 0.45},
        color: :white,
        variant: :pill
      })
  """

  @default_background {0.85, 0.26, 0.33}
  @default_color {1.0, 1.0, 1.0}
  @default_font "Helvetica"
  @default_font_size 8
  @default_size 18

  @doc """
  Render a badge at `{x, y}` (center point).

  ## Style options

  - `:content` — text to display (default `""`)
  - `:background` — fill color (default red)
  - `:color` — text color (default white)
  - `:font` — font name (default `"Helvetica"`)
  - `:font_size` — font size (default `8`)
  - `:size` — diameter for dot/circle variant (default `18`)
  - `:variant` — `:dot` (no text), `:standard` (circle), or `:pill` (auto-width)
  - `:border` — border width (default `0`)
  - `:border_color` — border color (default `:white`)
  """
  def render(doc, {x, y}, style \\ %{}) do
    content = Map.get(style, :content, "")
    bg = Map.get(style, :background, @default_background)
    color = Map.get(style, :color, @default_color)
    font = Map.get(style, :font, @default_font)
    font_size = Map.get(style, :font_size, @default_font_size)
    size = Map.get(style, :size, @default_size)
    variant = Map.get(style, :variant, :standard)
    border_w = Map.get(style, :border, 0)
    border_color = Map.get(style, :border_color, :white)

    case variant do
      :dot -> draw_dot(doc, {x, y}, size / 2, bg, border_w, border_color)
      :pill -> draw_pill(doc, {x, y}, content, bg, color, font, font_size, size, border_w, border_color)
      _standard -> draw_circle(doc, {x, y}, content, bg, color, font, font_size, size, border_w, border_color)
    end
  end

  defp draw_dot(doc, {cx, cy}, radius, bg, border_w, border_color) do
    size = radius * 2
    x = cx - radius
    y = cy - radius

    doc
    |> Pdf.save_state()
    |> Pdf.set_fill_color(bg)
    |> Pdf.rounded_rectangle({x, y}, {size, size}, radius)
    |> Pdf.fill()
    |> maybe_border({x, y}, {size, size}, radius, border_w, border_color)
    |> Pdf.restore_state()
  end

  defp draw_circle(doc, {cx, cy}, content, bg, color, font, font_size, size, border_w, border_color) do
    radius = size / 2
    x = cx - radius
    y = cy - radius

    doc
    |> Pdf.save_state()
    |> Pdf.set_fill_color(bg)
    |> Pdf.rounded_rectangle({x, y}, {size, size}, radius)
    |> Pdf.fill()
    |> maybe_border({x, y}, {size, size}, radius, border_w, border_color)
    |> draw_centered_text({cx, cy}, content, color, font, font_size)
    |> Pdf.restore_state()
  end

  defp draw_pill(doc, {cx, cy}, content, bg, color, font, font_size, height, border_w, border_color) do
    radius = height / 2
    text_width = String.length(content) * font_size * 0.6
    pill_width = max(height, text_width + height * 0.6)
    x = cx - pill_width / 2
    y = cy - radius

    doc
    |> Pdf.save_state()
    |> Pdf.set_fill_color(bg)
    |> Pdf.rounded_rectangle({x, y}, {pill_width, height}, radius)
    |> Pdf.fill()
    |> maybe_border({x, y}, {pill_width, height}, radius, border_w, border_color)
    |> draw_centered_text({cx, cy}, content, color, font, font_size)
    |> Pdf.restore_state()
  end

  # ── Border ─────────────────────────────────────────────────────

  defp maybe_border(doc, _pos, _size, _radius, 0, _color), do: doc

  defp maybe_border(doc, {x, y}, {w, h}, radius, width, color) do
    doc
    |> Pdf.set_stroke_color(color)
    |> Pdf.set_line_width(width)
    |> Pdf.rounded_rectangle({x, y}, {w, h}, radius)
    |> Pdf.stroke()
  end

  # ── Text ───────────────────────────────────────────────────────

  defp draw_centered_text(doc, _center, "", _color, _font, _font_size), do: doc

  defp draw_centered_text(doc, {cx, cy}, text, color, font, font_size) do
    text_w = String.length(text) * font_size * 0.52
    tx = cx - text_w / 2
    ty = cy - font_size * 0.35

    doc
    |> Pdf.set_font(font, font_size)
    |> Pdf.set_fill_color(color)
    |> Pdf.text_at({tx, ty}, text)
  end
end