{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "watermark",
  "title": "Watermark",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-angular/components/watermark/watermark.component.ts",
      "content": "import {\n  type AfterViewInit,\n  Component,\n  ElementRef,\n  Input,\n  type OnDestroy,\n  booleanAttribute,\n  inject,\n  signal,\n  ChangeDetectionStrategy,\n} from '@angular/core'\nimport { cn } from '@/lib/utils'\n\n/** Build the tiled SVG data URL (same math as React `Watermark`). Empty when there is nothing to draw. */\nexport function buildWatermarkUrl(opts: {\n  width: number\n  height: number\n  content: string\n  image?: string\n  rotate: number\n  gap: number\n  opacity: number\n  fontSize: number\n  color: string\n  fontFamily: string\n  fontWeight: number | string\n}): string {\n  const { width, height, content, image, rotate, gap, opacity, fontSize, color, fontFamily, fontWeight } = opts\n  if (!width || !height) return ''\n  const text = content || ''\n\n  if (image) {\n    const tile = gap + 100\n    const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${tile}\" height=\"${tile}\" viewBox=\"0 0 ${tile} ${tile}\">\n  <image href=\"${image}\" x=\"${gap / 2}\" y=\"${gap / 2}\" width=\"100\" height=\"100\" opacity=\"${opacity}\" transform=\"rotate(${rotate} ${tile / 2} ${tile / 2})\"/>\n</svg>`\n    return `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svg)))}`\n  }\n\n  if (!text) return ''\n\n  const textWidth = text.length * fontSize * 0.6\n  const tileW = gap + textWidth\n  const tileH = gap + fontSize * 1.5\n\n  const escapedText = text\n    .replace(/&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;')\n    .replace(/\"/g, '&quot;')\n    .replace(/'/g, '&#39;')\n\n  const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${tileW}\" height=\"${tileH}\" viewBox=\"0 0 ${tileW} ${tileH}\">\n  <text x=\"${gap / 2}\" y=\"${gap / 2 + fontSize}\" font-size=\"${fontSize}\" font-family=\"${fontFamily}\" font-weight=\"${fontWeight}\" fill=\"${color}\" opacity=\"${opacity}\" transform=\"rotate(${rotate} ${gap / 2} ${gap / 2 + fontSize / 2})\">${escapedText}</text>\n</svg>`\n  return `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svg)))}`\n}\n\n/**\n * Angular port of UIPKGE Watermark (React `Watermark`). Projects its content and lays an\n * aria-hidden overlay on top whose background is a tiled SVG of the text (or image),\n * rotated by `rotate`. The overlay is sized from a ResizeObserver on the host, is\n * pointer-events-none unless `interactive`, and sits at `zIndex`.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-watermark, [ui-watermark]',\n  standalone: true,\n  host: {\n    '[attr.data-slot]': '\"watermark\"',\n    '[attr.data-uipkge]': '\"\"',\n    '[class]': 'hostClass',\n  },\n  template: `\n    <ng-content />\n    <div\n      data-uipkge=\"\"\n      data-slot=\"watermark-overlay\"\n      class=\"absolute inset-0 overflow-hidden\"\n      aria-hidden=\"true\"\n      [style.background-image]=\"url ? 'url(&quot;' + url + '&quot;)' : null\"\n      [style.background-repeat]=\"'repeat'\"\n      [style.z-index]=\"zIndex\"\n      [style.pointer-events]=\"interactive ? 'auto' : 'none'\"\n    ></div>\n  `,\n})\nexport class UiWatermarkComponent implements AfterViewInit, OnDestroy {\n  private readonly el = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement\n  private ro: ResizeObserver | null = null\n  /** Host size (signal so ResizeObserver updates render in zoneless apps). */\n  readonly size = signal({ width: 0, height: 0 })\n\n  /** Text content for the watermark. Ignored when `image` is set. */\n  @Input() content = ''\n  /** Image URL. When set, repeats the image instead of text. */\n  @Input() image?: string\n  @Input() rotate = -22\n  @Input() gap = 100\n  @Input() opacity = 0.08\n  @Input() fontSize = 16\n  @Input() color = 'currentColor'\n  @Input() fontFamily = 'sans-serif'\n  @Input() fontWeight: number | string = 'normal'\n  @Input() zIndex = 9\n  /** When true, the overlay captures pointer events (blocks interaction). */\n  @Input({ transform: booleanAttribute }) interactive = false\n  @Input('class') className?: string\n\n  get hostClass(): string {\n    return cn('block relative', this.className)\n  }\n\n  get url(): string {\n    const { width, height } = this.size()\n    return buildWatermarkUrl({\n      width,\n      height,\n      content: this.content,\n      image: this.image,\n      rotate: this.rotate,\n      gap: this.gap,\n      opacity: this.opacity,\n      fontSize: this.fontSize,\n      color: this.color,\n      fontFamily: this.fontFamily,\n      fontWeight: this.fontWeight,\n    })\n  }\n\n  measure(): void {\n    const rect = this.el.getBoundingClientRect()\n    this.size.set({ width: rect.width, height: rect.height })\n  }\n\n  ngAfterViewInit(): void {\n    this.measure()\n    if (typeof ResizeObserver !== 'undefined') {\n      this.ro = new ResizeObserver(() => this.measure())\n      this.ro.observe(this.el)\n    }\n  }\n\n  ngOnDestroy(): void {\n    this.ro?.disconnect()\n  }\n}\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/watermark/watermark.component.ts"
    },
    {
      "path": "packages/registry-angular/components/watermark/index.ts",
      "content": "export { UiWatermarkComponent, buildWatermarkUrl } from './watermark.component'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/watermark/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Overlay watermark that repeats text or an image across the content area at a configurable angle. Supports rotate angle, gap between repeats, opacity, font size/color/weight, z-index, and pointer-event control. Wrap any content via projection.",
  "categories": [
    "display",
    "utility"
  ]
}