(function attachPyroSvgImport(root) {
  'use strict';

  const SvgPath = root.PyroSvgPath || (typeof require === 'function' ? require('./svg-path.js') : null);
  if (!SvgPath) throw new Error('PyroSvgPath fehlt');
  const Security = root.PyroSecurityPolicy || (typeof require === 'function' ? require('../security/policy.js') : null);
  const translated = (key, fallback, variables = {}) => root.PyroI18n?.t(key, variables) || fallback;

  const SVG_NS = 'http://www.w3.org/2000/svg';
  const XLINK_NS = 'http://www.w3.org/1999/xlink';
  const FORBIDDEN_TAGS = new Set(['script', 'foreignobject', 'iframe', 'object', 'embed', 'audio', 'video', 'canvas']);
  const DEFINITION_TAGS = new Set(['defs', 'lineargradient', 'radialgradient', 'mask', 'clippath', 'symbol', 'marker', 'filter', 'pattern']);
  const CONTAINER_TAGS = new Set(['g', 'a', 'switch']);
  const GEOMETRY_TAGS = new Set(['rect', 'circle', 'ellipse', 'line', 'polyline', 'polygon', 'path']);
  const SUPPORTED_FILTER_PRIMITIVES = new Set(['fegaussianblur', 'feoffset', 'feflood', 'fecomposite', 'femerge', 'femergenode', 'fedropshadow', 'feblend', 'fecomponenttransfer', 'fefunca', 'feimage']);
  const NATIVE_FILTER_TAGS = new Set([
    'filter', 'feblend', 'fecolormatrix', 'fecomponenttransfer', 'fecomposite', 'feconvolvematrix', 'fediffuselighting',
    'fedisplacementmap', 'fedistantlight', 'fedropshadow', 'feflood', 'fefunca', 'fefuncb', 'fefuncg', 'fefuncr',
    'fegaussianblur', 'feimage', 'femerge', 'femergenode', 'femorphology', 'feoffset', 'fepointlight',
    'fespecularlighting', 'fespotlight', 'fetile', 'feturbulence'
  ]);
  const SUPPORTED_TAGS = new Set(['svg', 'defs', 'g', 'switch', 'use', 'symbol', 'rect', 'circle', 'ellipse', 'line', 'polyline', 'polygon', 'path', 'text', 'tspan', 'textpath', 'image', 'lineargradient', 'radialgradient', 'stop', 'mask', 'clippath', 'filter', 'pattern', 'marker', ...SUPPORTED_FILTER_PRIMITIVES, 'style', 'title', 'desc', 'metadata']);
  const APPROXIMATED_TAGS = new Set(['a']);
  const ALLOWED_TAGS = new Set([...SUPPORTED_TAGS, ...APPROXIMATED_TAGS]);
  const ANIMATION_TAGS = new Set(['animate', 'animatetransform', 'animatemotion', 'animatecolor', 'set', 'mpath']);
  const IGNORED_TAG_CODES = Object.freeze({});
  const DIAGNOSTIC_STATUSES = Object.freeze(['supported', 'approximated', 'ignored', 'blocked', 'missing']);
  const DOCUMENT_SECURITY_METADATA = new WeakMap();
  const BLEND_MODES = new Set(['normal', 'multiply', 'screen', 'overlay', 'darken', 'lighten', 'color-dodge', 'color-burn', 'hard-light', 'soft-light', 'difference', 'exclusion', 'hue', 'saturation', 'color', 'luminosity']);
  const LENGTH_UNITS = Object.freeze({ px: 1, pt: 96 / 72, pc: 16, in: 96, cm: 96 / 2.54, mm: 96 / 25.4, q: 96 / 101.6 });
  const DEFAULT_STYLE = Object.freeze({
    fill: '#000000', stroke: 'none', color: '#000000', strokeWidth: 1,
    lineCap: 'butt', lineJoin: 'miter', miterLimit: 4, dashArray: [], dashOffset: 0,
    fillOpacity: 1, strokeOpacity: 1, fillRule: 'nonzero', stopColor: '#000000', stopOpacity: 1,
    fontFamily: 'serif', fontSize: 16, fontStyle: 'normal', fontWeight: '400', fontSizeAdjust: 'none', letterSpacing: 0, wordSpacing: 0,
    lineHeight: 'normal', inlineSize: 'auto', writingMode: 'horizontal-tb', direction: 'ltr', textOrientation: 'mixed', textAlign: 'start', textDecoration: 'none', whiteSpace: 'normal', shapeInside: 'none',
    baselineShift: '0', dominantBaseline: 'auto',
    textAnchor: 'start', visibility: 'visible', display: 'inline', opacity: 1, mask: '', maskType: 'luminance', clipPath: '', filter: '',
    markerStart: '', markerMid: '', markerEnd: '',
    x: '0', y: '0', width: 'auto', height: 'auto', cx: '0', cy: '0', r: '0', rx: 'auto', ry: 'auto', d: 'none', marker: 'none', transform: 'none',
    transformOrigin: '0 0', transformBox: 'view-box', pathLength: 'none',
    floodColor: '#000000', floodOpacity: 1, filterColorInterpolation: 'linearRGB', blendMode: 'normal', pointerEvents: 'auto', imageRendering: 'auto',
    overflow: 'hidden', shapeRendering: 'auto', colorInterpolation: 'sRGB', vectorEffect: 'none', paintOrder: 'normal', isolation: 'auto'
  });
  const INHERITED_STYLE_KEYS = Object.freeze([
    'fill', 'stroke', 'color', 'strokeWidth', 'lineCap', 'lineJoin', 'miterLimit', 'dashArray', 'dashOffset',
    'fillOpacity', 'strokeOpacity', 'fillRule', 'fontFamily', 'fontSize', 'fontStyle', 'fontWeight', 'fontSizeAdjust', 'letterSpacing', 'wordSpacing',
    'lineHeight', 'writingMode', 'direction', 'textOrientation', 'textAlign', 'textDecoration', 'whiteSpace',
    'baselineShift', 'dominantBaseline', 'textAnchor', 'markerStart', 'markerMid', 'markerEnd', 'visibility', 'paintOrder'
  ]);
  const PRESENTATION_ATTRIBUTES = Object.freeze({
    fill: 'fill', stroke: 'stroke', color: 'color', 'stroke-width': 'strokeWidth', 'stroke-linecap': 'lineCap',
    'stroke-linejoin': 'lineJoin', 'stroke-miterlimit': 'miterLimit', 'stroke-dasharray': 'dashArray',
    'stroke-dashoffset': 'dashOffset', 'fill-opacity': 'fillOpacity', 'stroke-opacity': 'strokeOpacity',
    'fill-rule': 'fillRule', 'stop-color': 'stopColor', 'stop-opacity': 'stopOpacity',
    font: 'font', 'font-family': 'fontFamily', 'font-size': 'fontSize', 'font-style': 'fontStyle', 'font-size-adjust': 'fontSizeAdjust',
    'font-weight': 'fontWeight', 'letter-spacing': 'letterSpacing', 'word-spacing': 'wordSpacing', 'line-height': 'lineHeight', 'line-spacing': 'lineHeight',
    'inline-size': 'inlineSize', 'writing-mode': 'writingMode', direction: 'direction', 'text-orientation': 'textOrientation', 'text-align': 'textAlign', 'text-decoration': 'textDecoration', 'white-space': 'whiteSpace', 'shape-inside': 'shapeInside', 'baseline-shift': 'baselineShift',
    'dominant-baseline': 'dominantBaseline', 'marker-start': 'markerStart', 'marker-mid': 'markerMid', 'marker-end': 'markerEnd', visibility: 'visibility', display: 'display',
    x: 'x', y: 'y', width: 'width', height: 'height', cx: 'cx', cy: 'cy', r: 'r', rx: 'rx', ry: 'ry', d: 'd', marker: 'marker', transform: 'transform',
    'transform-origin': 'transformOrigin', 'transform-box': 'transformBox', 'path-length': 'pathLength',
    'text-anchor': 'textAnchor', opacity: 'opacity', mask: 'mask', 'mask-type': 'maskType', 'clip-path': 'clipPath', filter: 'filter',
    'flood-color': 'floodColor', 'flood-opacity': 'floodOpacity', 'color-interpolation-filters': 'filterColorInterpolation', 'mix-blend-mode': 'blendMode',
    'pointer-events': 'pointerEvents', 'image-rendering': 'imageRendering', overflow: 'overflow', 'shape-rendering': 'shapeRendering',
    'color-interpolation': 'colorInterpolation', 'vector-effect': 'vectorEffect', 'paint-order': 'paintOrder', isolation: 'isolation'
  });
  const NAMED_COLORS = Object.freeze({
    black: '#000000', silver: '#c0c0c0', gray: '#808080', grey: '#808080', white: '#ffffff', maroon: '#800000',
    red: '#ff0000', purple: '#800080', fuchsia: '#ff00ff', green: '#008000', lime: '#00ff00', olive: '#808000',
    yellow: '#ffff00', navy: '#000080', blue: '#0000ff', teal: '#008080', aqua: '#00ffff', orange: '#ffa500',
    transparent: 'transparent'
  });
  const LEGACY_SYSTEM_COLORS = Object.freeze({
    activeborder: '#c4c4c4', activecaption: '#000080', appworkspace: '#005c5c', background: '#005c5c',
    buttonface: '#dfdfdf', buttonhighlight: '#ffffff', buttonshadow: '#808080', buttontext: '#000000',
    captiontext: '#000000', graytext: '#808080', highlight: '#000080', highlighttext: '#ffffff',
    inactiveborder: '#c4c4c4', inactivecaption: '#808080', inactivecaptiontext: '#c4c4c4',
    infobackground: '#ffffe1', infotext: '#000000', menu: '#c4c4c4', menutext: '#000000', scrollbar: '#c4c4c4',
    threeddarkshadow: '#4a4a4a', threedface: '#dfdfdf', threedhighlight: '#ffffff',
    threedlightshadow: '#ffffff', threedshadow: '#808080', window: '#dfdfdf', windowframe: '#c4c4c4', windowtext: '#000000'
  });
  const SUPPORTED_SVG_FEATURES = new Set([
    'svg', 'svg-static', 'coreattribute', 'structure', 'basicstructure', 'containerattribute', 'conditionalprocessing',
    'image', 'style', 'viewportattribute', 'shape', 'text', 'basictext', 'paintattribute', 'basicpaintattribute',
    'opacityattribute', 'graphicsattribute', 'basicgraphicsattribute', 'marker', 'gradient', 'pattern', 'clip', 'basicclip',
    'mask', 'filter', 'basicfilter', 'svgdom'
  ].map((feature) => `http://www.w3.org/tr/svg11/feature#${feature}`));

  const finite = (value, fallback = 0) => Number.isFinite(Number(value)) ? Number(value) : fallback;
  const clamp = (value, minimum, maximum) => Math.min(maximum, Math.max(minimum, value));
  const round = (value, digits = 4) => Number(finite(value).toFixed(digits));
  const localName = (element) => String(element?.localName || element?.nodeName || '').toLowerCase().replace(/^.*:/, '');
  const attr = (element, name) => element?.getAttribute?.(name) ?? '';
  const hrefOf = (element) => String(attr(element, 'href') || attr(element, 'xlink:href') || element?.getAttributeNS?.(XLINK_NS, 'href') || '').trim();
  const childrenOf = (element) => Array.from(element?.children || []).filter((child) => child?.nodeType === 1 || child?.localName);
  const deepClone = (value) => value == null ? value : JSON.parse(JSON.stringify(value));
  const normalizeBlendMode = (value) => BLEND_MODES.has(String(value || '').trim().toLowerCase()) ? String(value).trim().toLowerCase() : 'normal';
  const safeText = (value, limit = 512) => String(value ?? '').replace(/[\u0000-\u001f\u007f]/g, ' ').trim().slice(0, limit);

  function normalizeDiagnostic(input = {}) {
    const status = DIAGNOSTIC_STATUSES.includes(input.status) ? input.status : 'approximated';
    const severity = ['info', 'warning', 'error'].includes(input.severity) ? input.severity : status === 'blocked' || status === 'missing' ? 'error' : 'warning';
    const id = safeText(input.id, 256);
    const ids = [...new Set([...(Array.isArray(input.ids) ? input.ids : []), id].map((entry) => safeText(entry, 256)).filter(Boolean))].slice(0, 8);
    return {
      code: safeText(input.code || 'SVG_IMPORT_WARNING', 96).replace(/[^A-Z0-9_:-]/gi, '_').toUpperCase(),
      severity, status, message: safeText(input.message || 'SVG-Inhalt wurde beim Import angepasst'),
      element: safeText(input.element, 96).toLowerCase(), attribute: safeText(input.attribute, 128).toLowerCase(),
      id: ids[0] || '', ids, count: Math.max(1, Math.floor(finite(input.count, 1)))
    };
  }

  function diagnosticKey(diagnostic) {
    return [diagnostic.code, diagnostic.status, diagnostic.severity, diagnostic.element, diagnostic.attribute, diagnostic.message].join('\u001f');
  }

  function addDiagnostic(target, input) {
    if (!(target instanceof Map) || target.size >= 1000 && !target.has(diagnosticKey(normalizeDiagnostic(input)))) return;
    const diagnostic = normalizeDiagnostic(input); const key = diagnosticKey(diagnostic); const existing = target.get(key);
    if (existing) {
      existing.count += diagnostic.count;
      existing.ids = [...new Set([...existing.ids, ...diagnostic.ids])].slice(0, 8); existing.id = existing.ids[0] || '';
    } else target.set(key, diagnostic);
  }

  function finalizedDiagnostics(target) {
    const statusOrder = new Map(DIAGNOSTIC_STATUSES.map((status, index) => [status, index]));
    return [...target.values()].map((entry) => ({ ...entry, ids: [...entry.ids] })).sort((left, right) =>
      (statusOrder.get(left.status) ?? 99) - (statusOrder.get(right.status) ?? 99)
      || left.code.localeCompare(right.code) || left.element.localeCompare(right.element)
    );
  }

  function summarizeDiagnostics(diagnostics) {
    const summary = Object.fromEntries(DIAGNOSTIC_STATUSES.map((status) => [status, 0]));
    (diagnostics || []).forEach((diagnostic) => { summary[diagnostic.status] += Math.max(1, finite(diagnostic.count, 1)); });
    summary.total = DIAGNOSTIC_STATUSES.reduce((total, status) => total + summary[status], 0); return summary;
  }

  function statusForElement(tag) {
    if (FORBIDDEN_TAGS.has(tag)) return 'blocked';
    if (SUPPORTED_TAGS.has(tag)) return 'supported';
    if (APPROXIMATED_TAGS.has(tag)) return 'approximated';
    return 'ignored';
  }

  function lengthUnitValue(amount, unit, relative, fontSize, viewport) {
    const normalizedUnit = String(unit || '').toLowerCase();
    if (normalizedUnit === '%') return amount * finite(relative) / 100;
    if (normalizedUnit === 'em') return amount * Math.max(0, finite(fontSize, 16));
    if (normalizedUnit === 'rem') return amount * 16;
    if (normalizedUnit === 'ex' || normalizedUnit === 'ch') return amount * Math.max(0, finite(fontSize, 16)) * .5;
    if (normalizedUnit === 'vw') return amount * Math.max(0, finite(viewport?.width, relative)) / 100;
    if (normalizedUnit === 'vh') return amount * Math.max(0, finite(viewport?.height, relative)) / 100;
    if (normalizedUnit === 'vmin') return amount * Math.min(Math.max(0, finite(viewport?.width, relative)), Math.max(0, finite(viewport?.height, relative))) / 100;
    if (normalizedUnit === 'vmax') return amount * Math.max(Math.max(0, finite(viewport?.width, relative)), Math.max(0, finite(viewport?.height, relative))) / 100;
    return amount * (LENGTH_UNITS[normalizedUnit || 'px'] || 1);
  }

  function parseCalcLength(value, relative, fontSize, viewport) {
    const source = String(value || '').trim(); const match = source.match(/^calc\(([\s\S]*)\)$/i);
    if (!match) return NaN;
    const expression = match[1]; let index = 0;
    const whitespace = () => { while (/\s/.test(expression[index] || '')) index += 1; };
    const primary = () => {
      whitespace();
      if (expression[index] === '(') {
        index += 1; const result = sum(); whitespace(); if (expression[index] !== ')') return NaN; index += 1; return result;
      }
      const number = expression.slice(index).match(/^(?:\d*\.\d+|\d+\.?)(?:e[-+]?\d+)?/i);
      if (!number) return NaN;
      index += number[0].length; const unit = expression.slice(index).match(/^(?:[a-z]+|%)/i)?.[0] || ''; index += unit.length;
      return lengthUnitValue(Number(number[0]), unit, relative, fontSize, viewport);
    };
    const unary = () => {
      whitespace();
      if (expression[index] === '+' || expression[index] === '-') {
        const sign = expression[index++] === '-' ? -1 : 1; return sign * unary();
      }
      return primary();
    };
    const product = () => {
      let result = unary();
      while (Number.isFinite(result)) {
        whitespace(); const operator = expression[index]; if (operator !== '*' && operator !== '/') break;
        index += 1; const operand = unary(); if (!Number.isFinite(operand) || operator === '/' && Math.abs(operand) < 1e-12) return NaN;
        result = operator === '*' ? result * operand : result / operand;
      }
      return result;
    };
    const sum = () => {
      let result = product();
      while (Number.isFinite(result)) {
        whitespace(); const operator = expression[index]; if (operator !== '+' && operator !== '-') break;
        index += 1; const operand = product(); if (!Number.isFinite(operand)) return NaN;
        result = operator === '+' ? result + operand : result - operand;
      }
      return result;
    };
    const result = sum(); whitespace(); return index === expression.length ? result : NaN;
  }

  function parseLength(value, relative = 0, fallback = 0, fontSize = 16, viewport = null) {
    const source = String(value ?? '').trim();
    if (!source) return fallback;
    if (/^calc\(/i.test(source)) {
      const calculated = parseCalcLength(source, relative, fontSize, viewport);
      return Number.isFinite(calculated) ? calculated : fallback;
    }
    const match = source.match(/^([-+]?(?:\d*\.\d+|\d+\.?)(?:e[-+]?\d+)?)\s*([a-z%]*)$/i);
    if (!match) return fallback;
    const amount = Number(match[1]); const unit = match[2].toLowerCase();
    if (!Number.isFinite(amount)) return fallback;
    return lengthUnitValue(amount, unit, relative, fontSize, viewport);
  }

  function parseOpacity(value, fallback = 1) {
    const source = String(value ?? '').trim();
    if (!source) return fallback;
    return clamp(source.endsWith('%') ? finite(source.slice(0, -1), fallback * 100) / 100 : finite(source, fallback), 0, 1);
  }

  function colorChannel(value) {
    const source = String(value).trim();
    return clamp(Math.round(source.endsWith('%') ? finite(source.slice(0, -1)) * 2.55 : finite(source)), 0, 255);
  }

  function normalizeColor(value, currentColor = '#000000') {
    const source = String(value || '').trim().toLowerCase();
    if (!source) return '#000000';
    if (source === 'none') return 'none';
    if (source === 'currentcolor') return normalizeColor(currentColor, '#000000');
    if (NAMED_COLORS[source]) return NAMED_COLORS[source];
    const hex = source.match(/^#([0-9a-f]{3,8})$/i);
    if (hex) {
      const digits = hex[1];
      if (digits.length === 3 || digits.length === 4) return `#${[...digits.slice(0, 3)].map((entry) => entry + entry).join('')}`;
      return `#${digits.slice(0, 6).padEnd(6, '0')}`;
    }
    const rgb = source.match(/^rgba?\((.*)\)$/i);
    if (rgb) {
      const channels = rgb[1].split(/[\s,\/]+/).filter(Boolean);
      if (channels.length >= 3) return `#${channels.slice(0, 3).map((entry) => colorChannel(entry).toString(16).padStart(2, '0')).join('')}`;
    }
    return /^[a-z][a-z0-9-]{0,48}$/i.test(source) ? source : '#000000';
  }

  function resolveImportedColor(value, currentColor, context, element = null, attribute = '') {
    const source = String(value || '').trim().toLowerCase(); const normalized = normalizeColor(source, currentColor);
    if (!source || source === 'none' || source === 'currentcolor' || NAMED_COLORS[source]
      || /^#[0-9a-f]{3,8}$/i.test(source) || /^rgba?\(/i.test(source)) return normalized;
    if (LEGACY_SYSTEM_COLORS[source]) return LEGACY_SYSTEM_COLORS[source];
    let resolved = '';
    try { resolved = typeof context?.resolveColor === 'function' ? String(context.resolveColor(source) || '').trim() : ''; }
    catch { resolved = ''; }
    if (resolved) {
      const concrete = normalizeColor(resolved, currentColor); const resolvedSource = resolved.toLowerCase();
      if (concrete !== resolvedSource || NAMED_COLORS[resolvedSource] || /^#|^rgba?\(/i.test(resolvedSource)) return concrete;
    }
    context?.diagnose?.({
      code: 'SVG_COLOR_UNRESOLVED', status: 'approximated', element: localName(element), attribute, id: attr(element, 'id'),
      message: `Farbwert „${safeText(value, 64)}“ konnte nicht aufgelöst werden und wird als Schwarz importiert`
    });
    return '#000000';
  }

  function splitCssTopLevel(value, separator) {
    const result = []; let start = 0; let quote = ''; let escaped = false; let roundDepth = 0; let squareDepth = 0;
    const source = String(value || '');
    for (let index = 0; index < source.length; index += 1) {
      const character = source[index];
      if (quote) {
        if (escaped) escaped = false;
        else if (character === '\\') escaped = true;
        else if (character === quote) quote = '';
        continue;
      }
      if (character === '"' || character === "'") { quote = character; continue; }
      if (character === '(') roundDepth += 1;
      else if (character === ')') roundDepth = Math.max(0, roundDepth - 1);
      else if (character === '[') squareDepth += 1;
      else if (character === ']') squareDepth = Math.max(0, squareDepth - 1);
      else if (character === separator && roundDepth === 0 && squareDepth === 0) { result.push(source.slice(start, index)); start = index + 1; }
    }
    result.push(source.slice(start)); return result;
  }

  function topLevelColon(value) {
    const source = String(value || ''); let quote = ''; let escaped = false; let roundDepth = 0;
    for (let index = 0; index < source.length; index += 1) {
      const character = source[index];
      if (quote) {
        if (escaped) escaped = false;
        else if (character === '\\') escaped = true;
        else if (character === quote) quote = '';
        continue;
      }
      if (character === '"' || character === "'") quote = character;
      else if (character === '(') roundDepth += 1;
      else if (character === ')') roundDepth = Math.max(0, roundDepth - 1);
      else if (character === ':' && roundDepth === 0) return index;
    }
    return -1;
  }

  function parseCssDeclarations(value) {
    return splitCssTopLevel(value, ';').map((raw) => {
      const separator = topLevelColon(raw); if (separator < 1) return null;
      const rawName = raw.slice(0, separator).trim();
      const name = rawName.startsWith('--') ? rawName : rawName.toLowerCase(); let declarationValue = raw.slice(separator + 1).trim();
      const important = /!\s*important\s*$/i.test(declarationValue);
      if (important) declarationValue = declarationValue.replace(/!\s*important\s*$/i, '').trim();
      return name && declarationValue ? { name, value: declarationValue, important } : null;
    }).filter(Boolean);
  }

  function parseStyleAttribute(value) {
    const result = {};
    parseCssDeclarations(value).forEach((declaration) => { result[declaration.name] = declaration.value; });
    return result;
  }

  function compileSimpleSelector(value) {
    const source = String(value || '').trim(); if (!source) return null;
    let rest = source; let type = ''; const ids = []; const classes = []; const attributes = []; const pseudos = [];
    const typeMatch = rest.match(/^([a-z_][\w-]*|\*)/i);
    if (typeMatch) { type = typeMatch[1] === '*' ? '*' : typeMatch[1].toLowerCase(); rest = rest.slice(typeMatch[0].length); }
    while (rest) {
      const token = rest.match(/^([.#])([-_a-z][\w-]*)/i);
      if (token) { (token[1] === '#' ? ids : classes).push(token[2]); rest = rest.slice(token[0].length); continue; }
      const attribute = rest.match(/^\[\s*([-_a-z][\w:.-]*)\s*(?:=\s*(?:"([^"]*)"|'([^']*)'|([^\]\s]+)))?\s*\]/i);
      if (attribute) {
        attributes.push({ name: attribute[1], operator: attribute[2] == null && attribute[3] == null && attribute[4] == null ? 'exists' : 'equals', value: attribute[2] ?? attribute[3] ?? attribute[4] ?? '' });
        rest = rest.slice(attribute[0].length); continue;
      }
      const pseudo = rest.match(/^:(first-child|last-child|only-child|root|empty)\b/i);
      if (pseudo) { pseudos.push(pseudo[1].toLowerCase()); rest = rest.slice(pseudo[0].length); continue; }
      return null;
    }
    if (!type && !ids.length && !classes.length && !attributes.length && !pseudos.length) return null;
    return { type, ids, classes, attributes, pseudos };
  }

  function selectorSequence(source) {
    const parts = []; const combinators = []; let buffer = ''; let pending = ''; let quote = ''; let bracketDepth = 0;
    const finish = () => {
      const value = buffer.trim(); if (!value) return true;
      if (parts.length) { if (!pending) return false; combinators.push(pending); }
      parts.push(value); buffer = ''; pending = ''; return true;
    };
    for (const character of source) {
      if (quote) {
        buffer += character;
        if (character === quote) quote = '';
        continue;
      }
      if (character === '"' || character === "'") { quote = character; buffer += character; continue; }
      if (character === '[') { bracketDepth += 1; buffer += character; continue; }
      if (character === ']') { bracketDepth = Math.max(0, bracketDepth - 1); buffer += character; continue; }
      if (!bracketDepth && /\s/.test(character)) {
        if (buffer && !finish()) return null;
        if (parts.length && !pending) pending = ' ';
        continue;
      }
      if (!bracketDepth && ['>', '+', '~'].includes(character)) {
        if (buffer && !finish()) return null;
        if (!parts.length || pending && pending !== ' ') return null;
        pending = character; continue;
      }
      buffer += character;
    }
    if (quote || bracketDepth || !finish() || pending) return null;
    return parts.length && combinators.length === parts.length - 1 ? { parts, combinators } : null;
  }

  function compileCssSelector(value) {
    const source = String(value || '').trim();
    if (!source || /\|/.test(source) || /\\/.test(source)) return null;
    const sequence = selectorSequence(source); if (!sequence) return null;
    const parts = sequence.parts.map(compileSimpleSelector);
    if (!parts.length || parts.some((part) => !part)) return null;
    return {
      source, parts, combinators: sequence.combinators,
      specificity: [
        parts.reduce((sum, part) => sum + part.ids.length, 0),
        parts.reduce((sum, part) => sum + part.classes.length + part.attributes.length + part.pseudos.length, 0),
        parts.reduce((sum, part) => sum + (part.type && part.type !== '*' ? 1 : 0), 0)
      ]
    };
  }

  function matchesSimpleSelector(element, selector) {
    if (!element || !selector) return false;
    if (selector.type && selector.type !== '*' && localName(element) !== selector.type) return false;
    if (selector.ids.length && !selector.ids.every((id) => attr(element, 'id') === id)) return false;
    const classes = new Set(String(attr(element, 'class') || '').trim().split(/\s+/).filter(Boolean));
    if (!selector.classes.every((className) => classes.has(className))) return false;
    const hasAttribute = (name) => element?.hasAttribute?.(name) || Array.from(element?.attributes || []).some((attribute) => attribute.name === name);
    if (!selector.attributes.every((entry) => hasAttribute(entry.name) && (entry.operator !== 'equals' || attr(element, entry.name) === entry.value))) return false;
    const parent = parentElementOf(element); const siblings = parent ? childrenOf(parent) : [];
    return selector.pseudos.every((pseudo) => {
      if (pseudo === 'root') return !parent;
      if (pseudo === 'empty') return !childrenOf(element).length && !String(element.textContent || '').trim();
      if (!parent) return false;
      const index = siblings.indexOf(element);
      if (pseudo === 'first-child') return index === 0;
      if (pseudo === 'last-child') return index === siblings.length - 1;
      return pseudo === 'only-child' && siblings.length === 1;
    });
  }

  function parentElementOf(element) {
    const parent = element?.parentElement || element?.parentNode;
    return parent?.nodeType === 1 || parent?.localName ? parent : null;
  }

  function matchesCssSelector(element, selector, boundary = null) {
    let current = element; const parts = selector?.parts || [];
    if (!parts.length || !matchesSimpleSelector(current, parts.at(-1))) return false;
    const parentWithinBoundary = (node) => node && node !== boundary ? parentElementOf(node) : null;
    for (let index = parts.length - 2; index >= 0; index -= 1) {
      const combinator = selector.combinators?.[index] || ' ';
      if (combinator === '>') {
        current = parentWithinBoundary(current);
        if (!matchesSimpleSelector(current, parts[index])) return false;
      } else if (combinator === '+') {
        const parent = parentWithinBoundary(current); const siblings = parent ? childrenOf(parent) : []; const position = siblings.indexOf(current);
        current = position > 0 ? siblings[position - 1] : null;
        if (!matchesSimpleSelector(current, parts[index])) return false;
      } else if (combinator === '~') {
        const parent = parentWithinBoundary(current); const siblings = parent ? childrenOf(parent) : []; let position = siblings.indexOf(current) - 1; current = null;
        while (position >= 0 && !current) { if (matchesSimpleSelector(siblings[position], parts[index])) current = siblings[position]; position -= 1; }
        if (!current) return false;
      } else {
        current = parentWithinBoundary(current);
        while (current && !matchesSimpleSelector(current, parts[index])) current = parentWithinBoundary(current);
        if (!current) return false;
      }
    }
    return true;
  }

  function cssDiagnostic(code, message, attribute = '') {
    return { code, status: 'ignored', element: 'style', attribute, message };
  }

  function matchingBrace(source, opening) {
    let depth = 0; let quote = ''; let escaped = false;
    for (let index = opening; index < source.length; index += 1) {
      const character = source[index];
      if (quote) {
        if (escaped) escaped = false;
        else if (character === '\\') escaped = true;
        else if (character === quote) quote = '';
        continue;
      }
      if (character === '"' || character === "'") quote = character;
      else if (character === '{') depth += 1;
      else if (character === '}' && --depth === 0) return index;
    }
    return -1;
  }

  function documentElements(document) {
    const elements = Array.from(document?.getElementsByTagName?.('*') || []);
    if (document?.documentElement && !elements.includes(document.documentElement)) elements.unshift(document.documentElement);
    return elements;
  }

  function compileStylesheets(document, diagnose = null, preparedElements = null) {
    const report = (diagnostic) => { if (typeof diagnose === 'function') diagnose(diagnostic); };
    const elements = preparedElements || documentElements(document);
    const styleElements = elements.filter((element) => localName(element) === 'style'); const rules = []; const firstLetterRules = [];
    let ruleCount = 0; let unsupportedSelectors = 0; let unsupportedProperties = 0; let sourceOrder = 0;
    styleElements.forEach((styleElement) => {
      const source = String(styleElement.textContent || '').replace(/\/\*[\s\S]*?\*\//g, ' '); let cursor = 0;
      while (cursor < source.length) {
        while (/\s/.test(source[cursor] || '')) cursor += 1;
        if (cursor >= source.length) break;
        const opening = source.indexOf('{', cursor); const semicolon = source.indexOf(';', cursor);
        if (semicolon >= 0 && (opening < 0 || semicolon < opening)) {
          const statement = source.slice(cursor, semicolon).trim();
          if (statement.startsWith('@')) report(cssDiagnostic('SVG_CSS_AT_RULE_UNSUPPORTED', `CSS-Regel „${safeText(statement, 96)}“ wird nicht unterstützt`, 'at-rule'));
          else if (statement) report(cssDiagnostic('SVG_CSS_RULE_INVALID', `Unvollständige CSS-Regel „${safeText(statement, 96)}“ wurde ignoriert`, 'rule'));
          cursor = semicolon + 1; continue;
        }
        if (opening < 0) {
          const remainder = source.slice(cursor).trim();
          if (remainder) report(cssDiagnostic('SVG_CSS_RULE_INVALID', `Unvollständige CSS-Regel „${safeText(remainder, 96)}“ wurde ignoriert`, 'rule'));
          break;
        }
        const closing = matchingBrace(source, opening);
        if (closing < 0) { report(cssDiagnostic('SVG_CSS_RULE_INVALID', 'CSS-Regel ohne schließende Klammer wurde ignoriert', 'rule')); break; }
        const selectorText = source.slice(cursor, opening).trim(); const body = source.slice(opening + 1, closing); cursor = closing + 1;
        if (/^@font-face\b/i.test(selectorText)) continue;
        if (selectorText.startsWith('@')) {
          report(cssDiagnostic('SVG_CSS_AT_RULE_UNSUPPORTED', `CSS-At-Regel „${safeText(selectorText, 96)}“ wird nicht unterstützt`, 'at-rule')); continue;
        }
        ruleCount += 1; sourceOrder += 1;
        const declarations = parseCssDeclarations(body).filter((declaration) => {
          if (PRESENTATION_ATTRIBUTES[declaration.name] || declaration.name.startsWith('--')) return true;
          unsupportedProperties += 1;
          report(cssDiagnostic('SVG_CSS_PROPERTY_UNSUPPORTED', `CSS-Eigenschaft „${safeText(declaration.name, 96)}“ wird nicht unterstützt`, declaration.name)); return false;
        });
        splitCssTopLevel(selectorText, ',').map((selector) => selector.trim()).filter(Boolean).forEach((selectorSource) => {
          const firstLetter = /::?first-letter\s*$/i.test(selectorSource);
          const selector = compileCssSelector(firstLetter ? selectorSource.replace(/::?first-letter\s*$/i, '').trim() : selectorSource);
          if (!selector) {
            unsupportedSelectors += 1;
            report(cssDiagnostic('SVG_CSS_SELECTOR_UNSUPPORTED', `CSS-Selektor „${safeText(selectorSource, 128)}“ wird nicht unterstützt`, 'selector')); return;
          }
          if (declarations.length) (firstLetter ? firstLetterRules : rules).push({ selector, declarations, order: sourceOrder });
        });
      }
    });
    return { rules, firstLetterRules, styleElements: styleElements.length, ruleCount, unsupportedSelectors, unsupportedProperties };
  }

  function resolveFirstLetterStyle(element, inheritedStyle, context) {
    const matching = (context?.firstLetterRules || []).filter((rule) => matchesCssSelector(element, rule.selector, context?.cssSelectorBoundary || null));
    if (!matching.length) return null;
    const winners = new Map();
    matching.forEach((rule) => rule.declarations.forEach((declaration) => {
      const candidate = { ...declaration, specificity: [0, ...rule.selector.specificity], order: rule.order };
      if (cascadeWins(candidate, winners.get(declaration.name))) winners.set(declaration.name, candidate);
    }));
    const style = { ...inheritedStyle, cssVariables: { ...(inheritedStyle?.cssVariables || {}) } };
    [...winners.values()].forEach((declaration) => applyStyleDeclaration(style, declaration, inheritedStyle, context, element));
    return style;
  }

  function parseDashArray(value, relative = 0, fontSize = 16, viewport = null) {
    const source = String(value || '').trim();
    if (!source || source === 'none') return [];
    return (source.match(/[-+]?(?:\d*\.\d+|\d+\.?)(?:e[-+]?\d+)?(?:[a-z]+|%)?/gi) || [])
      .map((entry) => round(Math.max(0, parseLength(entry, relative, 0, fontSize, viewport))))
      .filter((entry) => entry > 0);
  }

  function cascadeWins(candidate, existing) {
    if (!existing) return true;
    if (candidate.important !== existing.important) return candidate.important;
    for (let index = 0; index < candidate.specificity.length; index += 1) {
      if (candidate.specificity[index] !== existing.specificity[index]) return candidate.specificity[index] > existing.specificity[index];
    }
    return candidate.order >= existing.order;
  }

  function cascadedDeclarations(element, context) {
    const winners = new Map();
    const offer = (declaration, specificity, order) => {
      if (!PRESENTATION_ATTRIBUTES[declaration.name] && !declaration.name.startsWith('--')) return;
      const candidate = { ...declaration, specificity, order };
      if (cascadeWins(candidate, winners.get(declaration.name))) winners.set(declaration.name, candidate);
    };
    Object.keys(PRESENTATION_ATTRIBUTES).forEach((name) => {
      // `marker` is a CSS shorthand only. SVG explicitly does not define a
      // same-named presentation attribute (marker-start/mid/end are valid).
      if (name === 'marker') return;
      const value = attr(element, name); if (value !== '') offer({ name, value, important: false }, [0, 0, 0, 0], -1);
    });
    const legacyPathLength = attr(element, 'pathLength');
    if (legacyPathLength !== '') offer({ name: 'path-length', value: legacyPathLength, important: false }, [0, 0, 0, 0], -1);
    (context?.cssRules || []).forEach((rule) => {
      if (!matchesCssSelector(element, rule.selector, context?.cssSelectorBoundary || null)) return;
      rule.declarations.forEach((declaration) => offer(declaration, [0, ...rule.selector.specificity], rule.order));
    });
    parseCssDeclarations(attr(element, 'style')).forEach((declaration) => offer(declaration, [1, 0, 0, 0], Number.MAX_SAFE_INTEGER - 1));
    if (attr(element, 'data-blend-mode')) offer({ name: 'mix-blend-mode', value: attr(element, 'data-blend-mode'), important: false }, [1, 0, 0, 0], Number.MAX_SAFE_INTEGER);
    return [...winners.values()];
  }

  function resolvedCssKeyword(property, value, parentStyle) {
    const source = String(value || '').trim(); const lower = source.toLowerCase();
    if (lower === 'inherit') return deepClone(parentStyle?.[property] ?? DEFAULT_STYLE[property]);
    if (lower === 'initial') return deepClone(DEFAULT_STYLE[property]);
    if (lower === 'unset') return deepClone(INHERITED_STYLE_KEYS.includes(property) ? parentStyle?.[property] ?? DEFAULT_STYLE[property] : DEFAULT_STYLE[property]);
    return source;
  }

  function resolvedCssVariables(value, variables, stack = new Set(), depth = 0) {
    const source = String(value ?? ''); if (!/var\s*\(/i.test(source)) return source;
    if (depth >= 32) return null;
    let result = ''; let cursor = 0;
    while (cursor < source.length) {
      const match = /var\s*\(/ig; match.lastIndex = cursor; const found = match.exec(source);
      if (!found) { result += source.slice(cursor); break; }
      result += source.slice(cursor, found.index);
      const opening = found.index + found[0].lastIndexOf('('); let level = 1; let quote = ''; let escaped = false; let closing = -1;
      for (let index = opening + 1; index < source.length; index += 1) {
        const character = source[index];
        if (quote) {
          if (escaped) escaped = false;
          else if (character === '\\') escaped = true;
          else if (character === quote) quote = '';
          continue;
        }
        if (character === '"' || character === "'") quote = character;
        else if (character === '(') level += 1;
        else if (character === ')' && --level === 0) { closing = index; break; }
      }
      if (closing < 0) return null;
      const argumentsList = splitCssTopLevel(source.slice(opening + 1, closing), ',');
      const name = String(argumentsList.shift() || '').trim(); const fallback = argumentsList.join(',').trim();
      if (!/^--[-_a-z0-9]+$/i.test(name)) return null;
      let replacement = null;
      if (Object.hasOwn(variables || {}, name) && !stack.has(name)) {
        const nextStack = new Set(stack); nextStack.add(name);
        replacement = resolvedCssVariables(variables[name], variables, nextStack, depth + 1);
      }
      if (replacement == null && fallback) replacement = resolvedCssVariables(fallback, variables, stack, depth + 1);
      if (replacement == null) return null;
      result += replacement; cursor = closing + 1;
    }
    return result;
  }

  function applyStyleDeclaration(style, declaration, parentStyle, context, element) {
    const name = declaration.name; const property = PRESENTATION_ATTRIBUTES[name]; if (!property) return;
    const substituted = resolvedCssVariables(declaration.value, style.cssVariables);
    if (substituted == null) return;
    const value = resolvedCssKeyword(property, substituted, parentStyle);
    if (typeof value !== 'string') { style[property] = value; return; }
    if (property === 'fill' || property === 'stroke' || property === 'color' || property === 'stopColor' || property === 'floodColor') {
      const keyword = String(value).trim().toLowerCase();
      if (keyword === 'context-stroke') {
        style[property] = parentStyle?.contextStroke ?? parentStyle?.stroke ?? style.stroke ?? '#000000';
        if (property === 'fill' || property === 'stroke') style[`${property}ContextPaint`] = style[property];
      }
      else if (keyword === 'context-fill') {
        style[property] = parentStyle?.contextFill ?? parentStyle?.fill ?? style.fill ?? '#000000';
        if (property === 'fill' || property === 'stroke') style[`${property}ContextPaint`] = style[property];
      }
      else if (property !== 'color' && keyword === 'currentcolor') style[property] = 'currentColor';
      else style[property] = /^url\(/i.test(value) ? value.trim() : resolveImportedColor(value, style.color, context, element, name);
    }
    else if (property === 'font') {
      const shorthand = String(value).trim().match(/^(?:(italic|oblique|normal)\s+)?(?:(bold|bolder|lighter|[1-9]00|normal)\s+)?([-+]?(?:\d*\.\d+|\d+\.?)(?:e[-+]?\d+)?(?:px|pt|pc|in|cm|mm|q|em|rem|%)?)(?:\s*\/\s*([^\s]+))?\s+(.+)$/i);
      if (!shorthand) return;
      if (shorthand[1]) style.fontStyle = shorthand[1];
      if (shorthand[2]) style.fontWeight = shorthand[2];
      style.fontSize = parseLength(shorthand[3], finite(parentStyle?.fontSize, 16), style.fontSize);
      if (shorthand[4]) style.lineHeight = shorthand[4];
      style.fontFamily = shorthand[5].trim();
    }
    else if (property === 'fontSize') style[property] = parseLength(value, finite(parentStyle?.fontSize, 16), style[property]);
    else if (property === 'strokeWidth' || property === 'dashOffset') {
      const viewport = context?.viewport || { width: 0, height: 0 };
      const normalizedDiagonal = Math.hypot(finite(viewport.width), finite(viewport.height)) / Math.SQRT2;
      style[property] = parseLength(value, normalizedDiagonal, style[property], style.fontSize, viewport);
    }
    else if (property === 'letterSpacing' || property === 'wordSpacing') {
      const ch = String(value).trim().match(/^([-+]?(?:\d*\.\d+|\d+\.?)(?:e[-+]?\d+)?)ch$/i); let measuredCh = NaN;
      if (ch && typeof context?.measureText === 'function') {
        let family = String(style.fontFamily || 'serif');
        if (typeof context.resolveFont === 'function') {
          try { family = String(context.resolveFont(family) || family); } catch { /* Browser-Fallback verwenden. */ }
        }
        try {
          measuredCh = Number(ch[1]) * Number(context.measureText('0', {
            fontFamily: family, fontSize: style.fontSize, fontStyle: style.fontStyle, fontWeight: style.fontWeight
          }));
        } catch { measuredCh = NaN; }
      }
      style[property] = Number.isFinite(measuredCh) ? measuredCh : parseLength(value, 0, style[property], style.fontSize, context?.viewport);
    }
    else if (property === 'baselineShift' || property === 'dominantBaseline') style[property] = String(value).trim();
    else if (property === 'fillOpacity' || property === 'strokeOpacity' || property === 'stopOpacity' || property === 'floodOpacity' || property === 'opacity') style[property] = parseOpacity(value, style[property]);
    else if (property === 'miterLimit') style[property] = Math.max(1, finite(value, style[property]));
    else if (property === 'dashArray') {
      const viewport = context?.viewport || { width: 0, height: 0 };
      const normalizedDiagonal = Math.hypot(finite(viewport.width), finite(viewport.height)) / Math.SQRT2;
      style[property] = parseDashArray(value, normalizedDiagonal, style.fontSize, viewport);
    }
    else if (property === 'blendMode') style[property] = normalizeBlendMode(value);
    else if (property === 'marker') {
      style.marker = String(value).trim(); style.markerStart = style.marker; style.markerMid = style.marker; style.markerEnd = style.marker;
    }
    else style[property] = String(value).trim();
  }

  function resolveStyle(element, parentStyle = DEFAULT_STYLE, context = null) {
    const style = { ...DEFAULT_STYLE, cssVariables: { ...(parentStyle?.cssVariables || {}) } };
    if (parentStyle?.contextFill != null) style.contextFill = parentStyle.contextFill;
    if (parentStyle?.contextStroke != null) style.contextStroke = parentStyle.contextStroke;
    if (parentStyle?.fillContextPaint != null) style.fillContextPaint = parentStyle.fillContextPaint;
    if (parentStyle?.strokeContextPaint != null) style.strokeContextPaint = parentStyle.strokeContextPaint;
    if (parentStyle?.contextPaintBounds != null) style.contextPaintBounds = parentStyle.contextPaintBounds;
    INHERITED_STYLE_KEYS.forEach((key) => { if (parentStyle?.[key] != null) style[key] = deepClone(parentStyle[key]); });
    const declarations = cascadedDeclarations(element, context);
    declarations.filter((declaration) => declaration.name.startsWith('--'))
      .forEach((declaration) => { style.cssVariables[declaration.name] = declaration.value; });
    declarations.filter((declaration) => !declaration.name.startsWith('--'))
      .sort((left, right) => (left.name === 'color' ? -1 : right.name === 'color' ? 1 : 0))
      .forEach((declaration) => applyStyleDeclaration(style, declaration, parentStyle, context, element));
    return style;
  }

  function resolveDocumentStyle(element, context = null) {
    const ancestry = []; let current = element; const visited = new Set();
    while (current && !visited.has(current)) {
      visited.add(current);
      if (localName(current)) ancestry.unshift(current);
      current = current.parentElement || current.parentNode;
    }
    return ancestry.reduce((style, node) => resolveStyle(node, style, context), DEFAULT_STYLE);
  }

  function parseViewBox(value) {
    const values = String(value || '').match(/[-+]?(?:\d*\.\d+|\d+\.?)(?:e[-+]?\d+)?/gi)?.map(Number) || [];
    return values.length >= 4 && values.slice(0, 4).every(Number.isFinite) && values[2] > 0 && values[3] > 0
      ? { x: values[0], y: values[1], width: values[2], height: values[3] }
      : null;
  }

  function viewBoxMatrix(viewBox, width, height, preserveAspectRatio = 'xMidYMid meet') {
    if (!viewBox) return SvgPath.identityMatrix();
    const scaleX = width / viewBox.width; const scaleY = height / viewBox.height; const source = String(preserveAspectRatio || 'xMidYMid meet').trim();
    if (/\bnone\b/i.test(source)) return [scaleX, 0, 0, scaleY, -viewBox.x * scaleX, -viewBox.y * scaleY];
    const slice = /\bslice\b/i.test(source); const scale = slice ? Math.max(scaleX, scaleY) : Math.min(scaleX, scaleY);
    const extraX = width - viewBox.width * scale; const extraY = height - viewBox.height * scale;
    const alignX = /xmax/i.test(source) ? extraX : /xmid/i.test(source) ? extraX / 2 : 0;
    const alignY = /ymax/i.test(source) ? extraY : /ymid/i.test(source) ? extraY / 2 : 0;
    return [scale, 0, 0, scale, alignX - viewBox.x * scale, alignY - viewBox.y * scale];
  }

  function parseSvgDocument(markup, options = {}) {
    const Parser = options.DOMParser || root.DOMParser;
    if (typeof Parser !== 'function') throw new Error('DOMParser ist in dieser Umgebung nicht verfügbar');
    const prepared = Security?.prepareSvgSource
      ? Security.prepareSvgSource(markup, options.limits)
      : { source: Security?.assertSvgSource ? Security.assertSvgSource(markup, options.limits) : String(markup || ''), dtdNeutralized: false };
    const source = prepared.source;
    if (!source.trim()) throw new Error('SVG-Datei ist leer');
    if (source.length > 32 * 1024 * 1024) throw new Error('SVG-Datei ist zu groß');
    const document = new Parser().parseFromString(source, 'image/svg+xml');
    const parserError = document.querySelector?.('parsererror');
    if (parserError) throw new Error(`SVG ist ungültig: ${String(parserError.textContent || '').trim().slice(0, 240)}`);
    const svg = document.documentElement;
    if (localName(svg) !== 'svg' || svg.namespaceURI && svg.namespaceURI !== SVG_NS) throw new Error('Datei enthält kein SVG-Wurzelelement');
    Security?.assertSvgDocument?.(document, options.limits);
    if (document && typeof document === 'object') DOCUMENT_SECURITY_METADATA.set(document, prepared);
    return document;
  }

  function elementDiagnostic(element, tag) {
    const details = { element: tag, id: attr(element, 'id') };
    if (element?.namespaceURI && element.namespaceURI !== SVG_NS) return { ...details, code: 'SVG_NAMESPACE_BLOCKED', severity: 'error', status: 'blocked', message: `Namespace-fremdes Element <${tag || '?'}> wurde ignoriert` };
    if (FORBIDDEN_TAGS.has(tag)) return { ...details, code: 'SVG_ACTIVE_CONTENT_BLOCKED', severity: 'error', status: 'blocked', message: `Unsicheres SVG-Element <${tag}> wurde ignoriert` };
    if (tag === 'a') return { ...details, code: 'SVG_LINK_FLATTENED', status: 'approximated', message: 'SVG-Links werden als normale Gruppen ohne Navigation importiert' };
    if (ANIMATION_TAGS.has(tag)) return { ...details, code: 'SVG_ANIMATION_IGNORED', status: 'ignored', message: 'SVG-Animationen werden beim statischen Import ignoriert' };
    if (tag.startsWith('fe') && !SUPPORTED_FILTER_PRIMITIVES.has(tag)) return { ...details, code: 'SVG_FILTER_PRIMITIVE_UNSUPPORTED', status: 'ignored', message: `SVG-Filterprimitive <${tag}> wird noch nicht in einen editierbaren Effekt übersetzt` };
    if (IGNORED_TAG_CODES[tag]) return { ...details, code: IGNORED_TAG_CODES[tag][0], status: 'ignored', message: IGNORED_TAG_CODES[tag][1] };
    if (!SUPPORTED_TAGS.has(tag)) return { ...details, code: 'SVG_ELEMENT_UNSUPPORTED', status: 'ignored', message: `Nicht unterstütztes SVG-Element <${tag || '?'}> wird ignoriert` };
    return null;
  }

  function analyzeDocument(document, prepared = null) {
    const diagnosticMap = new Map(); const elementCounts = new Map(); const attributeCounts = new Map();
    const referenceCounts = new Map(); const externalResources = []; const knownIds = new Set(); const duplicateIds = new Set();
    const security = DOCUMENT_SECURITY_METADATA.get(document) || { dtdNeutralized: false };
    if (security.dtdNeutralized) addDiagnostic(diagnosticMap, {
      code: 'SVG_DTD_NEUTRALIZED', severity: 'info', status: 'supported', element: 'svg', attribute: 'doctype',
      message: 'Die SVG-Datei enthielt eine DTD. DTD-Inhalte und externe Referenzen wurden aus Sicherheitsgründen ignoriert.'
    });
    const elements = prepared?.elements || documentElements(document);
    const stylesheet = compileStylesheets(document, (diagnostic) => addDiagnostic(diagnosticMap, diagnostic), elements);
    if (prepared) { prepared.elements = elements; prepared.stylesheet = stylesheet; }

    elements.forEach((element) => {
      const id = safeText(attr(element, 'id'), 256); if (!id) return;
      if (knownIds.has(id)) duplicateIds.add(id); else knownIds.add(id);
    });
    duplicateIds.forEach((id) => addDiagnostic(diagnosticMap, {
      code: 'SVG_DUPLICATE_ID', status: 'approximated', element: 'svg', id,
      message: 'Mehrfach verwendete SVG-IDs können nicht eindeutig aufgelöst werden'
    }));

    const addReference = (element, attributeName, rawValue) => {
      const value = safeText(rawValue, 512); if (!value) return;
      const internal = value.startsWith('#'); const key = `${attributeName}\u001f${value}\u001f${internal}`; const existing = referenceCounts.get(key);
      if (existing) existing.count += 1;
      else referenceCounts.set(key, { kind: attributeName, value, internal, count: 1 });
      if (internal && !knownIds.has(value.slice(1))) {
        const targetId = safeText(value.slice(1), 256); const useReference = localName(element) === 'use';
        addDiagnostic(diagnosticMap, {
          code: useReference ? 'SVG_USE_REFERENCE_MISSING' : 'SVG_REFERENCE_MISSING', severity: 'error', status: 'missing',
          element: localName(element), attribute: attributeName, id: targetId,
          message: useReference
            ? `<use> „${safeText(attr(element, 'id'), 128) || '(ohne ID)'}“ verweist auf das fehlende Ziel #${targetId || '?'}`
            : `Eine interne SVG-Referenz zeigt auf das fehlende Ziel #${targetId || '?'}`
        });
      }
      const navigationOnly = localName(element) === 'a' && (attributeName === 'href' || attributeName === 'xlink:href');
      if (!internal && !/^data:/i.test(value) && !navigationOnly) {
        if (externalResources.length < 1000) externalResources.push({ element: localName(element), id: safeText(attr(element, 'id'), 256), attribute: attributeName, value });
        addDiagnostic(diagnosticMap, {
          code: 'SVG_EXTERNAL_RESOURCE_MISSING', severity: 'error', status: 'missing', element: localName(element), attribute: attributeName, id: attr(element, 'id'),
          message: 'Eine externe SVG-Ressource kann beim lokalen Import nicht automatisch übernommen werden'
        });
      }
    };

    let inlineStyles = 0; let classAttributes = 0;
    elements.forEach((element) => {
      const tag = localName(element); if (!tag) return;
      elementCounts.set(tag, (elementCounts.get(tag) || 0) + 1);
      const diagnostic = elementDiagnostic(element, tag); if (diagnostic) addDiagnostic(diagnosticMap, diagnostic);
      if (tag === 'style') {
        const css = String(element.textContent || '');
        for (const match of css.matchAll(/url\(\s*["']?([^)'"\s]+)["']?\s*\)/gi)) addReference(element, 'css-url', match[1]);
      }
      Array.from(element.attributes || []).forEach((attribute) => {
        const name = String(attribute.name || '').toLowerCase(); const value = String(attribute.value || ''); if (!name) return;
        attributeCounts.set(name, (attributeCounts.get(name) || 0) + 1);
        if (name === 'style') {
          inlineStyles += 1;
          parseCssDeclarations(value).forEach((declaration) => {
            if (PRESENTATION_ATTRIBUTES[declaration.name] || declaration.name.startsWith('--')) return;
            addDiagnostic(diagnosticMap, cssDiagnostic(
              'SVG_CSS_PROPERTY_UNSUPPORTED',
              `CSS-Eigenschaft „${safeText(declaration.name, 96)}“ wird nicht unterstützt`,
              declaration.name
            ));
          });
        }
        if (name === 'class') classAttributes += 1;
        if (/^on/i.test(name)) addDiagnostic(diagnosticMap, {
          code: 'SVG_EVENT_ATTRIBUTE_BLOCKED', severity: 'error', status: 'blocked', element: tag, attribute: name, id: attr(element, 'id'),
          message: `Ereignisattribut ${name} wurde ignoriert`
        });
        if (name === 'href' || name === 'xlink:href') addReference(element, name, value);
        for (const match of value.matchAll(/url\(\s*["']?([^)'"\s]+)["']?\s*\)/gi)) addReference(element, name, match[1]);
      });
    });

    const analysisStyleContext = { cssRules: stylesheet.rules, resolveColor: null };
    elements.filter((element) => localName(element) === 'filter').forEach((element) => {
      const plan = filterPlan(element, analysisStyleContext);
      plan.diagnostics.forEach((diagnostic) => addDiagnostic(diagnosticMap, diagnostic));
      if (plan.textAntialias) addDiagnostic(diagnosticMap, {
        code: 'SVG_TEXT_ANTIALIAS_FILTER_SUPPORTED', severity: 'info', status: 'supported', element: 'filter', id: attr(element, 'id'),
        message: `PYRO-Textkantenglättung „${plan.textAntialias}“ wird beim Import wiederhergestellt`
      });
      else if (plan.image && embeddedImageHref(hrefOf(plan.image.element), analysisStyleContext, plan.image.element)) addDiagnostic(diagnosticMap, {
        code: 'SVG_FILTER_IMAGE_SUPPORTED', severity: 'info', status: 'supported', element: 'filter', id: attr(element, 'id'),
        message: `SVG-Filter #${attr(element, 'id') || '?'} erzeugt ein eingebettetes editierbares Bild`
      });
      else if (plan.effects.length) addDiagnostic(diagnosticMap, {
        code: 'SVG_FILTER_SUPPORTED', severity: 'info', status: 'supported', element: 'filter', id: attr(element, 'id'),
        message: `SVG-Filter #${attr(element, 'id') || '?'} enthält ${plan.effects.length} editierbaren PYRO-Effekt${plan.effects.length === 1 ? '' : 'e'}`
      });
      else if (plan.nativeRequired) addDiagnostic(diagnosticMap, {
        code: 'SVG_FILTER_NATIVE_PRESERVABLE', severity: 'info', status: 'approximated', element: 'filter', id: attr(element, 'id'),
        message: `SVG-Filter #${attr(element, 'id') || '?'} bleibt als sichere native SVG-Darstellung erhalten`
      });
    });

    const elementSummary = Object.fromEntries(DIAGNOSTIC_STATUSES.map((status) => [status, 0]));
    const elementInventory = [...elementCounts].map(([name, count]) => {
      const status = statusForElement(name); elementSummary[status] += count; return { name, count, status };
    }).sort((left, right) => right.count - left.count || left.name.localeCompare(right.name));
    elementSummary.total = elements.length;
    const diagnostics = finalizedDiagnostics(diagnosticMap);
    return {
      version: 1, nodeCount: elements.length, elementSummary, elements: elementInventory,
      attributes: [...attributeCounts].map(([name, count]) => ({ name, count })).sort((left, right) => right.count - left.count || left.name.localeCompare(right.name)),
      css: {
        styleElements: stylesheet.styleElements, rules: stylesheet.ruleCount, appliedRules: stylesheet.rules.length + stylesheet.firstLetterRules.length,
        unsupportedSelectors: stylesheet.unsupportedSelectors, unsupportedProperties: stylesheet.unsupportedProperties,
        inlineStyles, classAttributes
      },
      references: [...referenceCounts.values()].sort((left, right) => right.count - left.count || left.value.localeCompare(right.value)),
      externalResources,
      security: {
        dtdNeutralized: Boolean(security.dtdNeutralized),
        externalSubsetIgnored: Boolean(security.externalSubsetIgnored),
        internalSubsetIgnored: Boolean(security.internalSubsetIgnored),
        internalEntityDeclarationsIgnored: Math.max(0, finite(security.internalEntityDeclarationsIgnored))
      },
      diagnostics, diagnosticSummary: summarizeDiagnostics(diagnostics)
    };
  }

  function analyze(markup, options = {}) {
    return analyzeDocument(parseSvgDocument(markup, options));
  }

  function createContext(options, document, viewport, initialDiagnostics = [], preparedStylesheet = null) {
    let fallbackId = 0; const usedIds = new Set();
    const create = typeof options.createId === 'function' ? options.createId : (prefix) => `${prefix}-${++fallbackId}`;
    const diagnosticMap = new Map(); const warnings = [];
    const optionLanguages = Array.isArray(options.languages) ? options.languages : options.language ? [options.language] : [];
    const navigatorLanguages = Array.isArray(root.navigator?.languages) ? root.navigator.languages : root.navigator?.language ? [root.navigator.language] : [];
    const languages = [...new Set([...(optionLanguages.length ? optionLanguages : navigatorLanguages), 'en'].map((language) => String(language || '').trim().toLowerCase()).filter(Boolean))];
    const optionFeatures = Array.isArray(options.supportedFeatures) || options.supportedFeatures instanceof Set ? [...options.supportedFeatures] : [];
    const optionExtensions = Array.isArray(options.supportedExtensions) || options.supportedExtensions instanceof Set ? [...options.supportedExtensions] : [];
    const supportedFeatures = new Set([...SUPPORTED_SVG_FEATURES, ...optionFeatures].map((feature) => String(feature || '').trim().toLowerCase()).filter(Boolean));
    const supportedExtensions = new Set(optionExtensions.map((extension) => String(extension || '').trim()).filter(Boolean));
    const compiledStylesheet = preparedStylesheet || compileStylesheets(document);
    const context = {
      document, viewport, warnings, definitions: new Map(), deferredObjects: [], nodeCount: 0, referenceStack: new Set(),
      languages, supportedFeatures, supportedExtensions,
      DOMParser: options.DOMParser || root.DOMParser,
      cssRules: compiledStylesheet.rules, firstLetterRules: compiledStylesheet.firstLetterRules,
      resolveColor: typeof options.resolveColor === 'function' ? options.resolveColor : null,
      resolveFont: typeof options.resolveFont === 'function' ? options.resolveFont : null,
      measureText: typeof options.measureText === 'function' ? options.measureText : null,
      createId(prefix) {
        let candidate = String(create(prefix) || `${prefix}-${++fallbackId}`).replace(/[^a-z0-9_-]/gi, '-').slice(0, 128) || `${prefix}-${++fallbackId}`;
        const base = candidate; let suffix = 2;
        while (usedIds.has(candidate)) candidate = `${base}-${suffix++}`;
        usedIds.add(candidate); return candidate;
      },
      diagnose(input) {
        const diagnostic = normalizeDiagnostic(input); addDiagnostic(diagnosticMap, diagnostic);
        if (diagnostic.severity !== 'info' && warnings.length < 1000 && !warnings.includes(diagnostic.message)) warnings.push(diagnostic.message);
      },
      warn(message, details = {}) { this.diagnose({ code: 'SVG_IMPORT_APPROXIMATED', status: 'approximated', message, ...details }); },
      getDiagnostics() { return finalizedDiagnostics(diagnosticMap); }
    };
    initialDiagnostics.forEach((diagnostic) => context.diagnose(diagnostic)); return context;
  }

  function safeName(element, fallback) {
    return String(attr(element, 'aria-label') || attr(element, 'data-name') || attr(element, 'inkscape:label') || attr(element, 'id') || fallback)
      .replace(/[\u0000-\u001f\u007f]/g, ' ').trim().slice(0, 256) || fallback;
  }

  function checkElementSafety(element, context) {
    const tag = localName(element);
    if (element?.namespaceURI && element.namespaceURI !== SVG_NS) return false;
    if (!ALLOWED_TAGS.has(tag) || FORBIDDEN_TAGS.has(tag)) return false;
    context.nodeCount += 1;
    if (context.nodeCount > (Security?.LIMITS?.svgElements || 100000)) throw new Error('SVG enthält zu viele Elemente');
    return true;
  }

  function transformReferenceBox(element, style, context) {
    const viewport = context?.viewport || { width: 0, height: 0 };
    const transformBox = String(style?.transformBox || 'view-box').trim().toLowerCase();
    if ((transformBox === 'fill-box' || transformBox === 'stroke-box' || transformBox === 'content-box' || transformBox === 'border-box')
      && GEOMETRY_TAGS.has(localName(element))) {
      const bounds = SvgPath.subpathBounds(primitivePaths(element, context, style));
      if (bounds.width > 0 || bounds.height > 0) {
        if (transformBox === 'stroke-box' || transformBox === 'border-box') {
          const inset = Math.max(0, finite(style?.strokeWidth)) / 2;
          return { x: bounds.x - inset, y: bounds.y - inset, width: bounds.width + inset * 2, height: bounds.height + inset * 2 };
        }
        return bounds;
      }
    }
    return { x: 0, y: 0, width: Math.max(0, finite(viewport.width)), height: Math.max(0, finite(viewport.height)) };
  }

  function transformOriginPoint(element, style, context) {
    const box = transformReferenceBox(element, style, context);
    const source = String(style?.transformOrigin || '0 0').trim();
    const tokens = source.match(/(?:calc\([^)]*\)|[^\s]+)/gi) || ['0'];
    let xValue = tokens[0] || '0'; let yValue = tokens[1] || 'center';
    if (/^(?:top|bottom)$/i.test(xValue)) { yValue = xValue; xValue = 'center'; }
    const coordinate = (value, axis) => {
      const lower = String(value || '').toLowerCase(); const size = axis === 'x' ? box.width : box.height; const start = axis === 'x' ? box.x : box.y;
      if (lower === 'center') return start + size / 2;
      if (lower === (axis === 'x' ? 'left' : 'top')) return start;
      if (lower === (axis === 'x' ? 'right' : 'bottom')) return start + size;
      return start + parseLength(value, size, 0, finite(style?.fontSize, 16), context?.viewport);
    };
    return { x: coordinate(xValue, 'x'), y: coordinate(yValue, 'y') };
  }

  function elementMatrix(element, parentMatrix, context, style = null) {
    const transform = String(style?.transform && style.transform !== 'none' ? style.transform : attr(element, 'transform') || '').trim();
    try {
      const local = SvgPath.parseTransform(transform);
      if (!transform || String(style?.transformOrigin || '0 0').trim() === '0 0') return SvgPath.multiplyMatrices(parentMatrix, local);
      const origin = transformOriginPoint(element, style, context);
      const aroundOrigin = SvgPath.multiplyMatrices(
        [1, 0, 0, 1, origin.x, origin.y],
        SvgPath.multiplyMatrices(local, [1, 0, 0, 1, -origin.x, -origin.y])
      );
      return SvgPath.multiplyMatrices(parentMatrix, aroundOrigin);
    }
    catch (error) { context.warn(`${safeName(element, localName(element))}: ${error.message}`); return parentMatrix; }
  }

  function geometryValue(element, style, name) {
    if (style === DEFAULT_STYLE && attr(element, name) !== '') return attr(element, name);
    return style?.[name] == null ? attr(element, name) : String(style[name]);
  }

  function rectangleGeometry(element, viewport, style) {
    const fontSize = finite(style?.fontSize, 16);
    const x = parseLength(geometryValue(element, style, 'x'), viewport.width, 0, fontSize, viewport);
    const y = parseLength(geometryValue(element, style, 'y'), viewport.height, 0, fontSize, viewport);
    const width = parseLength(geometryValue(element, style, 'width'), viewport.width, 0, fontSize, viewport);
    const height = parseLength(geometryValue(element, style, 'height'), viewport.height, 0, fontSize, viewport);
    if (!(width > 0 && height > 0)) return null;
    let rx = parseLength(geometryValue(element, style, 'rx'), width, NaN, fontSize, viewport);
    let ry = parseLength(geometryValue(element, style, 'ry'), height, NaN, fontSize, viewport);
    if (!Number.isFinite(rx) && Number.isFinite(ry)) rx = ry;
    if (!Number.isFinite(ry) && Number.isFinite(rx)) ry = rx;
    rx = clamp(finite(rx), 0, width / 2); ry = clamp(finite(ry), 0, height / 2);
    return { x, y, width, height, rx, ry };
  }

  function rectanglePath(element, viewport, style) {
    const geometry = rectangleGeometry(element, viewport, style); if (!geometry) return [];
    const { x, y, width, height, rx, ry } = geometry;
    if (!rx && !ry) return [{ points: [[x, y], [x + width, y], [x + width, y + height], [x, y + height]], segments: [null, null, null, null], closed: true }];
    return SvgPath.parsePath(`M ${x + rx} ${y} H ${x + width - rx} A ${rx} ${ry} 0 0 1 ${x + width} ${y + ry} V ${y + height - ry} A ${rx} ${ry} 0 0 1 ${x + width - rx} ${y + height} H ${x + rx} A ${rx} ${ry} 0 0 1 ${x} ${y + height - ry} V ${y + ry} A ${rx} ${ry} 0 0 1 ${x + rx} ${y} Z`);
  }

  function ellipsePath(cx, cy, rx, ry) {
    if (!(rx > 0 && ry > 0)) return [];
    const kappa = 0.5522847498307936;
    return [{
      points: [[cx + rx, cy], [cx, cy + ry], [cx - rx, cy], [cx, cy - ry]],
      segments: [
        { c1: [cx + rx, cy + ry * kappa], c2: [cx + rx * kappa, cy + ry] },
        { c1: [cx - rx * kappa, cy + ry], c2: [cx - rx, cy + ry * kappa] },
        { c1: [cx - rx, cy - ry * kappa], c2: [cx - rx * kappa, cy - ry] },
        { c1: [cx + rx * kappa, cy - ry], c2: [cx + rx, cy - ry * kappa] }
      ],
      closed: true
    }];
  }

  function pointList(value) {
    const values = String(value || '').match(/[-+]?(?:\d*\.\d+|\d+\.?)(?:e[-+]?\d+)?/gi)?.map(Number) || [];
    const points = [];
    for (let index = 0; index + 1 < values.length; index += 2) points.push([values[index], values[index + 1]]);
    return points;
  }

  function primitivePaths(element, context, style = DEFAULT_STYLE) {
    const tag = localName(element); const { width, height } = context.viewport;
    const fontSize = finite(style.fontSize, 16); const viewport = context.viewport;
    if (tag === 'rect') return rectanglePath(element, viewport, style);
    if (tag === 'circle') {
      const normalizedDiagonal = Math.hypot(width, height) / Math.SQRT2;
      const radius = parseLength(geometryValue(element, style, 'r'), normalizedDiagonal, 0, fontSize, viewport);
      return ellipsePath(
        parseLength(geometryValue(element, style, 'cx'), width, 0, fontSize, viewport),
        parseLength(geometryValue(element, style, 'cy'), height, 0, fontSize, viewport),
        radius, radius
      );
    }
    if (tag === 'ellipse') {
      let rx = parseLength(geometryValue(element, style, 'rx'), width, NaN, fontSize, viewport);
      let ry = parseLength(geometryValue(element, style, 'ry'), height, NaN, fontSize, viewport);
      if (rx < 0) rx = NaN;
      if (ry < 0) ry = NaN;
      if (!Number.isFinite(rx) && Number.isFinite(ry)) rx = ry;
      if (!Number.isFinite(ry) && Number.isFinite(rx)) ry = rx;
      return ellipsePath(
        parseLength(geometryValue(element, style, 'cx'), width, 0, fontSize, viewport),
        parseLength(geometryValue(element, style, 'cy'), height, 0, fontSize, viewport), rx, ry
      );
    }
    if (tag === 'line') return [{
      points: [[parseLength(attr(element, 'x1'), width, 0), parseLength(attr(element, 'y1'), height, 0)], [parseLength(attr(element, 'x2'), width, 0), parseLength(attr(element, 'y2'), height, 0)]],
      segments: [null, null], closed: false
    }];
    if (tag === 'polyline' || tag === 'polygon') {
      const points = pointList(attr(element, 'points'));
      return points.length >= 2 ? [{ points, segments: points.map(() => null), closed: tag === 'polygon' }] : [];
    }
    if (tag === 'path') {
      let pathData = geometryValue(element, style, 'd').trim();
      if (/^none$/i.test(pathData)) return [];
      const cssPath = pathData.match(/^path\(\s*(["'])([\s\S]*)\1\s*\)$/i); if (cssPath) pathData = cssPath[2];
      try { return SvgPath.parsePath(pathData); }
      catch (error) { context.warn(`${safeName(element, 'Pfad')}: ${error.message}`); return []; }
    }
    return [];
  }

  function baseObject(type, name, context) {
    return {
      id: context.createId('object'), type, name, x: 0, y: 0, width: 1, height: 1,
      rotation: 0, opacity: 100, visible: true, locked: false, blendMode: 'normal', stroke: 'none', fill: 'none',
      strokeWidth: 1, strokeType: 'basic-soft-rounded', scalePercent: 100, flipX: false, flipY: false
    };
  }

  function matrixStrokeScale(matrix) {
    const [a, b, c, d] = matrix; return Math.sqrt(Math.max(1e-12, Math.abs(a * d - b * c)));
  }

  function paintUrl(value) {
    const match = String(value || '').trim().match(/^url\(\s*(?:(["'])([\s\S]*?)\1|([^)]*))\s*\)\s*(.*)$/i);
    if (!match) return null;
    return { url: String(match[2] ?? match[3] ?? '').trim(), fallback: String(match[4] || '').trim() };
  }

  function referenceId(value) {
    const url = paintUrl(value)?.url || '';
    const encoded = url.match(/^#([^\s]+)$/)?.[1] || '';
    try { return decodeURIComponent(encoded); } catch { return encoded; }
  }

  function localHrefId(value) {
    const href = String(value || '').trim(); if (!href.startsWith('#')) return '';
    try { return decodeURIComponent(href.slice(1)); } catch { return href.slice(1); }
  }

  function paintFallback(value, currentColor = '#000000') {
    const fallback = paintUrl(value)?.fallback || '';
    return fallback ? normalizeColor(fallback, currentColor) : 'none';
  }

  function filterNumberList(value, fallback = [0]) {
    const values = String(value ?? '').trim().match(/[-+]?(?:\d*\.\d+|\d+\.?)(?:e[-+]?\d+)?/gi)?.map(Number).filter(Number.isFinite) || [];
    return values.length ? values : fallback;
  }

  function filterPlan(definition, styleContext = null) {
    const filterId = attr(definition, 'id'); const diagnostics = [];
    const primitiveElements = childrenOf(definition).filter((element) => localName(element).startsWith('fe'));
    let previous = 'SourceGraphic';
    const primitives = primitiveElements.map((element, index) => {
      const tag = localName(element); const result = attr(element, 'result') || `__filter_${index + 1}`;
      const input = attr(element, 'in') || (tag === 'feflood' ? '' : previous);
      const entry = {
        element, tag, index, result, input, input2: attr(element, 'in2'),
        mergeInputs: tag === 'femerge' ? childrenOf(element).filter((child) => localName(child) === 'femergenode').map((child) => attr(child, 'in') || previous) : []
      };
      previous = result; return entry;
    });
    const requestedTextAntialias = String(attr(definition, 'data-text-antialias-filter') || '').trim().toLowerCase();
    let textAntialias = '';
    if (['crisp', 'none'].includes(requestedTextAntialias) && primitives.length === 1 && primitives[0].tag === 'fecomponenttransfer') {
      const alphaFunction = childrenOf(primitives[0].element).find((element) => localName(element) === 'fefunca');
      const expectedMarker = requestedTextAntialias === 'none' ? 'data-text-antialias-threshold' : 'data-text-antialias-contrast';
      if (alphaFunction && attr(alphaFunction, expectedMarker) !== '') textAntialias = requestedTextAntialias;
    } else if (requestedTextAntialias === 'smooth' && primitives.length === 1 && primitives[0].tag === 'fegaussianblur'
      && attr(primitives[0].element, 'data-text-antialias-softness') !== '') textAntialias = requestedTextAntialias;
    if (textAntialias) return { effects: [], diagnostics, primitives, textAntialias, image: null, nativeRequired: false };
    if (primitives.length === 1 && primitives[0].tag === 'feimage') {
      return { effects: [], diagnostics, primitives, textAntialias: '', image: primitives[0], nativeRequired: false };
    }
    const byResult = new Map(primitives.map((primitive) => [primitive.result, primitive]));
    const consumed = new Set(); const effects = [];
    const builtinInputs = new Set(['SourceGraphic', 'SourceAlpha', 'BackgroundImage', 'BackgroundAlpha', 'FillPaint', 'StrokePaint']);
    const producer = (name) => byResult.get(name) || null;
    const floodAppearance = (primitive) => {
      const style = resolveStyle(primitive.element, DEFAULT_STYLE, styleContext);
      return { color: normalizeColor(style.floodColor, '#000000'), opacity: clamp(finite(style.floodOpacity, 1), 0, 1) };
    };
    const blurValues = (primitive) => {
      const values = filterNumberList(attr(primitive?.element, 'stdDeviation'), [0]);
      return [Math.max(0, values[0] || 0), Math.max(0, values[1] == null ? values[0] || 0 : values[1])];
    };

    primitives.filter((primitive) => primitive.tag === 'fedropshadow').forEach((primitive) => {
      const values = blurValues(primitive); const appearance = floodAppearance(primitive);
      effects.push({
        kind: 'drop-shadow', index: primitive.index, stdDeviation: values,
        dx: finite(attr(primitive.element, 'dx')), dy: finite(attr(primitive.element, 'dy')), ...appearance,
        input: builtinInputs.has(primitive.input) ? primitive.input : ''
      });
      consumed.add(primitive.index);
    });

    primitives.filter((primitive) => primitive.tag === 'femerge').forEach((merge) => {
      const sourceIncluded = merge.mergeInputs.includes('SourceGraphic');
      const shadowNames = merge.mergeInputs.filter((name) => name && name !== 'SourceGraphic');
      if (!sourceIncluded || !shadowNames.length) return;
      let recognized = 0;
      shadowNames.forEach((shadowName, mergeOrder) => {
        const shadow = producer(shadowName); if (!shadow) return;
        let flood = null; let shape = shadow;
        if (shadow.tag === 'fecomposite' && String(attr(shadow.element, 'operator') || 'over').toLowerCase() === 'in') {
          const one = producer(shadow.input); const two = producer(shadow.input2);
          flood = one?.tag === 'feflood' ? one : two?.tag === 'feflood' ? two : null;
          shape = flood === one ? two : flood === two ? one : null;
        }
        if (!shape) return;
        const offset = shape.tag === 'feoffset' ? shape : null;
        const blur = producer(offset ? offset.input : shape.result);
        const gaussian = shape.tag === 'fegaussianblur' ? shape : blur?.tag === 'fegaussianblur' ? blur : null;
        if (!gaussian && !offset) return;
        const dx = offset ? finite(attr(offset.element, 'dx')) : 0; const dy = offset ? finite(attr(offset.element, 'dy')) : 0;
        const appearance = flood ? floodAppearance(flood) : { color: '#000000', opacity: 1 };
        const sourceInput = gaussian?.input || offset?.input || shape.input;
        effects.push({
          kind: Math.abs(dx) < 1e-8 && Math.abs(dy) < 1e-8 ? 'outer-glow' : 'drop-shadow',
          index: merge.index + mergeOrder / 1000, stdDeviation: gaussian ? blurValues(gaussian) : [0, 0], dx, dy, ...appearance,
          input: builtinInputs.has(sourceInput) ? sourceInput : 'SourceGraphic'
        });
        recognized += 1;
        [shadow, shape, offset, gaussian, flood].filter(Boolean).forEach((primitive) => consumed.add(primitive.index));
        if (!flood) diagnostics.push({
          code: 'SVG_FILTER_SHADOW_COLOR_APPROXIMATED', status: 'approximated', element: 'filter', id: filterId,
          message: `Filter #${filterId || '?'} verwendet einen farbigen Bildschatten; PYRO übernimmt ihn als schwarzen Schlagschatten`
        });
      });
      if (recognized) consumed.add(merge.index);
    });

    const remaining = primitives.filter((primitive) => !consumed.has(primitive.index));
    const blendModes = new Set(['normal', 'multiply', 'screen', 'darken', 'lighten', 'overlay', 'color-dodge', 'color-burn', 'hard-light', 'soft-light', 'difference', 'exclusion', 'hue', 'saturation', 'color', 'luminosity']);
    remaining.filter((primitive) => primitive.tag === 'feblend').forEach((blend) => {
      const gaussian = producer(blend.input); if (gaussian?.tag !== 'fegaussianblur' || consumed.has(gaussian.index)) return;
      const input2 = blend.input2 || 'SourceGraphic'; if (!builtinInputs.has(input2)) return;
      const modeValue = String(attr(blend.element, 'mode') || attr(blend.element, 'blend') || 'normal').toLowerCase();
      effects.push({
        kind: 'gaussian-blur', index: gaussian.index, stdDeviation: blurValues(gaussian),
        input: builtinInputs.has(gaussian.input) ? gaussian.input : 'SourceGraphic',
        blendMode: blendModes.has(modeValue) ? modeValue : 'normal', blendInput2: input2
      });
      consumed.add(gaussian.index); consumed.add(blend.index);
    });
    const remainingBlurs = primitives.filter((primitive) => !consumed.has(primitive.index));
    if (remainingBlurs.length && remainingBlurs.every((primitive) => primitive.tag === 'fegaussianblur')) remainingBlurs.forEach((primitive) => {
      const builtinInput = builtinInputs.has(primitive.input) ? primitive.input : '';
      effects.push({ kind: 'gaussian-blur', index: primitive.index, stdDeviation: blurValues(primitive), input: builtinInput }); consumed.add(primitive.index);
    });
    const unconsumed = primitives.filter((primitive) => !consumed.has(primitive.index));
    if (unconsumed.length) diagnostics.push({
      code: effects.length ? 'SVG_FILTER_CHAIN_PARTIAL' : 'SVG_FILTER_CHAIN_UNSUPPORTED', status: effects.length ? 'approximated' : 'ignored',
      element: 'filter', id: filterId,
      message: `Filter #${filterId || '?'} enthält noch nicht abbildbare Primitive: ${[...new Set(unconsumed.map((primitive) => `<${primitive.tag}>`))].join(', ')}`
    });
    return { effects: effects.sort((left, right) => left.index - right.index), diagnostics, primitives, textAntialias: '', image: null, nativeRequired: unconsumed.length > 0 };
  }

  function gradientCoordinate(value, fallback, relative, objectBoundingBox) {
    const source = String(value ?? '').trim();
    if (!source) return objectBoundingBox ? fallback : fallback * relative;
    if (source.endsWith('%')) {
      const fraction = finite(source.slice(0, -1), fallback * 100) / 100;
      return objectBoundingBox ? fraction : fraction * relative;
    }
    return objectBoundingBox ? parseLength(source, 1, fallback) : parseLength(source, relative, fallback * relative);
  }

  function gradientStops(element, inherited, context, paintOpacity = 1) {
    const ownStops = childrenOf(element).filter((child) => localName(child) === 'stop');
    const stops = ownStops.length ? ownStops : inherited; let previousOffset = 0;
    return (stops || []).slice(0, 64).map((stop) => {
      const stopStyle = resolveDocumentStyle(stop, context); const rawOffset = attr(stop, 'offset') || '0';
      const parsedOffset = clamp(rawOffset.trim().endsWith('%') ? finite(rawOffset.trim().slice(0, -1)) : finite(rawOffset) * 100, 0, 100);
      const offset = Math.max(previousOffset, parsedOffset); previousOffset = offset;
      return { id: context.createId('stop'), offset: round(offset), color: normalizeColor(stopStyle.stopColor, stopStyle.color), opacity: round(stopStyle.stopOpacity * paintOpacity * 100) };
    }).filter(Boolean);
  }

  function mergedGradient(element, context, visited = new Set()) {
    if (!element) return null;
    const elementId = attr(element, 'id');
    if (visited.has(element)) {
      context.diagnose({
        code: 'SVG_GRADIENT_REFERENCE_CYCLE', severity: 'error', status: 'blocked', element: localName(element), attribute: 'href', id: elementId,
        message: `Zyklische Verlaufsreferenz #${elementId || '?'} wurde sicher abgebrochen`
      });
      return null;
    }
    visited.add(element);
    const href = hrefOf(element); let inherited = null; let invalidInheritedReference = false;
    if (href) {
      const targetId = href.startsWith('#') ? href.slice(1) : '';
      const target = targetId ? context.definitions.get(targetId) : null;
      if (!target) context.diagnose({
        code: 'SVG_GRADIENT_REFERENCE_MISSING', severity: 'error', status: 'missing', element: localName(element), attribute: 'href', id: targetId || elementId,
        message: `Verlaufsreferenz ${href || '(leer)'} konnte nicht aufgelöst werden`
      });
      else if (!['lineargradient', 'radialgradient'].includes(localName(target))) context.diagnose({
        code: 'SVG_GRADIENT_REFERENCE_INVALID', severity: 'error', status: 'blocked', element: localName(element), attribute: 'href', id: targetId,
        message: `Verlaufsreferenz #${targetId} zeigt nicht auf einen Verlauf`
      });
      else if (paintServerDefinitionContextValid(target)) inherited = mergedGradient(target, context, visited);
      else invalidInheritedReference = true;
    }
    const attributes = { ...(inherited?.attributes || {}) };
    Array.from(element.attributes || []).forEach((attribute) => { if (!['id', 'href', 'xlink:href'].includes(attribute.name)) attributes[attribute.name] = attribute.value; });
    const ownStops = childrenOf(element).filter((child) => localName(child) === 'stop');
    return { element, tag: localName(element), attributes, stopElements: ownStops.length ? ownStops : inherited?.stopElements || [], invalidInheritedReference };
  }

  function mergedPattern(element, context, visited = new Set()) {
    if (!element || localName(element) !== 'pattern') return null;
    const elementId = attr(element, 'id');
    if (visited.has(element)) {
      context.diagnose({
        code: 'SVG_PATTERN_REFERENCE_CYCLE', severity: 'error', status: 'blocked', element: 'pattern', attribute: 'href', id: elementId,
        message: `Zyklische Musterreferenz #${elementId || '?'} wurde sicher abgebrochen`
      });
      return null;
    }
    visited.add(element);
    const href = hrefOf(element); let inherited = null; let invalidInheritedReference = false;
    if (href) {
      const targetId = href.startsWith('#') ? href.slice(1) : '';
      const target = targetId ? context.definitions.get(targetId) : null;
      if (!target) context.diagnose({
        code: 'SVG_PATTERN_REFERENCE_MISSING', severity: 'error', status: 'missing', element: 'pattern', attribute: 'href', id: targetId || elementId,
        message: `Musterreferenz ${href || '(leer)'} konnte nicht aufgelöst werden`
      });
      else if (localName(target) !== 'pattern') context.diagnose({
        code: 'SVG_PATTERN_REFERENCE_INVALID', severity: 'error', status: 'blocked', element: 'pattern', attribute: 'href', id: targetId,
        message: `Musterreferenz #${targetId} zeigt nicht auf ein <pattern>`
      });
      else if (paintServerDefinitionContextValid(target)) inherited = mergedPattern(target, context, visited);
      else invalidInheritedReference = true;
    }
    const attributes = { ...(inherited?.attributes || {}) };
    Array.from(element.attributes || []).forEach((attribute) => { if (!['id', 'href', 'xlink:href'].includes(attribute.name)) attributes[attribute.name] = attribute.value; });
    const ownChildren = childrenOf(element);
    return { element, attributes, contentElements: ownChildren.length ? ownChildren : inherited?.contentElements || [], invalidInheritedReference };
  }

  function paintServerDefinitionContextValid(element) {
    let current = parentElementOf(element);
    while (current) {
      const tag = localName(current);
      if (tag === 'svg' || tag === 'defs') return true;
      if (tag === 'text' || tag === 'tspan' || tag === 'textpath' || GEOMETRY_TAGS.has(tag)) return false;
      current = parentElementOf(current);
    }
    return true;
  }

  const SAFE_PATTERN_TAGS = new Set([
    'g', 'defs', 'use', 'symbol', 'svg', 'rect', 'circle', 'ellipse', 'line', 'polyline', 'polygon', 'path', 'image', 'text', 'tspan', 'textpath',
    'lineargradient', 'radialgradient', 'stop', 'clippath', 'mask', 'pattern', 'marker', 'filter', ...SUPPORTED_FILTER_PRIMITIVES
  ]);
  const CANONICAL_SVG_TAGS = Object.freeze({
    lineargradient: 'linearGradient', radialgradient: 'radialGradient', clippath: 'clipPath', textpath: 'textPath',
    fegaussianblur: 'feGaussianBlur', feoffset: 'feOffset', feflood: 'feFlood', fecomposite: 'feComposite',
    femerge: 'feMerge', femergenode: 'feMergeNode', fedropshadow: 'feDropShadow', feblend: 'feBlend',
    fecomponenttransfer: 'feComponentTransfer', fefunca: 'feFuncA', fefuncb: 'feFuncB', fefuncg: 'feFuncG', fefuncr: 'feFuncR',
    fecolormatrix: 'feColorMatrix', feconvolvematrix: 'feConvolveMatrix', fediffuselighting: 'feDiffuseLighting',
    fedisplacementmap: 'feDisplacementMap', fedistantlight: 'feDistantLight', feimage: 'feImage', femorphology: 'feMorphology',
    fepointlight: 'fePointLight', fespecularlighting: 'feSpecularLighting', fespotlight: 'feSpotLight', fetile: 'feTile', feturbulence: 'feTurbulence'
  });
  const escapeXml = (value) => String(value ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');

  function safePatternAttribute(attribute, context, owner) {
    const name = String(attribute?.name || ''); const lower = name.toLowerCase(); let value = String(attribute?.value ?? '');
    if (!name || lower.startsWith('on') || lower === 'src' || lower === 'xmlns' || lower.startsWith('xmlns:')) return null;
    if (lower === 'href' || lower === 'xlink:href') {
      if (/^data:image\/svg\+xml/i.test(value)) {
        context.diagnose({
          code: 'SVG_NESTED_IMAGE_BLOCKED', severity: 'error', status: 'blocked', element: localName(owner), attribute: lower,
          message: 'Ein verschachteltes SVG-Bild innerhalb eines SVG-Musters wurde nicht übernommen'
        });
        return null;
      }
      if (!value.startsWith('#') && !/^data:image\/(?:png|jpe?g|webp|gif|svg\+xml)(?:;[^,]*)?,/i.test(value)) {
        context.diagnose({
          code: 'SVG_PATTERN_RESOURCE_BLOCKED', severity: 'error', status: 'blocked', element: localName(owner), attribute: lower,
          message: 'Eine externe Ressource innerhalb eines SVG-Musters wurde nicht geladen'
        });
        return null;
      }
    }
    if (lower === 'style') {
      const declarations = parseCssDeclarations(value).filter((declaration) => !/(?:javascript:|expression\s*\(|url\(\s*["']?(?!#))/i.test(declaration.value));
      value = declarations.map((declaration) => `${declaration.name}:${declaration.value}${declaration.important ? ' !important' : ''}`).join(';');
      if (!value) return null;
    } else if (/(?:javascript:|expression\s*\()/i.test(value) || /url\(\s*["']?(?!#)/i.test(value)) return null;
    return `${escapeXml(name)}="${escapeXml(value)}"`;
  }

  function serializeSafePatternElement(element, context, referencedIds = new Set()) {
    const tag = localName(element); if (element?.namespaceURI && element.namespaceURI !== SVG_NS || !SAFE_PATTERN_TAGS.has(tag) || FORBIDDEN_TAGS.has(tag)) return '';
    const attributes = Array.from(element.attributes || []).map((attribute) => {
      const value = String(attribute?.value ?? '');
      for (const match of value.matchAll(/(?:url\(\s*["']?#|^#)([-_a-z][\w:.-]*)/gi)) referencedIds.add(match[1]);
      return safePatternAttribute(attribute, context, element);
    }).filter(Boolean);
    const children = childrenOf(element).map((child) => serializeSafePatternElement(child, context, referencedIds)).join('');
    const text = children ? '' : ['text', 'tspan', 'textpath'].includes(tag) ? escapeXml(element.textContent || '') : '';
    const name = CANONICAL_SVG_TAGS[tag] || tag;
    return `<${name}${attributes.length ? ` ${attributes.join(' ')}` : ''}>${children || text}</${name}>`;
  }

  function nativeFilterNode(element, context, depth = 0) {
    const tag = localName(element); if (element?.namespaceURI && element.namespaceURI !== SVG_NS || !NATIVE_FILTER_TAGS.has(tag) || depth > 16) return null;
    const attributes = {};
    Array.from(element.attributes || []).slice(0, 128).forEach((attribute) => {
      const name = String(attribute?.name || ''); const lower = name.toLowerCase(); let value = String(attribute?.value ?? '').slice(0, 262144);
      if (!/^[a-z_][\w:.-]*$/i.test(name) || lower.startsWith('on') || lower.startsWith('xmlns') || lower === 'id') return;
      if (lower === 'href' || lower === 'xlink:href') {
        if (/^data:image\/svg\+xml/i.test(value)) {
          context.diagnose({
            code: 'SVG_NESTED_IMAGE_BLOCKED', severity: 'error', status: 'blocked', element: tag, attribute: lower,
            message: 'Ein verschachteltes SVG-Bild innerhalb eines nativen Filters wurde nicht übernommen'
          });
          return;
        }
        if (!value.startsWith('#') && !/^data:image\/(?:png|jpe?g|webp|gif|svg\+xml)(?:;[^,]*)?,/i.test(value)) {
          context.diagnose({
            code: 'SVG_FILTER_RESOURCE_BLOCKED', severity: 'error', status: 'blocked', element: tag, attribute: lower,
            message: 'Eine externe Ressource in einem nativen SVG-Filter wurde nicht übernommen'
          });
          return;
        }
      } else if (lower === 'style') {
        const declarations = parseCssDeclarations(value).filter((declaration) => !/(?:javascript:|expression\s*\(|url\(\s*["']?(?!#))/i.test(declaration.value));
        value = declarations.map((declaration) => `${declaration.name}:${declaration.value}${declaration.important ? ' !important' : ''}`).join(';');
        if (!value) return;
      } else if (/(?:javascript:|expression\s*\(|url\(\s*["']?(?!#))/i.test(value)) return;
      attributes[lower === 'xlink:href' ? 'href' : name] = value;
    });
    const children = childrenOf(element).map((child) => nativeFilterNode(child, context, depth + 1)).filter(Boolean);
    return { tag: CANONICAL_SVG_TAGS[tag] || tag, attributes, children };
  }

  function patternDataHref(definition, viewBox, context) {
    const references = new Set(); const content = definition.contentElements.map((element) => serializeSafePatternElement(element, context, references)).join('');
    const inherited = resolveDocumentStyle(definition.element, context);
    const inheritedAttributes = {
      fill: inherited.fill, stroke: inherited.stroke, color: inherited.color,
      'stroke-width': inherited.strokeWidth, 'stroke-linecap': inherited.lineCap, 'stroke-linejoin': inherited.lineJoin,
      'stroke-miterlimit': inherited.miterLimit, 'stroke-dasharray': inherited.dashArray?.length ? inherited.dashArray.join(' ') : undefined,
      'stroke-dashoffset': inherited.dashOffset || undefined, 'fill-rule': inherited.fillRule,
      'font-family': inherited.fontFamily, 'font-size': inherited.fontSize, 'font-style': inherited.fontStyle, 'font-weight': inherited.fontWeight,
      'text-anchor': inherited.textAnchor, visibility: inherited.visibility
    };
    const wrapperAttributes = Object.entries(inheritedAttributes)
      .filter(([, value]) => value !== undefined && value !== '')
      .map(([name, value]) => `${name}="${escapeXml(value)}"`).join(' ');
    const serializedDefinitions = []; const handled = new Set(); const queue = [...references];
    while (queue.length && handled.size < 256) {
      const id = queue.shift(); if (!id || handled.has(id)) continue; handled.add(id);
      const target = context.definitions.get(id); if (!target || target === definition.element || !SAFE_PATTERN_TAGS.has(localName(target))) continue;
      const nested = new Set(); const serialized = serializeSafePatternElement(target, context, nested);
      if (serialized) serializedDefinitions.push(serialized);
      nested.forEach((nestedId) => { if (!handled.has(nestedId)) queue.push(nestedId); });
    }
    const svg = `<svg xmlns="${SVG_NS}" xmlns:xlink="${XLINK_NS}" width="256" height="256" viewBox="${viewBox.map((value) => round(value, 8)).join(' ')}" preserveAspectRatio="none">${serializedDefinitions.length ? `<defs>${serializedDefinitions.join('')}</defs>` : ''}<g${wrapperAttributes ? ` ${wrapperAttributes}` : ''}>${content}</g></svg>`;
    return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
  }

  function patternPaint(paintValue, object, elementMatrixValue, sourceBounds, paintOpacity, context) {
    const id = referenceId(paintValue); const target = context.definitions.get(id);
    if (!target || localName(target) !== 'pattern') return null;
    const definition = mergedPattern(target, context);
    // An empty pattern cannot supply a tile.  SVG therefore continues with the
    // fallback paint after url(...), unlike an empty gradient which resolves to
    // a paint server that simply produces no paint.
    if (!definition?.contentElements?.length) return null;
    const attributes = definition.attributes;
    const objectBoundingBox = String(attributes.patternUnits || 'objectBoundingBox').toLowerCase() !== 'userspaceonuse';
    const contentObjectBoundingBox = String(attributes.patternContentUnits || 'userSpaceOnUse').toLowerCase() === 'objectboundingbox';
    const relative = (value, fallback, axis) => gradientCoordinate(
      value, fallback, axis === 'x' ? context.viewport.width : context.viewport.height, objectBoundingBox
    );
    const x = relative(attributes.x, 0, 'x'); const y = relative(attributes.y, 0, 'y');
    const width = Math.max(.00000001, relative(attributes.width, 0, 'x'));
    const height = Math.max(.00000001, relative(attributes.height, 0, 'y'));
    if (!(width > .00000001 && height > .00000001)) {
      context.diagnose({
        code: 'SVG_PATTERN_SIZE_INVALID', status: 'ignored', element: 'pattern', id,
        message: `Muster #${id || '?'} besitzt keine positive Kachelgröße`
      });
      return null;
    }
    let patternTransform = SvgPath.identityMatrix();
    try { patternTransform = SvgPath.parseTransform(attributes.patternTransform || ''); }
    catch {
      context.diagnose({ code: 'SVG_PATTERN_TRANSFORM_INVALID', status: 'approximated', element: 'pattern', id, message: `patternTransform von #${id || '?'} wurde ignoriert` });
    }
    if (Math.abs(patternTransform[0] * patternTransform[3] - patternTransform[1] * patternTransform[2]) < 1e-12) {
      context.diagnose({
        code: 'SVG_PATTERN_NONINVERTIBLE_FALLBACK', severity: 'info', status: 'supported', element: 'pattern', id,
        message: `Nicht invertierbares Muster #${id || '?'} verwendet korrekt die angegebene Fallback-Farbe`
      });
      return null;
    }
    const unitsMatrix = objectBoundingBox
      ? [Math.max(.0001, sourceBounds.width), 0, 0, Math.max(.0001, sourceBounds.height), sourceBounds.x, sourceBounds.y]
      : SvgPath.identityMatrix();
    const localMatrix = translatedMatrix(SvgPath.identityMatrix(), -object.x, -object.y);
    const matrix = SvgPath.multiplyMatrices(localMatrix, SvgPath.multiplyMatrices(elementMatrixValue, SvgPath.multiplyMatrices(unitsMatrix, patternTransform))).map((value) => round(value, 8));
    const parsedViewBox = parseViewBox(attributes.viewBox); const tileViewBox = parsedViewBox
      ? [parsedViewBox.x, parsedViewBox.y, parsedViewBox.width, parsedViewBox.height]
      : [0, 0, width, height];
    const href = patternDataHref(definition, tileViewBox, context);
    context.diagnose({
      code: 'SVG_PATTERN_IMPORTED', severity: 'info', status: 'supported', element: 'pattern', attribute: 'fill', id,
      message: `SVG-Muster #${id} wurde als verlustfreie Vektorkachel importiert`
    });
    return {
      type: 'pattern', x1: 0, y1: 50, x2: 100, y2: 50, cx: 50, cy: 50, radius: 50, angle: 0,
      scale: 100, rotation: 0, offsetX: 0, offsetY: 0, href, imageWidth: Math.max(1, Math.round(width)), imageHeight: Math.max(1, Math.round(height)),
      svgPattern: {
        x, y, width, height, viewBox: tileViewBox, matrix, patternUnits: objectBoundingBox ? 'objectBoundingBox' : 'userSpaceOnUse',
        patternContentUnits: contentObjectBoundingBox ? 'objectBoundingBox' : 'userSpaceOnUse',
        preserveAspectRatio: String(attributes.preserveAspectRatio || 'xMidYMid meet'), opacity: round(paintOpacity * 100)
      }
    };
  }

  function gradientPaint(paintValue, object, elementMatrixValue, sourceBounds, paintOpacity, context) {
    const id = referenceId(paintValue); const target = context.definitions.get(id);
    if (!target) {
      context.diagnose({
        code: 'SVG_GRADIENT_REFERENCE_MISSING', severity: 'error', status: 'missing', element: 'gradient', attribute: 'fill', id,
        message: `Verlauf #${id || '?'} konnte nicht aufgelöst werden`
      });
      return null;
    }
    if (!['lineargradient', 'radialgradient'].includes(localName(target))) return null;
    const definition = mergedGradient(target, context);
    if (!definition || !['lineargradient', 'radialgradient'].includes(definition.tag)) return null;
    const attributes = definition.attributes;
    const objectBoundingBox = String(attributes.gradientUnits || 'objectBoundingBox').toLowerCase() !== 'userspaceonuse';
    let gradientTransform = SvgPath.identityMatrix();
    try { gradientTransform = SvgPath.parseTransform(attributes.gradientTransform || ''); }
    catch {
      context.diagnose({
        code: 'SVG_GRADIENT_TRANSFORM_INVALID', status: 'approximated', element: definition.tag, attribute: 'gradienttransform', id,
        message: `Verlauf #${id}: gradientTransform wurde ignoriert`
      });
    }
    if (Math.abs(gradientTransform[0] * gradientTransform[3] - gradientTransform[1] * gradientTransform[2]) < 1e-12) {
      context.diagnose({
        code: 'SVG_GRADIENT_NONINVERTIBLE_FALLBACK', severity: 'info', status: 'supported', element: definition.tag, id,
        message: `Nicht invertierbarer Verlauf #${id || '?'} verwendet korrekt die angegebene Fallback-Farbe`
      });
      return null;
    }
    const unitsMatrix = objectBoundingBox
      ? [Math.max(.0001, sourceBounds.width), 0, 0, Math.max(.0001, sourceBounds.height), sourceBounds.x, sourceBounds.y]
      : SvgPath.identityMatrix();
    const localMatrix = translatedMatrix(SvgPath.identityMatrix(), -object.x, -object.y);
    const gradientMatrix = SvgPath.multiplyMatrices(
      localMatrix,
      SvgPath.multiplyMatrices(elementMatrixValue, SvgPath.multiplyMatrices(unitsMatrix, gradientTransform))
    ).map((value) => round(value, 8));
    const coordinate = (value, fallback, axis) => gradientCoordinate(
      value,
      fallback,
      axis === 'x' ? context.viewport.width : axis === 'y' ? context.viewport.height : Math.hypot(context.viewport.width, context.viewport.height) / Math.SQRT2,
      objectBoundingBox
    );
    const stops = gradientStops(definition.element, definition.stopElements, context, paintOpacity);
    if (!stops.length) {
      context.diagnose({
        code: 'SVG_GRADIENT_EMPTY', status: 'ignored', element: definition.tag, id,
        message: `Verlauf #${id || '?'} enthält keine Stopps und erzeugt deshalb keine Farbe`
      });
      return definition.invalidInheritedReference ? { type: 'svg-none' } : null;
    }
    if (stops.length < 2) stops.push({ id: context.createId('stop'), offset: 100, color: stops[0]?.color || '#ffffff', opacity: stops[0]?.opacity ?? round(paintOpacity * 100) });
    const percentX = (value) => round(value / Math.max(.0001, object.width) * 100);
    const percentY = (value) => round(value / Math.max(.0001, object.height) * 100);
    const map = (x, y) => SvgPath.applyMatrix(gradientMatrix, [x, y]);
    const common = {
      x1: 0, y1: 50, x2: 100, y2: 50, cx: 50, cy: 50, radius: 50, angle: 0,
      spreadMethod: ['pad', 'reflect', 'repeat'].includes(String(attributes.spreadMethod || '').toLowerCase()) ? String(attributes.spreadMethod).toLowerCase() : 'pad',
      colorInterpolation: String(resolveStyle(definition.element, DEFAULT_STYLE, context).colorInterpolation || 'sRGB').toLowerCase() === 'linearrgb' ? 'linearRGB' : 'sRGB',
      gradientMatrix, scale: 100, rotation: 0, offsetX: 0, offsetY: 0, href: '', imageWidth: 1, imageHeight: 1, stops
    };
    if (definition.tag === 'lineargradient') {
      const gradientCoordinates = {
        x1: coordinate(attributes.x1, 0, 'x'), y1: coordinate(attributes.y1, 0, 'y'),
        x2: coordinate(attributes.x2, 1, 'x'), y2: coordinate(attributes.y2, 0, 'y')
      };
      const one = map(gradientCoordinates.x1, gradientCoordinates.y1); const two = map(gradientCoordinates.x2, gradientCoordinates.y2);
      return { type: 'linear', ...common, gradientCoordinates, x1: percentX(one.x), y1: percentY(one.y), x2: percentX(two.x), y2: percentY(two.y) };
    }
    const cx = coordinate(attributes.cx, .5, 'x'); const cy = coordinate(attributes.cy, .5, 'y');
    const gradientCoordinates = {
      cx, cy,
      fx: coordinate(attributes.fx === undefined ? attributes.cx : attributes.fx, .5, 'x'),
      fy: coordinate(attributes.fy === undefined ? attributes.cy : attributes.fy, .5, 'y'),
      r: Math.max(.0001, coordinate(attributes.r, .5, 'r')),
      fr: Math.max(0, coordinate(attributes.fr, 0, 'r'))
    };
    const center = map(cx, cy); const radiusPoint = map(cx + gradientCoordinates.r, cy);
    const radius = Math.hypot(radiusPoint.x - center.x, radiusPoint.y - center.y) / Math.max(.0001, Math.min(object.width, object.height)) * 100;
    return { type: 'radial', ...common, gradientCoordinates, cx: percentX(center.x), cy: percentY(center.y), radius: round(Math.max(.1, radius)) };
  }

  function applyAppearance(object, style, matrix, sourceBounds, context, text = false) {
    const strokeScale = String(style.vectorEffect || '').trim().toLowerCase() === 'non-scaling-stroke' ? 1 : matrixStrokeScale(matrix);
    const fillUrl = paintUrl(style.fill); const strokeUrl = paintUrl(style.stroke);
    const fillReference = referenceId(style.fill); const strokeReference = referenceId(style.stroke);
    const fillPaint = fillReference ? gradientPaint(style.fill, object, matrix, sourceBounds, 1, context)
      || patternPaint(style.fill, object, matrix, sourceBounds, 1, context) : null;
    const strokePaint = strokeReference ? gradientPaint(style.stroke, object, matrix, sourceBounds, 1, context)
      || patternPaint(style.stroke, object, matrix, sourceBounds, 1, context) : null;
    const fillProducesNoPaint = fillPaint?.type === 'svg-none'; const strokeProducesNoPaint = strokePaint?.type === 'svg-none';
    object.fill = fillProducesNoPaint ? 'none' : fillPaint ? fillPaint.stops?.[0]?.color || '#000000' : fillUrl ? paintFallback(style.fill, style.color) : normalizeColor(style.fill, style.color);
    if (fillPaint && !fillProducesNoPaint) object.fillPaint = fillPaint;
    if (style.fillContextPaint && fillReference) {
      object.svgContextFillPaint = style.fillContextPaint;
      object.svgContextFillOpacity = style.fillOpacity;
    }
    const stroke = strokeProducesNoPaint ? 'none' : strokePaint ? strokePaint.stops?.[0]?.color || '#000000' : strokeUrl ? paintFallback(style.stroke, style.color) : normalizeColor(style.stroke, style.color);
    if (strokePaint && !strokeProducesNoPaint) object.strokePaint = strokePaint;
    if (style.strokeContextPaint && strokeReference) {
      object.svgContextStrokePaint = style.strokeContextPaint;
      object.svgContextStrokeOpacity = style.strokeOpacity;
    }
    object.opacity = round(clamp(style.opacity, 0, 1) * 100);
    object.svgFillOpacity = round(clamp(style.fillOpacity, 0, 1), 8);
    object.svgStrokeOpacity = round(clamp(style.strokeOpacity, 0, 1), 8);
    object.blendMode = normalizeBlendMode(style.blendMode);
    if (String(style.isolation || '').toLowerCase() === 'isolate') object.svgIsolation = 'isolate';
    const hasFill = object.fill !== 'none'; const hasStroke = stroke !== 'none' && style.strokeWidth > 0;
    if (text) {
      object.textStroke = stroke; object.textStrokeWidth = round(Math.max(0, style.strokeWidth * strokeScale)); object.stroke = 'none'; object.strokeWidth = 0;
    } else {
      object.stroke = stroke; object.strokeWidth = round(Math.max(0, style.strokeWidth * strokeScale));
    }
    object.strokeLineCap = ['butt', 'round', 'square'].includes(style.lineCap) ? style.lineCap : 'butt';
    object.strokeLineJoin = ['miter', 'round', 'bevel'].includes(style.lineJoin) ? style.lineJoin : 'miter';
    object.strokeMiterLimit = round(Math.max(1, style.miterLimit));
    object.strokeDashArray = (style.dashArray || []).map((value) => round(value * strokeScale));
    object.strokeDashOffset = round(style.dashOffset * strokeScale);
    object.strokePattern = object.strokeDashArray.length ? 'custom' : 'solid';
    object.svgShapeRendering = ['auto', 'optimizespeed', 'crispedges', 'geometricprecision'].includes(String(style.shapeRendering || '').toLowerCase())
      ? String(style.shapeRendering).toLowerCase() : 'auto';
    const paintOrder = String(style.paintOrder || 'normal').trim().toLowerCase();
    if (paintOrder !== 'normal') {
      const requested = paintOrder.split(/\s+/).filter((value, index, values) => ['fill', 'stroke', 'markers'].includes(value) && values.indexOf(value) === index);
      object.svgPaintOrder = [...requested, ...['fill', 'stroke', 'markers'].filter((value) => !requested.includes(value))].join(' ');
    }
    object.fillRule = style.fillRule === 'evenodd' ? 'evenodd' : 'nonzero';
    object.visible = style.display !== 'none' && style.visibility !== 'hidden' && style.visibility !== 'collapse';
    if (style.display === 'none') object.svgDisplayNone = true;
    return object;
  }

  function filterRegionSettings(definition, object, matrix, sourceBounds, context) {
    const objectBoundingBox = String(attr(definition, 'filterUnits') || 'objectBoundingBox').toLowerCase() !== 'userspaceonuse';
    const bboxMatrix = objectBoundingBoxMatrix(matrix, sourceBounds, object);
    const regionMatrix = objectBoundingBox ? bboxMatrix : matrix;
    const coordinate = (value, fallback, axis) => gradientCoordinate(
      value, fallback, axis === 'x' ? context.viewport.width : context.viewport.height, objectBoundingBox
    );
    const x = coordinate(attr(definition, 'x'), -.1, 'x'); const y = coordinate(attr(definition, 'y'), -.1, 'y');
    const width = Math.max(.0001, coordinate(attr(definition, 'width'), 1.2, 'x'));
    const height = Math.max(.0001, coordinate(attr(definition, 'height'), 1.2, 'y'));
    const bounds = transformedRectBounds(regionMatrix, x, y, width, height);
    const filterStyle = resolveStyle(definition, DEFAULT_STYLE, context);
    return {
      filterUnits: objectBoundingBox ? 'objectBoundingBox' : 'userSpaceOnUse',
      primitiveUnits: String(attr(definition, 'primitiveUnits') || 'userSpaceOnUse').toLowerCase() === 'objectboundingbox' ? 'objectBoundingBox' : 'userSpaceOnUse',
      colorInterpolationFilters: String(filterStyle.filterColorInterpolation || 'linearRGB').toLowerCase() === 'srgb' ? 'sRGB' : 'linearRGB',
      region: { x: round(bounds.x - object.x), y: round(bounds.y - object.y), width: round(bounds.width), height: round(bounds.height) }
    };
  }

  function transformedFilterParameters(effect, settings, object, matrix, sourceBounds, context, filterId) {
    const objectBoundingBox = settings.primitiveUnits === 'objectBoundingBox';
    const rawX = Math.max(0, finite(effect.stdDeviation?.[0])); const rawY = Math.max(0, finite(effect.stdDeviation?.[1], rawX));
    const sourceWidth = Math.max(.0001, finite(sourceBounds?.width, object.width)); const sourceHeight = Math.max(.0001, finite(sourceBounds?.height, object.height));
    const sourceStdX = objectBoundingBox ? rawX * sourceWidth : rawX; const sourceStdY = objectBoundingBox ? rawY * sourceHeight : rawY;
    let stdX; let stdY; let offsetX; let offsetY;
    if (!sourceBounds && objectBoundingBox) {
      stdX = sourceStdX; stdY = sourceStdY; offsetX = finite(effect.dx) * sourceWidth; offsetY = finite(effect.dy) * sourceHeight;
    } else {
      const [a, b, c, d] = matrix;
      stdX = Math.sqrt((a * sourceStdX) ** 2 + (c * sourceStdY) ** 2);
      stdY = Math.sqrt((b * sourceStdX) ** 2 + (d * sourceStdY) ** 2);
      const dx = objectBoundingBox ? finite(effect.dx) * sourceWidth : finite(effect.dx);
      const dy = objectBoundingBox ? finite(effect.dy) * sourceHeight : finite(effect.dy);
      offsetX = a * dx + c * dy; offsetY = b * dx + d * dy;
      const covariance = a * b * sourceStdX ** 2 + c * d * sourceStdY ** 2;
      if (Math.abs(covariance) > .001) context.diagnose({
        code: 'SVG_FILTER_AXIS_APPROXIMATED', status: 'approximated', element: 'filter', id: filterId,
        message: `Filter #${filterId}: gedrehte oder gescherte Unschärfeachsen wurden achsenparallel angenähert`
      });
    }
    stdX = round(clamp(stdX, 0, 64)); stdY = round(clamp(stdY, 0, 64));
    const average = Math.sqrt(Math.max(0, stdX * stdY));
    const svgFilter = {
      stdDeviation: [stdX, stdY], ...(effect.input ? { input: effect.input } : {}),
      ...(effect.blendMode ? { blendMode: effect.blendMode, blendInput2: effect.blendInput2 || 'SourceGraphic' } : {})
    };
    if (effect.kind === 'gaussian-blur') return { id: context.createId('effect'), type: 'gaussian-blur', enabled: true, radius: round(average), svgFilter };
    if (effect.kind === 'outer-glow') return {
      id: context.createId('effect'), type: 'outer-glow', enabled: true, size: 0, softness: round(average * 2), rounding: 0,
      opacity: round(effect.opacity * 100), color: effect.color, svgFilter
    };
    return {
      id: context.createId('effect'), type: 'drop-shadow', enabled: true, offsetX: round(clamp(offsetX, -200, 200)), offsetY: round(clamp(offsetY, -200, 200)),
      blur: round(average), opacity: round(effect.opacity * 100), color: effect.color, svgFilter
    };
  }

  function restoreTextAntialias(object, mode) {
    if (!object || !['crisp', 'smooth', 'auto', 'none'].includes(mode)) return 0;
    let restored = 0;
    if (object.type === 'text') { object.antiAlias = mode; restored += 1; }
    (object.children || []).forEach((child) => { restored += restoreTextAntialias(child, mode); });
    return restored;
  }

  function filterImageObject(primitive, settings, sourceObject, matrix, sourceBounds, context, filterId) {
    const dataHref = embeddedImageHref(hrefOf(primitive.element), context, primitive.element); if (!dataHref) return null;
    const objectBoundingBox = settings.primitiveUnits === 'objectBoundingBox';
    const hasExplicitRegion = ['x', 'y', 'width', 'height'].some((name) => attr(primitive.element, name) !== '');
    let geometry; let sourceWidth; let sourceHeight;
    if (!hasExplicitRegion) {
      const region = settings.region || { x: 0, y: 0, width: sourceObject.width, height: sourceObject.height };
      sourceWidth = Math.max(.0001, finite(region.width, sourceObject.width)); sourceHeight = Math.max(.0001, finite(region.height, sourceObject.height));
      geometry = {
        sourceBounds: { x: 0, y: 0, width: sourceWidth, height: sourceHeight },
        x: round(sourceObject.x + finite(region.x)), y: round(sourceObject.y + finite(region.y)), width: round(sourceWidth), height: round(sourceHeight),
        rotation: sourceObject.rotation || 0, flipY: Boolean(sourceObject.flipY), skew: 0
      };
    } else {
      const coordinateWidth = objectBoundingBox ? 1 : context.viewport.width; const coordinateHeight = objectBoundingBox ? 1 : context.viewport.height;
      const sourceX = parseLength(attr(primitive.element, 'x'), coordinateWidth, 0);
      const sourceY = parseLength(attr(primitive.element, 'y'), coordinateHeight, 0);
      sourceWidth = Math.max(0, parseLength(attr(primitive.element, 'width'), coordinateWidth, coordinateWidth));
      sourceHeight = Math.max(0, parseLength(attr(primitive.element, 'height'), coordinateHeight, coordinateHeight));
      if (!(sourceWidth > 0 && sourceHeight > 0)) return null;
      const primitiveMatrix = objectBoundingBox ? objectBoundingBoxMatrix(matrix, sourceBounds, sourceObject) : matrix;
      geometry = imageGeometryFromBounds(sourceX, sourceY, sourceWidth, sourceHeight, primitiveMatrix);
    }
    const image = baseObject('image', `SVG-Filterbild #${filterId || '?'}`, context);
    Object.assign(image, geometry); delete image.sourceBounds; image.href = dataHref;
    image.pixelWidth = Math.max(1, Math.round(sourceWidth)); image.pixelHeight = Math.max(1, Math.round(sourceHeight));
    image.bitmapInterpolation = 'bicubic'; image.svgImageSettings = { preserveAspectRatio: String(attr(primitive.element, 'preserveAspectRatio') || 'xMidYMid meet').slice(0, 128) };
    image.opacity = sourceObject.opacity; image.blendMode = sourceObject.blendMode; image.visible = sourceObject.visible; image.locked = sourceObject.locked;
    image.fill = 'none'; image.stroke = 'none'; image.strokeWidth = 0; image.svgFilterImage = true;
    const filterElement = primitive.element.parentElement || primitive.element.parentNode;
    if (!hasExplicitRegion && localName(filterElement) === 'filter') {
      const nativeFilter = nativeFilterNode(filterElement, context);
      if (nativeFilter?.children?.length) image.svgNativeFilter = nativeFilter;
    }
    context.diagnose({
      code: 'SVG_FILTER_IMAGE_IMPORTED', severity: 'info', status: 'supported', element: 'feimage', attribute: attr(primitive.element, 'href') ? 'href' : 'xlink:href', id: filterId,
      message: `SVG-Filter #${filterId || '?'} wurde als editierbares Bildobjekt importiert`
    });
    return image;
  }

  function applySvgFilter(object, filterValue, matrix, sourceBounds, context) {
    const id = referenceId(filterValue); if (!id) return object;
    const definition = context.definitions.get(id);
    if (!definition || localName(definition) !== 'filter') {
      context.diagnose({
        code: 'SVG_FILTER_REFERENCE_MISSING', severity: 'error', status: 'missing', element: localName(definition) || 'filter', attribute: 'filter', id,
        message: `SVG-Filter #${id || '?'} konnte nicht aufgelöst werden`
      });
      return object;
    }
    const plan = filterPlan(definition, context);
    if (plan.image) {
      const image = filterImageObject(plan.image, filterRegionSettings(definition, object, matrix, sourceBounds, context), object, matrix, sourceBounds, context, id);
      if (image) return image;
    }
    if (plan.nativeRequired) {
      const nativeFilter = nativeFilterNode(definition, context);
      if (nativeFilter?.children?.length) {
        object.svgNativeFilter = nativeFilter;
        context.diagnose({
          code: 'SVG_FILTER_NATIVE_PRESERVED', severity: 'info', status: 'approximated', element: 'filter', attribute: 'filter', id,
          message: `SVG-Filter #${id} bleibt sicher erhalten und wird nativ gerendert; seine Primitive sind nicht als einzelne PYRO-Effekte editierbar`
        });
        return object;
      }
    }
    if (plan.textAntialias) {
      const restored = restoreTextAntialias(object, plan.textAntialias);
      if (restored) context.diagnose({
        code: 'SVG_TEXT_ANTIALIAS_RESTORED', severity: 'info', status: 'supported', element: 'filter', attribute: 'filter', id, count: restored,
        message: `PYRO-Textkantenglättung „${plan.textAntialias}“ wurde für ${restored} Textobjekt${restored === 1 ? '' : 'e'} wiederhergestellt`
      });
      return object;
    }
    if (!plan.effects.length) return object;
    const settings = filterRegionSettings(definition, object, matrix, sourceBounds, context);
    const effects = plan.effects.map((effect) => transformedFilterParameters(effect, settings, object, matrix, sourceBounds, context, id));
    object.effects = [...(Array.isArray(object.effects) ? object.effects : []), ...effects].slice(0, 16);
    object.svgFilterSettings = settings;
    context.diagnose({
      code: 'SVG_FILTER_IMPORTED', severity: 'info', status: 'supported', element: 'filter', attribute: 'filter', id,
      message: `SVG-Filter #${id} wurde als ${effects.length} editierbarer PYRO-Effekt${effects.length === 1 ? '' : 'e'} importiert`
    });
    return object;
  }

  function shiftObject(object, dx, dy) {
    object.x = round(finite(object.x) + dx, 8); object.y = round(finite(object.y) + dy, 8); return object;
  }

  function groupFromObjects(objects, name, context, style = DEFAULT_STYLE) {
    if (!objects.length) return null;
    const left = Math.min(...objects.map((object) => finite(object.x))); const top = Math.min(...objects.map((object) => finite(object.y)));
    const right = Math.max(...objects.map((object) => finite(object.x) + Math.max(.0001, finite(object.width, 1))));
    const bottom = Math.max(...objects.map((object) => finite(object.y) + Math.max(.0001, finite(object.height, 1))));
    const hasNativeFilter = (object) => Boolean(object?.svgNativeFilter || (object?.children || []).some((child) => hasNativeFilter(child)));
    const preserveDocumentCoordinates = objects.some((object) => object?.preserveDocumentCoordinates || object?.type === 'text' && object?.pathBinding || hasNativeFilter(object));
    const group = baseObject('group', name, context);
    group.x = preserveDocumentCoordinates ? 0 : round(left, 8); group.y = preserveDocumentCoordinates ? 0 : round(top, 8);
    group.width = round(Math.max(.0001, preserveDocumentCoordinates ? Math.max(0, right) : right - left), 8);
    group.height = round(Math.max(.0001, preserveDocumentCoordinates ? Math.max(0, bottom) : bottom - top), 8);
    group.fill = 'none'; group.stroke = 'none'; group.strokeWidth = 0; group.opacity = round(style.opacity * 100);
    group.blendMode = normalizeBlendMode(style.blendMode);
    if (String(style.isolation || '').toLowerCase() === 'isolate') group.svgIsolation = 'isolate';
    group.visible = style.display !== 'none';
    if (style.display === 'none') group.svgDisplayNone = true;
    if (preserveDocumentCoordinates) group.preserveDocumentCoordinates = true;
    group.children = preserveDocumentCoordinates ? objects : objects.map((object) => shiftObject(object, -left, -top)); return group;
  }

  function objectFromSinglePath(path, typeHint, name, style, matrix, sourceBounds, context) {
    const normalized = SvgPath.normalizeSubpaths([path]); const localPath = normalized.paths[0];
    const axisAlignedEllipse = ['circle', 'ellipse'].includes(typeHint) && Math.abs(finite(matrix?.[1])) < 1e-10 && Math.abs(finite(matrix?.[2])) < 1e-10;
    const type = axisAlignedEllipse ? 'ellipse'
      : typeHint === 'line' && localPath.points.length === 2 && !localPath.segments.some(Boolean) ? 'line'
      : typeHint === 'polyline' && !localPath.closed ? 'polyline'
        : ['rect', 'polygon'].includes(typeHint) && localPath.closed && !localPath.segments.some(Boolean) ? 'polygon' : 'pen';
    const object = baseObject(type, name, context); const bounds = normalized.bounds;
    object.x = bounds.x; object.y = bounds.y; object.width = Math.max(.0001, bounds.width); object.height = Math.max(.0001, bounds.height);
    if (type === 'ellipse' && typeHint === 'circle' && Math.abs(object.width - object.height) < 1e-8) object.svgPrimitiveType = 'circle';
    object.points = localPath.points; object.bezierSegments = localPath.segments; object.pointModes = localPath.points.map((_, index) => {
      const incoming = localPath.segments[(index - 1 + localPath.segments.length) % localPath.segments.length]; const outgoing = localPath.segments[index];
      return incoming && outgoing ? 'smooth' : 'corner';
    });
    object.closed = Boolean(localPath.closed);
    if (type === 'polygon') object.sides = localPath.points.length;
    const appeared = applyAppearance(object, style, matrix, sourceBounds, context);
    if (!localPath.closed && !['line'].includes(typeHint) && appeared.fill !== 'none') {
      if (appeared.stroke === 'none' && localPath.points.length >= 3) appeared.closed = true;
      else appeared.svgOpenFill = true;
    }
    return appeared;
  }

  function editablePathData(paths) {
    return (paths || []).map((path) => {
      const points = path.points || []; if (!points.length) return '';
      const commands = [`M ${round(points[0][0], 8)} ${round(points[0][1], 8)}`];
      const segmentCount = path.closed ? points.length : Math.max(0, points.length - 1);
      for (let index = 0; index < segmentCount; index += 1) {
        const end = points[(index + 1) % points.length]; const segment = path.segments?.[index];
        commands.push(segment
          ? `C ${round(segment.c1[0], 8)} ${round(segment.c1[1], 8)} ${round(segment.c2[0], 8)} ${round(segment.c2[1], 8)} ${round(end[0], 8)} ${round(end[1], 8)}`
          : `L ${round(end[0], 8)} ${round(end[1], 8)}`);
      }
      if (path.closed) commands.push('Z');
      return commands.join(' ');
    }).filter(Boolean).join(' ');
  }

  function objectsFromPaths(paths, tag, name, style, matrix, sourceBounds, context) {
    const fillOnly = style.fill !== 'none' && style.stroke === 'none';
    const usable = paths.filter((path) => path.points.length >= 2).map((path) => (
      fillOnly && !path.closed && path.points.length >= 3 ? { ...path, closed: true } : path
    ));
    if (!usable.length) return null;
    if (usable.length === 1) return objectFromSinglePath(usable[0], tag, name, style, matrix, sourceBounds, context);
    if (usable.every((path) => path.closed)) {
      const normalized = SvgPath.normalizeSubpaths(usable); const object = baseObject('compound-path', name, context); const bounds = normalized.bounds;
      object.x = bounds.x; object.y = bounds.y; object.width = Math.max(.0001, bounds.width); object.height = Math.max(.0001, bounds.height);
      object.pathWidth = object.width; object.pathHeight = object.height;
      object.outlineContours = normalized.paths.map((path) => path.points);
      object.outlineBezierContours = normalized.paths.map((path) => path.segments);
      object.pathData = editablePathData(normalized.paths);
      return applyAppearance(object, style, matrix, sourceBounds, context);
    }
    const children = usable.map((path, index) => objectFromSinglePath(path, 'path', `${name} ${index + 1}`, style, matrix, sourceBounds, context));
    return groupFromObjects(children, name, context);
  }

  function mergedMarker(element, context, visited = new Set()) {
    if (!element || localName(element) !== 'marker') return null;
    const id = attr(element, 'id'); if (visited.has(element)) {
      context.diagnose({ code: 'SVG_MARKER_REFERENCE_CYCLE', severity: 'error', status: 'blocked', element: 'marker', attribute: 'href', id, message: `Zyklische Markerreferenz #${id || '?'} wurde abgebrochen` });
      return null;
    }
    visited.add(element); const href = hrefOf(element); let inherited = null;
    if (href) {
      const targetId = href.startsWith('#') ? href.slice(1) : ''; const target = targetId ? context.definitions.get(targetId) : null;
      if (target && localName(target) === 'marker') inherited = mergedMarker(target, context, visited);
      else context.diagnose({ code: 'SVG_MARKER_REFERENCE_MISSING', severity: 'error', status: 'missing', element: 'marker', attribute: 'href', id: targetId || id, message: `Markerreferenz ${href || '(leer)'} konnte nicht aufgelöst werden` });
    }
    const attributes = { ...(inherited?.attributes || {}) };
    Array.from(element.attributes || []).forEach((attribute) => { if (!['id', 'href', 'xlink:href'].includes(attribute.name)) attributes[attribute.name] = attribute.value; });
    const ownChildren = childrenOf(element);
    return { element, attributes, contentElements: ownChildren.length ? ownChildren : inherited?.contentElements || [] };
  }

  function simpleMarkerStyle(definition, context) {
    if (definition.contentElements.length !== 1) return '';
    const element = definition.contentElements[0]; const tag = localName(element);
    if (tag === 'circle' || tag === 'ellipse') return 'circle';
    if (tag === 'line') return 'bar';
    if (!['path', 'polyline', 'polygon'].includes(tag)) return '';
    const paths = primitivePaths(element, context); if (paths.length !== 1) return '';
    const path = paths[0]; const count = path.points.length;
    if (path.closed && count === 3) return 'triangle';
    if (path.closed && count === 4) return 'diamond';
    if (!path.closed && count === 3) return 'open-arrow';
    return '';
  }

  function markerDefinitionSettings(definition, object, matrix, style) {
    const attributes = definition.attributes; const markerWidth = Math.max(.0001, parseLength(attributes.markerWidth, 0, 3));
    const markerHeight = Math.max(.0001, parseLength(attributes.markerHeight, 0, 3));
    const viewBox = parseViewBox(attributes.viewBox) || { x: 0, y: 0, width: markerWidth, height: markerHeight };
    const matrixScale = matrixStrokeScale(matrix);
    const userSpaceUnits = String(attributes.markerUnits || 'strokeWidth').toLowerCase() === 'userspaceonuse';
    const nonScalingStroke = String(style?.vectorEffect || '').trim().toLowerCase() === 'non-scaling-stroke';
    // marker geometry is subsequently transformed by `matrix` itself.  Its
    // local scale must therefore remain in the source coordinate system; using
    // the already transformed object stroke width would apply the transform a
    // second time.  A non-scaling stroke compensates that later transform.
    const unitScale = userSpaceUnits ? 1 : Math.max(.0001, finite(style?.strokeWidth, 1) / (nonScalingStroke ? matrixScale : 1));
    const refX = parseLength(attributes.refX, viewBox.width, 0); const refY = parseLength(attributes.refY, viewBox.height, 0);
    return {
      markerWidth, markerHeight, viewBox, unitScale, size: round(Math.max(markerWidth, markerHeight) * unitScale),
      refX: round((refX - viewBox.x) / Math.max(.0001, viewBox.width) * 10), refY: round((refY - viewBox.y) / Math.max(.0001, viewBox.height) * 10),
      rawRefX: refX, rawRefY: refY, orient: String(attributes.orient || '0').trim(),
      preserveAspectRatio: String(attributes.preserveAspectRatio || 'xMidYMid meet')
    };
  }

  function markerAngle(path, index, edge) {
    const points = path.points || []; const point = points[index]; if (!point) return 0;
    const lastIndex = points.length - 1;
    const explicitlyClosed = Boolean(path.closed && lastIndex > 0
      && Math.hypot(points[0][0] - points[lastIndex][0], points[0][1] - points[lastIndex][1]) < 1e-9);
    const logicalIndex = explicitlyClosed && index === lastIndex ? 0 : index;
    const previousIndex = logicalIndex > 0 ? logicalIndex - 1 : path.closed ? lastIndex - (explicitlyClosed ? 1 : 0) : -1;
    const nextIndex = logicalIndex + 1 < points.length - (explicitlyClosed ? 1 : 0) ? logicalIndex + 1 : path.closed ? 0 : -1;
    const incomingSegment = previousIndex >= 0 ? path.segments?.[previousIndex] : null;
    const outgoingSegment = path.segments?.[logicalIndex];
    const incomingPoint = incomingSegment?.c2 || (previousIndex >= 0 ? points[previousIndex] : point);
    const outgoingPoint = outgoingSegment?.c1 || (nextIndex >= 0 ? points[nextIndex] : point);
    const incoming = Math.atan2(point[1] - incomingPoint[1], point[0] - incomingPoint[0]);
    const outgoing = Math.atan2(outgoingPoint[1] - point[1], outgoingPoint[0] - point[0]);
    if (edge === 'start' && !path.closed) return outgoing * 180 / Math.PI;
    if (edge === 'end' && !path.closed) return incoming * 180 / Math.PI;
    if (previousIndex < 0) return outgoing * 180 / Math.PI;
    if (nextIndex < 0) return incoming * 180 / Math.PI;
    const summedX = Math.cos(incoming) + Math.cos(outgoing); const summedY = Math.sin(incoming) + Math.sin(outgoing);
    // At an exact 180-degree reversal the bisector vector is zero. SVG defines
    // the marker direction as 90 degrees counter-clockwise from the incoming tangent.
    if (Math.hypot(summedX, summedY) < 1e-9) return (incoming - Math.PI / 2) * 180 / Math.PI;
    return Math.atan2(summedY, summedX) * 180 / Math.PI;
  }

  function svgAngle(value, fallback = 0) {
    const match = String(value ?? '').trim().match(/^([-+]?(?:\d*\.\d+|\d+\.?)(?:e[-+]?\d+)?)\s*(deg|grad|rad|turn)?$/i);
    if (!match) return fallback;
    const amount = Number(match[1]); const unit = String(match[2] || 'deg').toLowerCase();
    if (unit === 'turn') return amount * 360;
    if (unit === 'grad') return amount * .9;
    if (unit === 'rad') return amount * 180 / Math.PI;
    return amount;
  }

  function expandedMarker(definition, settings, point, tangentAngle, edge, targetMatrix, parentStyle, context, markerId, contextBounds) {
    const orient = settings.orient.toLowerCase(); let angle = tangentAngle;
    if (!orient.startsWith('auto')) angle = svgAngle(settings.orient);
    else if (orient === 'auto-start-reverse' && edge === 'start') angle += 180;
    const viewportMatrix = viewBoxMatrix(settings.viewBox, settings.markerWidth, settings.markerHeight, settings.preserveAspectRatio);
    const reference = SvgPath.applyMatrix(viewportMatrix, [settings.rawRefX, settings.rawRefY]);
    const placement = SvgPath.parseTransform(`translate(${point[0]} ${point[1]}) rotate(${angle}) scale(${settings.unitScale}) translate(${-reference.x} ${-reference.y})`);
    const markerMatrix = SvgPath.multiplyMatrices(targetMatrix, SvgPath.multiplyMatrices(placement, viewportMatrix));
    const markerStyle = resolveDocumentStyle(definition.element, context);
    markerStyle.contextFill = parentStyle?.fill ?? '#000000';
    markerStyle.contextStroke = parentStyle?.stroke ?? 'none';
    markerStyle.contextPaintBounds = contextBounds;
    // A marker definition is not rendered in its original tree. Its own
    // display value only controls that definition node there and must not hide
    // graphics generated at a referencing path.
    markerStyle.display = 'inline';
    markerStyle.markerStart = ''; markerStyle.markerMid = ''; markerStyle.markerEnd = '';
    const objects = definition.contentElements.map((child) => convertElement(child, markerMatrix, markerStyle, context)).filter(Boolean);
    const group = groupFromObjects(objects, `SVG-Marker #${markerId || '?'}`, context, markerStyle);
    if (!group) return null;
    reapplyContextPaints(group, context, contextBounds);
    let result = group;
    const markerOverflow = String(markerStyle.overflow || definition.attributes.overflow || '').trim().toLowerCase();
    if (!['visible', 'auto'].includes(markerOverflow)) {
      const clipMatrix = SvgPath.multiplyMatrices(targetMatrix, placement);
      const clipPaths = SvgPath.transformSubpaths([{
        points: [[0, 0], [settings.markerWidth, 0], [settings.markerWidth, settings.markerHeight], [0, settings.markerHeight]],
        segments: [null, null, null, null], closed: true
      }], clipMatrix);
      const clipStyle = { ...DEFAULT_STYLE, fill: '#000000', stroke: 'none', strokeWidth: 0 };
      const clipSource = objectsFromPaths(
        clipPaths, 'rect', `Markerfenster #${markerId || '?'}`, clipStyle, SvgPath.identityMatrix(),
        { x: 0, y: 0, width: settings.markerWidth, height: settings.markerHeight }, context
      );
      if (clipSource) result = maskGroup(group, { source: clipSource, maskType: 'luminance', region: null }, 'clip', context);
    }
    result.svgMarkerRole = edge; result.svgMarkerSourceId = markerId;
    return result;
  }

  function applySvgMarkers(object, sourcePaths, matrix, style, context) {
    const entries = [
      { edge: 'start', value: style.markerStart }, { edge: 'mid', value: style.markerMid }, { edge: 'end', value: style.markerEnd }
    ].filter((entry) => referenceId(entry.value));
    if (!entries.length || !sourcePaths.length) return object;
    const expanded = []; let imported = 0;
    entries.forEach(({ edge, value }) => {
      const markerId = referenceId(value); const target = context.definitions.get(markerId); const definition = mergedMarker(target, context);
      if (!definition?.contentElements?.length) {
        context.diagnose({ code: 'SVG_MARKER_REFERENCE_MISSING', severity: 'error', status: 'missing', element: 'marker', id: markerId, message: `Marker #${markerId || '?'} konnte nicht aufgelöst werden` }); return;
      }
      const settings = markerDefinitionSettings(definition, object, matrix, style); const simple = simpleMarkerStyle(definition, context);
      const simpleOrientation = edge === 'end' ? /^auto(?:-start-reverse)?$/i.test(settings.orient) : edge === 'start' && /^auto-start-reverse$/i.test(settings.orient);
      const simpleUsesTargetStroke = definition.contentElements.some((element) => /context-stroke/i.test(`${attr(element, 'fill')} ${attr(element, 'stroke')} ${attr(element, 'style')}`));
      if (simple && simpleUsesTargetStroke && edge !== 'mid' && sourcePaths.length === 1 && simpleOrientation && object.type !== 'group') {
        object[`stroke${edge[0].toUpperCase()}${edge.slice(1)}Marker`] = simple;
        object[`stroke${edge[0].toUpperCase()}${edge.slice(1)}MarkerSize`] = Math.max(1, settings.size);
        object.svgMarkerSettings = { ...(object.svgMarkerSettings || {}), [edge]: {
          refX: settings.refX, refY: settings.refY, orient: settings.orient,
          markerUnits: String(definition.attributes.markerUnits || 'strokeWidth')
        } };
        imported += 1; return;
      }
      sourcePaths.forEach((path, pathIndex) => {
        let indices = [];
        if (edge === 'start') indices = pathIndex === 0 ? [0] : [];
        else if (edge === 'end') {
          if (pathIndex !== sourcePaths.length - 1) return;
          const first = path.points[0]; const last = path.points.at(-1);
          const explicitlyClosed = path.closed && first && last && Math.hypot(first[0] - last[0], first[1] - last[1]) < 1e-9;
          indices = [path.closed && !explicitlyClosed ? 0 : Math.max(0, path.points.length - 1)];
        }
        else {
          const first = path.points[0]; const last = path.points.at(-1);
          const explicitlyClosed = path.closed && first && last && Math.hypot(first[0] - last[0], first[1] - last[1]) < 1e-9;
          const lastMidIndex = path.closed && !explicitlyClosed ? path.points.length - 1 : path.points.length - 2;
          indices = Array.from({ length: Math.max(0, lastMidIndex) }, (_, index) => index + 1)
            .filter((index) => path.markerVertices?.[index] !== false);
          if (path.closed && sourcePaths.length > 1 && path.markerVertices?.[0] !== false) {
            const boundaryMarkers = (pathIndex > 0 ? 1 : 0) + (pathIndex < sourcePaths.length - 1 ? 1 : 0);
            for (let markerIndex = 0; markerIndex < boundaryMarkers; markerIndex += 1) indices.unshift(0);
          }
          if (!path.closed && pathIndex > 0 && path.markerVertices?.[0] !== false) indices.unshift(0);
          if (!path.closed && pathIndex < sourcePaths.length - 1 && path.points.length > 1 && path.markerVertices?.[path.points.length - 1] !== false) indices.push(path.points.length - 1);
        }
        indices.forEach((index) => {
          const marker = expandedMarker(
            definition, settings, path.points[index], markerAngle(path, index, edge), edge, matrix, style, context, markerId,
            style.contextPaintBounds || { x: finite(object.x), y: finite(object.y), width: Math.max(.0001, finite(object.width, 1)), height: Math.max(.0001, finite(object.height, 1)) }
          );
          if (marker) expanded.push(marker);
        });
      });
      imported += 1;
    });
    if (imported) context.diagnose({
      code: 'SVG_MARKER_IMPORTED', severity: 'info', status: 'supported', element: 'marker', count: imported,
      message: `${imported} SVG-Markerzuweisung${imported === 1 ? '' : 'en'} wurde${imported === 1 ? '' : 'n'} als Linienabschluss oder editierbare Geometrie importiert`
    });
    if (!expanded.length) return object;
    if (object.svgPaintOrder) {
      const fillLayer = deepClone(object); fillLayer.id = context.createId('object'); fillLayer.name = `${object.name} – Füllung`;
      fillLayer.stroke = 'none'; fillLayer.strokeWidth = 0; fillLayer.textStroke = 'none'; fillLayer.textStrokeWidth = 0;
      delete fillLayer.strokePaint; delete fillLayer.svgStrokeOpacity; delete fillLayer.svgPaintOrder;
      const strokeLayer = deepClone(object); strokeLayer.id = context.createId('object'); strokeLayer.name = `${object.name} – Kontur`;
      strokeLayer.fill = 'none'; delete strokeLayer.fillPaint; delete strokeLayer.svgFillOpacity; delete strokeLayer.svgPaintOrder;
      const ordered = [];
      String(object.svgPaintOrder).split(/\s+/).forEach((part) => {
        if (part === 'fill' && object.fill !== 'none') ordered.push(fillLayer);
        else if (part === 'stroke' && (object.type === 'text' ? object.textStroke : object.stroke) !== 'none' && object.strokeWidth > 0) ordered.push(strokeLayer);
        else if (part === 'markers') ordered.push(...expanded);
      });
      if (ordered.length) return groupFromObjects(ordered, object.name, context, style);
    }
    return groupFromObjects([object, ...expanded], object.name, context, style);
  }

  function convertPrimitive(element, parentMatrix, parentStyle, context) {
    const style = resolveStyle(element, parentStyle, context); const matrix = elementMatrix(element, parentMatrix, context, style);
    const sourcePaths = primitivePaths(element, context, style); if (!sourcePaths.length) return null;
    const sourceBounds = SvgPath.subpathBounds(sourcePaths); const paths = SvgPath.transformSubpaths(sourcePaths, matrix);
    const appearanceStyle = style; const specifiedPathLength = parseLength(style.pathLength, 0, NaN, style.fontSize, context.viewport);
    const tag = localName(element); const name = safeName(element, tag === 'path' ? 'SVG-Pfad' : `SVG-${tag}`);
    const axisAlignedRectangle = tag === 'rect' && Math.abs(finite(matrix?.[1])) < 1e-10 && Math.abs(finite(matrix?.[2])) < 1e-10;
    let object = null;
    if (axisAlignedRectangle) {
      const geometry = rectangleGeometry(element, context.viewport, style);
      const first = SvgPath.applyMatrix(matrix, [geometry.x, geometry.y]);
      const opposite = SvgPath.applyMatrix(matrix, [geometry.x + geometry.width, geometry.y + geometry.height]);
      object = baseObject('rectangle', name, context);
      object.x = round(Math.min(first.x, opposite.x), 6); object.y = round(Math.min(first.y, opposite.y), 6);
      object.width = round(Math.max(.0001, Math.abs(opposite.x - first.x)), 6); object.height = round(Math.max(.0001, Math.abs(opposite.y - first.y)), 6);
      object.svgRx = round(Math.min(object.width / 2, geometry.rx * Math.abs(matrix[0])), 6);
      object.svgRy = round(Math.min(object.height / 2, geometry.ry * Math.abs(matrix[3])), 6);
      object = applyAppearance(object, appearanceStyle, matrix, sourceBounds, context);
    } else object = objectsFromPaths(paths, tag, name, appearanceStyle, matrix, sourceBounds, context);
    // Degenerate closed subpaths can still carry visible markers. Keep their
    // two-point direction as an invisible open path so marker placement is not
    // discarded together with the zero-area source geometry.
    if (!object && [style.markerStart, style.markerMid, style.markerEnd].some((value) => referenceId(value))) {
      const markerCarrierPaths = paths.filter((path) => path.points.length >= 2).map((path) => ({ ...path, closed: false }));
      object = objectsFromPaths(markerCarrierPaths, tag, name, appearanceStyle, matrix, sourceBounds, context);
    }
    if (object && specifiedPathLength >= 0) object.svgPathLength = round(specifiedPathLength, 8);
    if (object) object = applySvgMarkers(object, sourcePaths, matrix, style, context);
    return object ? applyReferences(object, element, matrix, style, context, sourceBounds) : null;
  }

  function textPosition(element, attribute, relative, fallback = 0, fontSize = 16) {
    const source = String(attr(element, attribute)).trim().split(/[\s,]+/)[0];
    const fontRelative = source.match(/^([-+]?(?:\d*\.\d+|\d+\.?)(?:e[-+]?\d+)?)\s*(em|ex|ch|rem)$/i);
    if (fontRelative) {
      const amount = Number(fontRelative[1]); const unit = fontRelative[2].toLowerCase();
      if (!Number.isFinite(amount)) return fallback;
      if (unit === 'em') return amount * Math.max(1, finite(fontSize, 16));
      if (unit === 'rem') return amount * 16;
      return amount * Math.max(1, finite(fontSize, 16)) * .5;
    }
    return parseLength(source, relative, fallback);
  }

  function inheritedTextLanguage(element) {
    let current = element; const visited = new Set();
    while (current && !visited.has(current)) {
      visited.add(current);
      const xmlLanguage = String(current.getAttributeNS?.('http://www.w3.org/XML/1998/namespace', 'lang') || attr(current, 'xml:lang') || '').trim();
      const language = xmlLanguage || String(attr(current, 'lang') || '').trim();
      if (language) return language.replace(/[\u0000-\u001f\u007f]/g, '').slice(0, 64);
      current = current.parentElement || current.parentNode;
    }
    return '';
  }

  function inheritedXmlSpace(element) {
    let current = element; const visited = new Set();
    while (current && !visited.has(current)) {
      visited.add(current);
      const value = String(attr(current, 'xml:space') || '').trim().toLowerCase();
      if (value === 'preserve' || value === 'default') return value;
      current = current.parentElement || current.parentNode;
    }
    return 'default';
  }

  function hasSpecifiedFontSize(element, context) {
    let current = element; const visited = new Set();
    while (current && !visited.has(current)) {
      visited.add(current);
      if (cascadedDeclarations(current, context).some((declaration) => declaration.name === 'font-size' || declaration.name === 'font')) return true;
      current = current.parentElement || current.parentNode;
    }
    return false;
  }

  function resolvedTextFont(style, context, element) {
    const requested = String(style.fontFamily || 'serif').replace(/["']/g, '').slice(0, 256);
    if (typeof context.resolveFont !== 'function') return requested;
    let resolved = '';
    try { resolved = String(context.resolveFont(requested) || '').replace(/["']/g, '').trim().slice(0, 256); } catch { resolved = ''; }
    if (!resolved) resolved = 'serif';
    if (resolved !== requested) context.diagnose({
      code: 'SVG_FONT_SUBSTITUTED', status: 'approximated', element: localName(element), attribute: 'font-family', id: attr(element, 'id'),
      message: `Schrift „${requested}“ ist nicht verfügbar; verwendet wird „${resolved}“`
    });
    return resolved;
  }

  function measuredTextWidth(text, style, context, fontFamily = null) {
    const value = String(text || ''); const fontSize = Math.max(1, finite(style?.fontSize, 16));
    const family = fontFamily || String(style?.fontFamily || 'serif'); let measured = NaN;
    if (typeof context?.measureText === 'function') {
      try {
        measured = Number(context.measureText(value, {
          fontFamily: family, fontSize, fontStyle: String(style?.fontStyle || 'normal'), fontWeight: String(style?.fontWeight || '400')
        }));
      } catch { measured = NaN; }
    }
    const base = Number.isFinite(measured) && measured >= 0 ? measured : value.length * fontSize * .62;
    const spaces = (value.match(/\s/g) || []).length;
    return Math.max(fontSize * .04, base + Math.max(0, value.length - 1) * finite(style?.letterSpacing) + spaces * finite(style?.wordSpacing));
  }

  function baselineShiftValue(value, fontSize) {
    const source = String(value || '').trim().toLowerCase();
    if (source === 'sub') return -fontSize * .2;
    if (source === 'super') return fontSize * .35;
    if (source.endsWith('%')) return finite(source.slice(0, -1)) * fontSize / 100;
    if (source.endsWith('em')) return finite(source.slice(0, -2)) * fontSize;
    return parseLength(source, fontSize, 0);
  }

  function textLineHeightPixels(style, fontSize) {
    const raw = String(style?.lineHeight || 'normal').trim();
    if (raw === 'normal') return fontSize * 1.2;
    if (raw.endsWith('%')) return finite(raw.slice(0, -1), 120) * fontSize / 100;
    if (/^[-+]?(?:\d*\.\d+|\d+\.?)$/.test(raw)) return finite(raw, 1.2) * fontSize;
    return parseLength(raw, fontSize, fontSize * 1.2);
  }

  function convertPlainText(element, parentMatrix, parentStyle, context, textOverride = null, positionFallback = {}, resolvedStyle = null) {
    const style = resolvedStyle || resolveStyle(element, parentStyle, context); const matrix = elementMatrix(element, parentMatrix, context, style);
    const rawText = String(textOverride == null ? element.textContent || '' : textOverride).replace(/\r/g, '');
    const xmlSpace = inheritedXmlSpace(element);
    const whiteSpace = xmlSpace === 'preserve' && String(style.whiteSpace || 'normal').trim().toLowerCase() === 'normal'
      ? 'pre' : String(style.whiteSpace || 'normal').trim().toLowerCase();
    const text = /^(?:pre|pre-line|pre-wrap|break-spaces)$/.test(whiteSpace)
      ? rawText.split('\n').map((line) => whiteSpace === 'pre-line' ? line.trim().replace(/[\t ]+/g, ' ') : line).join('\n').trim()
      : rawText.replace(/[\t ]*\n[\t ]*/g, ' ').replace(/[\t ]+/g, ' ').trim();
    if (!text) return null;
    const fontSize = Math.max(1, finite(style.fontSize, 16)); const letterSpacing = finite(style.letterSpacing); const wordSpacing = finite(style.wordSpacing);
    const fontFamily = resolvedTextFont(style, context, element);
    const normalizedWritingMode = ({ 'tb-rl': 'vertical-rl', tb: 'vertical-rl', 'tb-lr': 'vertical-lr' })[String(style.writingMode || '').trim().toLowerCase()] || String(style.writingMode || '').trim().toLowerCase();
    const verticalWriting = /^(?:vertical|sideways)-/.test(normalizedWritingMode);
    const shapeInsideValue = String(style.shapeInside || '').trim();
    const singleShapeMatch = shapeInsideValue.match(/^url\(\s*["']?#([^)'"\s]+)["']?\s*\)$/i);
    const shapeId = singleShapeMatch ? localHrefId(`#${singleShapeMatch[1]}`) : '';
    const complexShapeFlow = Boolean(shapeInsideValue && !/^none$/i.test(shapeInsideValue) && !shapeId);
    if (complexShapeFlow) context.diagnose({
      code: 'SVG_TEXT_COMPLEX_SHAPE_FLOW_UNSUPPORTED', status: 'ignored', element: 'text', attribute: 'shape-inside', id: attr(element, 'id'),
      message: 'Textfluss über mehrere Formen wird nicht unvollständig angenähert; der editierbare Text bleibt ausgeblendet erhalten'
    });
    const shapeElement = shapeId ? context.definitions.get(shapeId) : null;
    let shapeBounds = null;
    if (shapeElement && GEOMETRY_TAGS.has(localName(shapeElement))) {
      const shapeStyle = resolveDocumentStyle(shapeElement, context); const paths = primitivePaths(shapeElement, context, shapeStyle);
      if (paths.length) shapeBounds = SvgPath.subpathBounds(paths);
    }
    const textAlign = String(style.textAlign || 'start').trim().toLowerCase();
    const effectiveAnchor = shapeBounds ? textAlign === 'center' ? 'middle' : /^(?:right|end)$/.test(textAlign) ? 'end' : 'start' : String(style.textAnchor || 'start').toLowerCase();
    let sourceX = textPosition(element, 'x', context.viewport.width, finite(positionFallback.x), fontSize) + textPosition(element, 'dx', context.viewport.width, 0, fontSize);
    let baseline = textPosition(element, 'y', context.viewport.height, finite(positionFallback.y, 0), fontSize) + textPosition(element, 'dy', context.viewport.height, 0, fontSize);
    if (shapeBounds) {
      if (verticalWriting) {
        sourceX = shapeBounds.x + shapeBounds.width - fontSize * .625;
        baseline = effectiveAnchor === 'middle' ? shapeBounds.y + shapeBounds.height / 2 : effectiveAnchor === 'end' ? shapeBounds.y + shapeBounds.height : shapeBounds.y;
        style.inlineSize = String(shapeBounds.height);
      } else {
        sourceX = effectiveAnchor === 'middle' ? shapeBounds.x + shapeBounds.width / 2 : effectiveAnchor === 'end' ? shapeBounds.x + shapeBounds.width : shapeBounds.x;
        baseline = shapeBounds.y + fontSize * .925;
        style.inlineSize = String(shapeBounds.width);
      }
      context.diagnose({
        code: 'SVG_TEXT_SHAPE_INSIDE_IMPORTED', severity: 'info', status: 'supported', element: 'text', attribute: 'shape-inside', id: shapeId,
        message: `Textfluss in Form #${shapeId} wurde als editierbarer Textrahmen übernommen`
      });
    }
    baseline -= baselineShiftValue(style.baselineShift, fontSize);
    const dominant = String(style.dominantBaseline || 'auto').trim().toLowerCase();
    const rotationAnchorX = sourceX; const rotationAnchorY = baseline;
    const sourceWidth = Math.max(fontSize * .25, measuredTextWidth(text, style, context, fontFamily)); const sourceHeight = fontSize * 1.2;
    const anchor = effectiveAnchor;
    if (!verticalWriting) {
      if (anchor === 'middle') sourceX -= sourceWidth / 2; else if (anchor === 'end') sourceX -= sourceWidth;
    }
    const sourceTop = baseline - fontSize; const [a, b, c, d] = matrix; const scaleX = Math.max(1e-8, Math.hypot(a, b)); const determinant = a * d - b * c;
    const scaleY = Math.max(1e-8, Math.abs(determinant) / scaleX); const skew = (a * c + b * d) / Math.max(1e-8, scaleX * scaleY);
    if (Math.abs(skew) > .001) context.warn(`${safeName(element, 'SVG-Text')}: Scherung wurde beim editierbaren Text angenähert`);
    const width = Math.max(.0001, sourceWidth * scaleX); const height = Math.max(.0001, sourceHeight * scaleY);
    const sourceCenter = [sourceX + sourceWidth / 2, sourceTop + sourceHeight / 2]; const unrotatedCenter = SvgPath.applyMatrix(matrix, sourceCenter);
    const rotateValues = filterNumberList(attr(element, 'rotate'), []); const glyphRotation = finite(rotateValues[0]);
    const rotationPivot = SvgPath.applyMatrix(matrix, [rotationAnchorX, rotationAnchorY]); const rotationRadians = glyphRotation * Math.PI / 180;
    const rotationCosine = Math.cos(rotationRadians); const rotationSine = Math.sin(rotationRadians);
    const centerOffsetX = unrotatedCenter.x - rotationPivot.x; const centerOffsetY = unrotatedCenter.y - rotationPivot.y;
    const center = glyphRotation ? {
      x: rotationPivot.x + centerOffsetX * rotationCosine - centerOffsetY * rotationSine,
      y: rotationPivot.y + centerOffsetX * rotationSine + centerOffsetY * rotationCosine
    } : unrotatedCenter;
    const object = baseObject('text', safeName(element, text.slice(0, 32) || 'SVG-Text'), context);
    object.text = text; object.x = round(center.x - width / 2, 8); object.y = round(center.y - height / 2, 8); object.width = round(width, 8); object.height = round(height, 8);
    object.rotation = round((((Math.atan2(b, a) * 180 / Math.PI) + glyphRotation) % 360 + 360) % 360); object.flipY = determinant < 0;
    if (rotateValues.length > 1) context.diagnose({
      code: 'SVG_TEXT_ROTATE_PARTIAL', status: 'approximated', element: localName(element), attribute: 'rotate', id: attr(element, 'id'),
      message: 'Mehrere zeichenweise Textrotationen wurden auf die erste Rotation des Textlaufs reduziert'
    });
    object.fontFamily = fontFamily;
    object.fontSize = round(fontSize * scaleY); object.horizontalScale = round(scaleX / scaleY * 100); object.verticalScale = 100;
    if (!hasSpecifiedFontSize(element, context)) object.svgImplicitFontSize = object.fontSize;
    object.letterSpacing = round(letterSpacing * scaleX);
    const lineHeight = textLineHeightPixels(style, fontSize) / fontSize * 100;
    object.lineHeight = round(clamp(lineHeight, 40, 400)); object.baselineShift = 0;
    object.align = textAlign === 'justify' ? 'justify' : anchor === 'middle' ? 'center' : anchor === 'end' ? 'right' : 'left';
    const inlineSize = parseLength(style.inlineSize, context.viewport.width, 0, fontSize, context.viewport);
    if (inlineSize > 0) object.svgInlineSize = round(inlineSize * scaleY, 6);
    const writingMode = normalizedWritingMode;
    if (writingMode && writingMode !== 'horizontal-tb') object.svgWritingMode = writingMode;
    const direction = String(style.direction || '').trim().toLowerCase(); if (direction === 'rtl') object.svgDirection = direction;
    const textOrientation = String(style.textOrientation || '').trim().toLowerCase(); if (textOrientation && textOrientation !== 'mixed') object.svgTextOrientation = textOrientation;
    if (textAlign && textAlign !== 'start') object.svgTextAlign = textAlign;
    const fontSizeAdjust = String(style.fontSizeAdjust || '').trim(); if (fontSizeAdjust && fontSizeAdjust !== 'none') object.svgFontSizeAdjust = fontSizeAdjust;
    const rawTextLength = String(attr(element, 'textLength') || '').trim();
    const textLength = rawTextLength ? parseLength(rawTextLength, verticalWriting ? context.viewport.height : context.viewport.width, NaN, fontSize, context.viewport) : NaN;
    if (Number.isFinite(textLength) && textLength >= 0) {
      object.svgTextLength = round(textLength * scaleY, 6);
      object.svgLengthAdjust = String(attr(element, 'lengthAdjust') || '').trim().toLowerCase() === 'spacingandglyphs' ? 'spacingAndGlyphs' : 'spacing';
    }
    if (xmlSpace === 'preserve') object.svgXmlSpace = 'preserve';
    const language = inheritedTextLanguage(element); if (language) object.svgLanguage = language;
    if (['use-script', 'no-change', 'reset-size', 'ideographic', 'alphabetic', 'hanging', 'mathematical', 'central', 'middle', 'text-after-edge', 'text-before-edge', 'text-top', 'text-bottom'].includes(dominant)) {
      object.svgDominantBaseline = dominant;
    }
    object.bold = String(style.fontWeight).toLowerCase() === 'bold' || finite(style.fontWeight) >= 600; object.italic = /italic|oblique/i.test(style.fontStyle);
    const importedAntialias = String(attr(element, 'data-text-antialias') || '').trim().toLowerCase();
    const textDecoration = String(style.textDecoration || '').trim().toLowerCase();
    object.underline = /(?:^|\s)underline(?:\s|$)/.test(textDecoration);
    object.strike = /(?:^|\s)line-through(?:\s|$)/.test(textDecoration); object.autoKern = true;
    object.antiAlias = ['crisp', 'smooth', 'auto', 'none'].includes(importedAntialias) ? importedAntialias : 'auto';
    object.nameManuallyEdited = Boolean(attr(element, 'aria-label') || attr(element, 'data-name') || attr(element, 'inkscape:label') || attr(element, 'id'));
    applyAppearance(object, style, matrix, { x: sourceX, y: sourceTop, width: sourceWidth, height: sourceHeight }, context, true);
    if (complexShapeFlow) { object.visible = false; object.svgUnsupportedShapeFlow = true; }
    return applyReferences(object, element, matrix, style, context, { x: sourceX, y: sourceTop, width: sourceWidth, height: sourceHeight });
  }

  function textAdvance(text, style, context) {
    let family = String(style?.fontFamily || 'serif');
    if (typeof context?.resolveFont === 'function') {
      try { family = String(context.resolveFont(family) || family); } catch { /* Browser-Fallback verwenden. */ }
    }
    return Math.max(Math.max(1, finite(style?.fontSize, 16)) * .25, measuredTextWidth(text, style, context, family));
  }

  function convertTspanText(element, parentMatrix, parentStyle, context, spans) {
    const style = resolveStyle(element, parentStyle, context); const matrix = elementMatrix(element, parentMatrix, context, style);
    const baseFontSize = Math.max(1, finite(style.fontSize, 16));
    let cursorX = textPosition(element, 'x', context.viewport.width, 0, baseFontSize) + textPosition(element, 'dx', context.viewport.width, 0, baseFontSize);
    let cursorY = textPosition(element, 'y', context.viewport.height, 0, baseFontSize) + textPosition(element, 'dy', context.viewport.height, 0, baseFontSize);
    const runs = [];
    const childNodes = Array.from(element.childNodes || []);
    const entries = childNodes.length
      ? childNodes.filter((node) => ['tspan', 'a'].includes(localName(node)) || node?.nodeType === 3 && String(node.textContent || '').trim())
      : spans.length ? spans : [element];
    const nativeRuns = entries.map((entry) => {
      const entryTag = localName(entry); const link = entryTag === 'a' ? entry : null;
      const linkStyle = link ? resolveStyle(link, style, context) : null;
      const nestedSpan = link && childrenOf(link).length === 1 && localName(childrenOf(link)[0]) === 'tspan' ? childrenOf(link)[0] : null;
      const carrier = entryTag === 'tspan' ? entry : nestedSpan || link;
      const runStyle = carrier ? resolveStyle(carrier, linkStyle || style, context) : style;
      const text = String(entry.textContent || '').replace(/\r/g, '').replace(/\s+/g, ' ');
      const positioned = [link, carrier].filter(Boolean).some((node) => ['x', 'y', 'dx', 'dy', 'rotate', 'transform'].some((name) => attr(node, name) !== ''));
      const opacity = clamp(finite(runStyle.opacity, 1) * (nestedSpan ? finite(linkStyle?.opacity, 1) : 1), 0, 1);
      return {
        text, positioned, opacity: round(opacity * 100),
        fill: runStyle.fill !== style.fill ? normalizeColor(runStyle.fill, style.color) : '',
        fontFamily: runStyle.fontFamily !== style.fontFamily ? resolvedTextFont(runStyle, context, carrier || element) : '',
        fontSize: finite(runStyle.fontSize) !== finite(style.fontSize) ? round(runStyle.fontSize) : 0,
        lineHeight: round(textLineHeightPixels(runStyle, Math.max(1, finite(runStyle.fontSize, baseFontSize))), 6), lineHeightExplicit: true,
        bold: String(runStyle.fontWeight).toLowerCase() === 'bold' || finite(runStyle.fontWeight) >= 600,
        italic: /italic|oblique/i.test(runStyle.fontStyle),
        direction: String(runStyle.direction || '').trim().toLowerCase() === 'rtl' ? 'rtl' : ''
      };
    }).filter((run) => run.text.trim());
    if (nativeRuns.length) {
      nativeRuns[0].text = nativeRuns[0].text.trimStart();
      nativeRuns[nativeRuns.length - 1].text = nativeRuns[nativeRuns.length - 1].text.trimEnd();
    }
    const nativeRunText = nativeRuns.map((run) => run.text).join('');
    const inlineSize = parseLength(style.inlineSize, context.viewport.width, 0, baseFontSize, context.viewport);
    if (inlineSize > 0 && nativeRuns.length > 1) {
      const object = convertPlainText(element, parentMatrix, parentStyle, context); if (!object) return null;
      if (nativeRunText === object.text) {
        object.svgTextRuns = nativeRuns.map(({ positioned, ...run }) => run);
        object.svgTextRunsSource = object.text;
        context.diagnose({
          code: 'SVG_TEXT_INLINE_RUNS_IMPORTED', severity: 'info', status: 'supported', element: 'text', id: attr(element, 'id'), count: nativeRuns.length,
          message: `${nativeRuns.length} formatierte SVG-Textläufe bleiben in einem gemeinsamen Textfluss erhalten`
        });
        return object;
      }
    }
    const mixedDirection = /[\u0590-\u08ff]/u.test(nativeRunText) && /[A-Za-z\u00c0-\u024f]/u.test(nativeRunText);
    if (mixedDirection && nativeRuns.length > 1 && nativeRuns.every((run) => !run.positioned)) {
      const object = convertPlainText(element, parentMatrix, parentStyle, context); if (!object) return null;
      object.svgTextRuns = nativeRuns.map(({ positioned, ...run }) => run);
      object.svgTextRunsSource = object.text;
      context.diagnose({
        code: 'SVG_TEXT_BIDI_RUNS_PRESERVED', severity: 'info', status: 'supported', element: 'text', id: attr(element, 'id'), count: nativeRuns.length,
        message: `${nativeRuns.length} gemischte bidirektionale SVG-Textläufe bleiben in einem gemeinsamen Textlayout erhalten`
      });
      return object;
    }
    let firstLetterPending = true;
    entries.forEach((entry) => {
      const entryTag = localName(entry); const link = entryTag === 'a' ? entry : null;
      let span = entryTag === 'tspan' ? entry : null; let carrier = span; let runParentStyle = style; let containerOpacity = 1;
      if (link) {
        const linkStyle = resolveStyle(link, style, context); const inlineChild = childrenOf(link).length === 1 && localName(childrenOf(link)[0]) === 'tspan' ? childrenOf(link)[0] : null;
        span = inlineChild; carrier = inlineChild || link; runParentStyle = inlineChild ? linkStyle : style;
        containerOpacity = finite(linkStyle.opacity, 1);
      }
      const runStyle = carrier ? resolveStyle(carrier, runParentStyle, context) : style;
      const normalizedText = String(entry.textContent || '').replace(/\r/g, '').replace(/\s+/g, ' ');
      const text = normalizedText.trim(); if (!text) return;
      const leadingSpace = /^\s/.test(normalizedText) && runs.length ? textAdvance(' ', runStyle, context) : 0;
      const trailingSpace = /\s$/.test(normalizedText) ? textAdvance(' ', runStyle, context) : 0;
      carrier = carrier || {
        localName: 'tspan', nodeName: 'tspan', nodeType: 1, attributes: [], parentElement: element, parentNode: element,
        textContent: text, getAttribute: () => null, getAttributeNS: () => null
      };
      const runFontSize = Math.max(1, finite(runStyle.fontSize, baseFontSize));
      const explicitX = span && attr(span, 'x') !== '';
      const runX = explicitX ? textPosition(span, 'x', context.viewport.width, cursorX, runFontSize) : cursorX + leadingSpace;
      const runY = span && attr(span, 'y') !== '' ? textPosition(span, 'y', context.viewport.height, cursorY, runFontSize) : cursorY;
      const firstLetterStyle = firstLetterPending ? resolveFirstLetterStyle(element, runStyle, context) : null;
      const characters = [...text]; const segments = firstLetterStyle && characters.length
        ? [{ text: characters[0], style: firstLetterStyle }, { text: characters.slice(1).join(''), style: runStyle }]
        : [{ text, style: runStyle }];
      let segmentX = runX;
      segments.filter((segment) => segment.text).forEach((segment) => {
        const run = convertPlainText(carrier, matrix, runParentStyle, context, segment.text, { x: segmentX, y: runY }, segment.style); if (!run) return;
        if (link && span && containerOpacity < 1) run.opacity = round(clamp(finite(run.opacity, 100) / 100 * containerOpacity, 0, 1) * 100);
        run.name = `${safeName(element, 'SVG-Text')} – Lauf ${runs.length + 1}`; run.svgTextRun = true; runs.push(run);
        segmentX += textAdvance(segment.text, segment.style, context);
      });
      firstLetterPending = false;
      cursorX = segmentX + (span ? textPosition(span, 'dx', context.viewport.width, 0, runFontSize) : 0) + trailingSpace;
      cursorY = runY + (span ? textPosition(span, 'dy', context.viewport.height, 0, runFontSize) : 0);
      if (span && childrenOf(span).some((child) => localName(child) === 'tspan')) context.diagnose({
        code: 'SVG_TEXT_TSPAN_NESTED_APPROXIMATED', status: 'approximated', element: 'tspan', id: attr(span, 'id'),
        message: 'Verschachtelte <tspan>-Läufe wurden innerhalb ihres übergeordneten Textlaufs zusammengefasst'
      });
    });
    const group = groupFromObjects(runs, safeName(element, 'Strukturierter SVG-Text'), context, style); if (!group) return null;
    context.diagnose({
      code: 'SVG_TEXT_TSPAN_IMPORTED', severity: 'info', status: 'supported', element: 'tspan', id: attr(element, 'id'), count: runs.length,
      message: `${runs.length} SVG-Textläufe wurden getrennt editierbar importiert`
    });
    return applyReferences(group, element, matrix, style, context);
  }

  function approximatePathLength(paths) {
    const cubicPoint = (start, control1, control2, end, t) => {
      const inverse = 1 - t;
      return [
        inverse ** 3 * start[0] + 3 * inverse ** 2 * t * control1[0] + 3 * inverse * t ** 2 * control2[0] + t ** 3 * end[0],
        inverse ** 3 * start[1] + 3 * inverse ** 2 * t * control1[1] + 3 * inverse * t ** 2 * control2[1] + t ** 3 * end[1]
      ];
    };
    return Math.max(.0001, (paths || []).reduce((total, path) => {
      const points = path.points || []; const count = path.closed ? points.length : Math.max(0, points.length - 1); let length = 0;
      for (let index = 0; index < count; index += 1) {
        const start = points[index]; const end = points[(index + 1) % points.length]; const segment = path.segments?.[index];
        if (!start || !end) continue;
        if (!segment) { length += Math.hypot(end[0] - start[0], end[1] - start[1]); continue; }
        let previous = start;
        for (let step = 1; step <= 24; step += 1) {
          const point = cubicPoint(start, segment.c1, segment.c2, end, step / 24);
          length += Math.hypot(point[0] - previous[0], point[1] - previous[1]); previous = point;
        }
      }
      return total + length;
    }, 0));
  }

  function ellipsePerimeter(rx, ry) {
    const radiusX = Math.max(0, finite(rx)); const radiusY = Math.max(0, finite(ry));
    if (!(radiusX > 0 && radiusY > 0)) return 0;
    if (Math.abs(radiusX - radiusY) < 1e-12) return Math.PI * 2 * radiusX;
    const steps = 256; const step = Math.PI * 2 / steps;
    const speed = (angle) => Math.hypot(radiusX * Math.sin(angle), radiusY * Math.cos(angle));
    let sum = speed(0) + speed(Math.PI * 2);
    for (let index = 1; index < steps; index += 1) sum += speed(index * step) * (index % 2 ? 4 : 2);
    return sum * step / 3;
  }

  function geometryPathLength(element, paths, style, context) {
    const tag = localName(element); const bounds = SvgPath.subpathBounds(paths);
    if (tag === 'circle') return Math.PI * Math.max(0, bounds.width);
    if (tag === 'ellipse') return ellipsePerimeter(bounds.width / 2, bounds.height / 2);
    if (tag === 'rect') {
      const geometry = rectangleGeometry(element, context.viewport, style);
      if (geometry) return 2 * Math.max(0, geometry.width - geometry.rx * 2) + 2 * Math.max(0, geometry.height - geometry.ry * 2)
        + ellipsePerimeter(geometry.rx, geometry.ry);
    }
    return approximatePathLength(paths);
  }

  function inlineTextPathElement(textPath, pathData) {
    const attributes = [{ name: 'd', value: String(pathData || '') }];
    return {
      localName: 'path', nodeName: 'path', namespaceURI: SVG_NS, nodeType: 1, attributes,
      children: [], childNodes: [], parentElement: textPath?.parentElement || null, parentNode: textPath?.parentNode || null,
      textContent: '',
      getAttribute(name) { return String(name || '').toLowerCase() === 'd' ? String(pathData || '') : null; },
      getAttributeNS() { return null; }
    };
  }

  function convertTextPath(element, textPath, parentMatrix, parentStyle, context) {
    const style = resolveStyle(element, parentStyle, context); const matrix = elementMatrix(element, parentMatrix, context, style);
    const inlinePathData = String(attr(textPath, 'path') || '').trim();
    const inlineElement = inlinePathData ? inlineTextPathElement(textPath, inlinePathData) : null;
    const inlineStyle = inlineElement ? resolveStyle(inlineElement, style, context) : null;
    const inlinePaths = inlineElement ? primitivePaths(inlineElement, context, inlineStyle) : [];
    const targetId = localHrefId(hrefOf(textPath));
    const targetElement = inlinePaths.some((path) => path.points?.length >= 2) ? inlineElement : targetId ? context.definitions.get(targetId) : null;
    if (!targetElement || !GEOMETRY_TAGS.has(localName(targetElement))) {
      context.diagnose({
        code: 'SVG_TEXT_PATH_REFERENCE_MISSING', severity: 'error', status: 'missing', element: 'textpath', attribute: 'href', id: targetId,
        message: `Textpfad #${targetId || '?'} konnte nicht als Vektorform aufgelöst werden`
      });
      return convertPlainText(element, parentMatrix, parentStyle, context);
    }
    const targetStyle = targetElement === inlineElement ? inlineStyle : resolveStyle(targetElement, style, context);
    const sourcePaths = targetElement === inlineElement ? inlinePaths : primitivePaths(targetElement, context, targetStyle); const target = convertPrimitive(targetElement, matrix, style, context);
    const text = String(textPath.textContent || '').replace(/\r/g, '').replace(/[\t ]*\n[\t ]*/g, ' ').trim();
    const nestedRuns = []; const collectRuns = (node) => childrenOf(node).forEach((child) => {
      if (localName(child) === 'tspan') nestedRuns.push(child); collectRuns(child);
    });
    collectRuns(textPath);
    if (nestedRuns.length) context.diagnose({
      code: 'SVG_TEXT_PATH_RUNS_APPROXIMATED', status: 'approximated', element: 'textpath', id: attr(element, 'id'), count: nestedRuns.length,
      message: `${nestedRuns.length} unterschiedlich formatierte Textpfad-Läufe wurden zu einem editierbaren „Text an Form“-Objekt zusammengefasst`
    });
    const textObject = convertPlainText(textPath, matrix, style, context, text, { x: 0, y: Math.max(1, finite(style.fontSize, 16)) });
    if (!target || !textObject) return convertPlainText(element, parentMatrix, parentStyle, context);
    target.visible = false; target.name = `Textpfad: ${targetElement === inlineElement ? 'Inline-Pfad' : safeName(targetElement, targetId || 'Form')}`; target.svgTextPathSource = true;
    const rawOffset = String(attr(textPath, 'startOffset') || '0').trim();
    const specifiedPathLength = parseLength(targetStyle.pathLength, 0, NaN, targetStyle.fontSize, context.viewport);
    const numericOffset = finite(rawOffset);
    const percentageOffset = rawOffset.endsWith('%');
    const fraction = specifiedPathLength === 0
      ? 0
      : percentageOffset
        ? finite(rawOffset.slice(0, -1)) / 100
        : numericOffset / (specifiedPathLength > 0 ? specifiedPathLength : geometryPathLength(targetElement, sourcePaths, targetStyle, context));
    if (specifiedPathLength === 0 && numericOffset !== 0 && !percentageOffset) textObject.visible = false;
    const oppositeSide = String(attr(textPath, 'side')).trim().toLowerCase() === 'right';
    textObject.pathBinding = {
      targetId: target.id, placement: oppositeSide ? 'inside' : 'outside', distance: 0,
      offset: round(clamp(fraction, -1, 1) * 360), orientation: 'vertical', spacing: 0, reversed: oppositeSide, targetVisible: false
    };
    const padding = Math.max(4, textObject.fontSize * .75);
    textObject.x = round(target.x - padding); textObject.y = round(target.y - padding);
    textObject.width = round(target.width + padding * 2); textObject.height = round(target.height + padding * 2); textObject.preserveDocumentCoordinates = true;
    context.deferredObjects.push(target);
    context.diagnose({
      code: 'SVG_TEXT_PATH_IMPORTED', severity: 'info', status: 'supported', element: 'textpath', attribute: attr(textPath, 'href') ? 'href' : 'xlink:href', id: targetId,
      message: `Text auf Pfad #${targetId} wurde mit „Text an Form“ verknüpft`
    });
    return applyReferences(textObject, element, matrix, style, context);
  }

  function convertText(element, parentMatrix, parentStyle, context) {
    const children = childrenOf(element); const textPaths = children.filter((child) => localName(child) === 'textpath');
    if (textPaths.length === 1) return convertTextPath(element, textPaths[0], parentMatrix, parentStyle, context);
    if (textPaths.length > 1) {
      const style = resolveStyle(element, parentStyle, context);
      const objects = textPaths.map((textPath) => convertTextPath(element, textPath, parentMatrix, parentStyle, context)).filter(Boolean);
      const group = groupFromObjects(objects, safeName(element, 'SVG-Textpfade'), context, style);
      if (group) context.diagnose({
        code: 'SVG_TEXT_PATH_MULTIPLE_IMPORTED', severity: 'info', status: 'supported', element: 'textpath', id: attr(element, 'id'), count: objects.length,
        message: `${objects.length} Textpfade eines Textobjekts wurden getrennt editierbar importiert`
      });
      return group;
    }
    const inlineRuns = children.filter((child) => ['tspan', 'a'].includes(localName(child)));
    if (inlineRuns.length || resolveFirstLetterStyle(element, resolveStyle(element, parentStyle, context), context)) return convertTspanText(element, parentMatrix, parentStyle, context, inlineRuns);
    return convertPlainText(element, parentMatrix, parentStyle, context);
  }

  function embeddedSvgIntrinsicSize(svg) {
    const viewBox = parseViewBox(attr(svg, 'viewBox'));
    const length = (value) => {
      const source = String(value || '').trim();
      if (!source || source === 'auto' || /%$/.test(source)) return 0;
      return Math.max(0, parseLength(source, 0, 0));
    };
    const width = length(attr(svg, 'width')); const height = length(attr(svg, 'height'));
    const ratio = width > 0 && height > 0 ? width / height : viewBox?.width > 0 && viewBox?.height > 0 ? viewBox.width / viewBox.height : 0;
    return { width, height, ratio };
  }

  function embeddedImageResource(value, context, element) {
    const href = String(value || '').trim();
    if (!/^data:image\/(?:png|jpe?g|webp|gif|svg\+xml)(?:;[^,]*)?,/i.test(href)) return { href: '', intrinsic: null };
    if (!/^data:image\/svg\+xml/i.test(href)) return { href, intrinsic: null };
    try {
      const separator = href.indexOf(','); const metadata = href.slice(0, separator);
      const fragmentIndex = href.indexOf('#', separator + 1);
      const payload = href.slice(separator + 1, fragmentIndex < 0 ? undefined : fragmentIndex);
      const fragment = fragmentIndex < 0 ? '' : decodeURIComponent(href.slice(fragmentIndex + 1));
      let source;
      if (/;base64(?:;|$)/i.test(metadata)) {
        const binary = root.atob(payload); const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
        source = new TextDecoder().decode(bytes);
      } else source = decodeURIComponent(payload);
      const document = parseSvgDocument(source, { DOMParser: context.DOMParser });
      const svg = document.documentElement; const intrinsic = embeddedSvgIntrinsicSize(svg);
      const references = new Set(); let serialized = serializeSafePatternElement(document.documentElement, context, references);
      if (fragment) {
        const candidates = Array.from(document.getElementsByTagName?.('*') || []);
        const view = candidates.find((candidate) => attr(candidate, 'id') === fragment && localName(candidate) === 'view');
        const viewBox = view && attr(view, 'viewBox');
        if (viewBox) {
          serialized = serialized.replace(/\sviewBox="[^"]*"/i, '').replace(/^<svg\b/, `<svg viewBox="${escapeXml(viewBox)}"`);
          const preserveAspectRatio = attr(view, 'preserveAspectRatio');
          if (preserveAspectRatio) serialized = serialized.replace(/\spreserveAspectRatio="[^"]*"/i, '').replace(/^<svg\b/, `<svg preserveAspectRatio="${escapeXml(preserveAspectRatio)}"`);
        }
      }
      serialized = serialized.replace(/^<svg\b/, `<svg xmlns="${SVG_NS}" xmlns:xlink="${XLINK_NS}"`);
      return { href: `data:image/svg+xml;charset=utf-8,${encodeURIComponent(serialized)}`, intrinsic };
    } catch {
      context.diagnose({
        code: 'SVG_IMAGE_DATA_INVALID', severity: 'error', status: 'blocked', element: 'image', id: attr(element, 'id'),
        message: 'Ein eingebettetes SVG-Bild ist ungültig und wurde nicht geladen'
      });
      return { href: '', intrinsic: null };
    }
  }

  function embeddedImageHref(value, context, element) {
    return embeddedImageResource(value, context, element).href;
  }

  function imageGeometryFromBounds(sourceX, sourceY, sourceWidth, sourceHeight, matrix) {
    const [a, b, c, d] = matrix; const scaleX = Math.max(1e-8, Math.hypot(a, b)); const determinant = a * d - b * c;
    const scaleY = Math.max(1e-8, Math.abs(determinant) / scaleX); const skew = (a * c + b * d) / Math.max(1e-8, scaleX * scaleY);
    const center = SvgPath.applyMatrix(matrix, [sourceX + sourceWidth / 2, sourceY + sourceHeight / 2]);
    return {
      sourceBounds: { x: sourceX, y: sourceY, width: sourceWidth, height: sourceHeight },
      x: round(center.x - sourceWidth * scaleX / 2), y: round(center.y - sourceHeight * scaleY / 2),
      width: round(sourceWidth * scaleX), height: round(sourceHeight * scaleY),
      rotation: round(((Math.atan2(b, a) * 180 / Math.PI) % 360 + 360) % 360), flipY: determinant < 0, skew
    };
  }

  function imageGeometry(element, matrix, context, placeholder = false, style = DEFAULT_STYLE, intrinsic = null) {
    const fontSize = finite(style.fontSize, 16); const viewport = context.viewport;
    const sourceX = parseLength(geometryValue(element, style, 'x'), viewport.width, 0, fontSize, viewport);
    const sourceY = parseLength(geometryValue(element, style, 'y'), viewport.height, 0, fontSize, viewport);
    let sourceWidth = parseLength(geometryValue(element, style, 'width'), viewport.width, 0, fontSize, viewport);
    let sourceHeight = parseLength(geometryValue(element, style, 'height'), viewport.height, 0, fontSize, viewport);
    const intrinsicRatio = finite(intrinsic?.ratio, intrinsic?.width > 0 && intrinsic?.height > 0 ? intrinsic.width / intrinsic.height : 0);
    if (!(sourceWidth > 0) && !(sourceHeight > 0) && intrinsic?.width > 0 && intrinsic?.height > 0) {
      sourceWidth = intrinsic.width; sourceHeight = intrinsic.height;
    } else if (sourceWidth > 0 && !(sourceHeight > 0) && intrinsicRatio > 0) sourceHeight = sourceWidth / intrinsicRatio;
    else if (sourceHeight > 0 && !(sourceWidth > 0) && intrinsicRatio > 0) sourceWidth = sourceHeight * intrinsicRatio;
    if (placeholder) { sourceWidth = sourceWidth > 0 ? sourceWidth : 96; sourceHeight = sourceHeight > 0 ? sourceHeight : 64; }
    if (!(sourceWidth > 0 && sourceHeight > 0)) return null;
    return imageGeometryFromBounds(sourceX, sourceY, sourceWidth, sourceHeight, matrix);
  }

  function inverseAffineMatrix(matrix) {
    const [a, b, c, d, e, f] = matrix; const determinant = a * d - b * c;
    if (Math.abs(determinant) < 1e-10) return null;
    return [d / determinant, -b / determinant, -c / determinant, a / determinant, (c * f - d * e) / determinant, (b * e - a * f) / determinant];
  }

  function imageObjectMatrix(geometry) {
    const angle = finite(geometry.rotation) * Math.PI / 180; const cosine = Math.cos(angle); const sine = Math.sin(angle);
    const width = Math.max(1e-8, finite(geometry.width, 1)); const height = Math.max(1e-8, finite(geometry.height, 1));
    const centerX = width / 2; const centerY = height / 2; const flipY = geometry.flipY ? -1 : 1;
    const a = cosine; const b = sine; const c = -sine * flipY; const d = cosine * flipY;
    return [
      a, b, c, d,
      finite(geometry.x) + centerX - a * centerX - c * centerY,
      finite(geometry.y) + centerY - b * centerX - d * centerY
    ];
  }

  function importedImageViewport(geometry, matrix) {
    const inverse = inverseAffineMatrix(imageObjectMatrix(geometry));
    if (!inverse) return null;
    return {
      x: round(geometry.sourceBounds.x, 8), y: round(geometry.sourceBounds.y, 8),
      width: round(geometry.sourceBounds.width, 8), height: round(geometry.sourceBounds.height, 8),
      baseWidth: round(geometry.width, 8), baseHeight: round(geometry.height, 8),
      matrix: SvgPath.multiplyMatrices(inverse, matrix).map((value) => round(value, 10))
    };
  }

  function convertImage(element, parentMatrix, parentStyle, context) {
    const style = resolveStyle(element, parentStyle, context); const matrix = elementMatrix(element, parentMatrix, context, style); const rawHref = hrefOf(element);
    const resource = embeddedImageResource(rawHref, context, element); const dataHref = resource.href; const placeholder = !dataHref;
    const geometry = imageGeometry(element, matrix, context, placeholder, style, resource.intrinsic); if (!geometry) return null;
    const object = baseObject(placeholder ? 'resource-placeholder' : 'image', safeName(element, placeholder ? 'Fehlende SVG-Ressource' : 'SVG-Bild'), context);
    Object.assign(object, geometry); delete object.sourceBounds; delete object.skew;
    object.opacity = round(clamp(style.opacity, 0, 1) * 100); object.blendMode = normalizeBlendMode(style.blendMode);
    object.visible = style.display !== 'none' && style.visibility !== 'hidden' && style.visibility !== 'collapse';
    if (style.display === 'none') object.svgDisplayNone = true;
    object.fill = 'none'; object.stroke = 'none'; object.strokeWidth = 0;
    if (Math.abs(geometry.skew) > .001 && !placeholder) context.diagnose({
      code: 'SVG_IMAGE_AFFINE_PRESERVED', severity: 'info', status: 'supported', element: 'image', id: attr(element, 'id'),
      message: `${object.name}: Affine Bildabbildung wurde verlustfrei bewahrt`
    });
    if (!placeholder) {
      object.href = dataHref; object.pixelWidth = Math.max(1, Math.round(geometry.sourceBounds.width)); object.pixelHeight = Math.max(1, Math.round(geometry.sourceBounds.height));
      object.bitmapInterpolation = /^(?:pixelated|crisp-edges)$/i.test(String(style.imageRendering || '').trim()) ? 'nearest' : 'bicubic';
      object.svgImageSettings = {
        preserveAspectRatio: String(attr(element, 'preserveAspectRatio') || 'xMidYMid meet').slice(0, 128),
        importedViewport: importedImageViewport(geometry, matrix)
      };
      context.diagnose({
        code: 'SVG_IMAGE_EMBEDDED_IMPORTED', severity: 'info', status: 'supported', element: 'image', attribute: attr(element, 'href') ? 'href' : 'xlink:href', id: attr(element, 'id'),
        message: 'Ein eingebettetes SVG-Bild wurde als editierbares Bildobjekt importiert'
      });
    } else {
      object.resourceHref = safeText(rawHref, 2048); object.resourceKind = /^(?:https?:|blob:)/i.test(rawHref) ? 'blocked' : 'missing';
      const blocked = object.resourceKind === 'blocked';
      context.diagnose({
        code: blocked ? 'SVG_IMAGE_RESOURCE_BLOCKED' : 'SVG_IMAGE_RESOURCE_MISSING', severity: 'error', status: blocked ? 'blocked' : 'missing',
        element: 'image', attribute: attr(element, 'href') ? 'href' : 'xlink:href', id: attr(element, 'id'),
        message: blocked ? 'Eine externe SVG-Bildressource wurde aus Sicherheitsgründen nicht geladen' : 'Eine relative SVG-Bildressource fehlt; im Editor bleibt ein Platzhalter sichtbar'
      });
    }
    return applyReferences(object, element, matrix, style, context, geometry.sourceBounds);
  }

  function transformedRectBounds(matrix, x, y, width, height) {
    const points = [[x, y], [x + width, y], [x + width, y + height], [x, y + height]].map((point) => SvgPath.applyMatrix(matrix, point));
    const left = Math.min(...points.map((point) => point.x)); const top = Math.min(...points.map((point) => point.y));
    const right = Math.max(...points.map((point) => point.x)); const bottom = Math.max(...points.map((point) => point.y));
    return { x: left, y: top, width: Math.max(.0001, right - left), height: Math.max(.0001, bottom - top) };
  }

  function objectBoundingBoxMatrix(matrix, sourceBounds, object) {
    if (sourceBounds) return SvgPath.multiplyMatrices(matrix, [
      Math.max(.0001, sourceBounds.width), 0, 0, Math.max(.0001, sourceBounds.height), sourceBounds.x, sourceBounds.y
    ]);
    return [Math.max(.0001, finite(object.width, 1)), 0, 0, Math.max(.0001, finite(object.height, 1)), finite(object.x), finite(object.y)];
  }

  function maskRegion(definition, matrix, bboxMatrix, context) {
    const objectBoundingBox = String(attr(definition, 'maskUnits') || 'objectBoundingBox').toLowerCase() !== 'userspaceonuse';
    const regionMatrix = objectBoundingBox ? bboxMatrix : matrix;
    const relative = (value, fallback, axis) => gradientCoordinate(
      value, fallback, axis === 'x' ? context.viewport.width : context.viewport.height, objectBoundingBox
    );
    const x = relative(attr(definition, 'x'), -.1, 'x'); const y = relative(attr(definition, 'y'), -.1, 'y');
    const width = Math.max(.0001, relative(attr(definition, 'width'), 1.2, 'x'));
    const height = Math.max(.0001, relative(attr(definition, 'height'), 1.2, 'y'));
    return transformedRectBounds(regionMatrix, x, y, width, height);
  }

  function renderableReferenceGraphic(object) {
    if (!object || object.svgDisplayNone) return null;
    if (Array.isArray(object.children)) {
      object.children = object.children.map((child) => renderableReferenceGraphic(child)).filter(Boolean);
      if (!object.children.length) return null;
      return object;
    }
    return object.visible === false ? null : object;
  }

  function referenceObject(id, matrix, mode, context, object, sourceBounds = null) {
    const definition = context.definitions.get(id); const expectedTag = mode === 'clip' ? 'clippath' : 'mask';
    if (!definition || localName(definition) !== expectedTag) {
      context.diagnose({
        code: mode === 'clip' ? 'SVG_CLIP_REFERENCE_MISSING' : 'SVG_MASK_REFERENCE_MISSING', severity: 'error', status: 'missing',
        element: expectedTag, attribute: mode === 'clip' ? 'clip-path' : 'mask', id,
        message: `${mode === 'clip' ? 'Clipping-Pfad' : 'Maske'} #${id || '?'} konnte nicht aufgelöst werden`
      });
      return null;
    }
    if (context.referenceStack.has(id)) {
      context.diagnose({
        code: mode === 'clip' ? 'SVG_CLIP_REFERENCE_CYCLE' : 'SVG_MASK_REFERENCE_CYCLE', severity: 'error', status: 'blocked',
        element: localName(definition), attribute: mode === 'clip' ? 'clip-path' : 'mask', id,
        message: `Zirkuläre ${mode === 'clip' ? 'Clipping-Referenz' : 'Maskenreferenz'} #${id} wurde sicher abgebrochen`
      });
      return null;
    }
    context.referenceStack.add(id);
    const definitionStyle = resolveStyle(definition, DEFAULT_STYLE, context);
    const bboxMatrix = objectBoundingBoxMatrix(matrix, sourceBounds, object);
    const unitsAttribute = mode === 'clip' ? 'clipPathUnits' : 'maskContentUnits';
    const objectBoundingBox = String(attr(definition, unitsAttribute) || (mode === 'clip' ? 'userSpaceOnUse' : 'userSpaceOnUse')).toLowerCase() === 'objectboundingbox';
    const definitionMatrix = elementMatrix(definition, objectBoundingBox ? bboxMatrix : matrix, context, definitionStyle);
    let candidates = [];
    try {
      candidates = childrenOf(definition).map((child) => convertElement(child, definitionMatrix, definitionStyle, context)).filter(Boolean);
    } finally {
      context.referenceStack.delete(id);
    }
    const converted = candidates.length === 1 ? candidates[0] : groupFromObjects(candidates, `${mode === 'clip' ? 'Clipping-Pfad' : 'Maske'} #${id}`, context, definitionStyle);
    const source = renderableReferenceGraphic(converted);
    return {
      source: source ? deepClone(source) : null, empty: !source,
      maskType: String(definitionStyle.maskType || 'luminance').toLowerCase() === 'alpha' ? 'alpha' : 'luminance',
      region: mode === 'clip' ? null : maskRegion(definition, matrix, bboxMatrix, context)
    };
  }

  function maskGroup(content, reference, mode, context) {
    const maskSource = reference.source; const clippingBounds = reference.region || maskSource;
    const left = Math.max(finite(content.x), finite(clippingBounds.x)); const top = Math.max(finite(content.y), finite(clippingBounds.y));
    const right = Math.min(finite(content.x) + Math.max(.0001, finite(content.width, 1)), finite(clippingBounds.x) + Math.max(.0001, finite(clippingBounds.width, 1)));
    const bottom = Math.min(finite(content.y) + Math.max(.0001, finite(content.height, 1)), finite(clippingBounds.y) + Math.max(.0001, finite(clippingBounds.height, 1)));
    const group = baseObject(mode === 'clip' ? 'clip-group' : 'mask-group', `${mode === 'clip' ? 'Clipping-Pfad' : 'SVG-Maske'}: ${content.name}`, context);
    group.x = round(left); group.y = round(top); group.width = round(Math.max(.0001, right - left)); group.height = round(Math.max(.0001, bottom - top));
    group.fill = 'none'; group.stroke = 'none'; group.strokeWidth = 0;
    if (mode === 'clip') maskSource.clipRole = 'clip';
    else {
      group.maskInverted = false; group.maskType = reference.maskType; group.maskUsesAppearance = true; maskSource.maskRole = 'mask';
      if (reference.region) group.maskRegion = {
        x: round(reference.region.x - left), y: round(reference.region.y - top),
        width: round(reference.region.width), height: round(reference.region.height)
      };
    }
    maskSource.visible = true;
    group.children = [shiftObject(content, -left, -top), shiftObject(maskSource, -left, -top)]; return group;
  }

  function applyReferences(object, element, matrix, style, context, sourceBounds = null) {
    let result = applySvgFilter(object, style.filter || attr(element, 'filter'), matrix, sourceBounds, context);
    const references = [
      { value: style.clipPath || attr(element, 'clip-path'), mode: 'clip' },
      { value: style.mask || attr(element, 'mask'), mode: 'mask' }
    ];
    references.forEach(({ value, mode }) => {
      const id = referenceId(value); if (!id) return;
      const reference = referenceObject(id, matrix, mode, context, result, sourceBounds);
      if (!reference) { context.warn(`${mode === 'clip' ? 'Clipping-Pfad' : 'Maske'} #${id} konnte nicht editierbar übernommen werden`); return; }
      if (reference.empty) {
        result.visible = false;
        context.diagnose({
          code: mode === 'clip' ? 'SVG_EMPTY_CLIP_SUPPORTED' : 'SVG_EMPTY_MASK_SUPPORTED', severity: 'info', status: 'supported',
          element: mode === 'clip' ? 'clippath' : 'mask', attribute: mode === 'clip' ? 'clip-path' : 'mask', id,
          message: `${mode === 'clip' ? 'Leerer Clipping-Pfad' : 'Leere Maske'} #${id} blendet das referenzierende Objekt vollständig aus`
        });
        return;
      }
      result = maskGroup(result, reference, mode, context);
    });
    return result;
  }

  function conditionalAttribute(element, name) {
    const value = element?.getAttribute?.(name);
    return { present: value != null, value: String(value ?? '').trim() };
  }

  function passesConditionalProcessing(element, context, diagnose = true) {
    const requiredFeatures = conditionalAttribute(element, 'requiredFeatures');
    const requiredExtensions = conditionalAttribute(element, 'requiredExtensions');
    const systemLanguage = conditionalAttribute(element, 'systemLanguage');
    let reason = '';
    if (requiredFeatures.present) {
      const features = requiredFeatures.value.split(/\s+/).filter(Boolean).map((feature) => feature.toLowerCase());
      if (!features.length || features.some((feature) => !context.supportedFeatures.has(feature))) reason = 'requiredFeatures wird nicht vollständig unterstützt';
    }
    if (!reason && requiredExtensions.present) {
      const extensions = requiredExtensions.value.split(/\s+/).filter(Boolean);
      if (!extensions.length || extensions.some((extension) => !context.supportedExtensions.has(extension))) reason = 'requiredExtensions wird nicht vollständig unterstützt';
    }
    if (!reason && systemLanguage.present) {
      const ranges = systemLanguage.value.split(',').map((language) => language.trim().toLowerCase()).filter(Boolean);
      const matches = ranges.length && ranges.some((range) => context.languages.some((language) => language === range || language.startsWith(`${range}-`)));
      if (!matches) reason = 'systemLanguage passt nicht zur aktuellen Sprache';
    }
    if (!reason) return true;
    if (diagnose) context.diagnose({
      code: 'SVG_CONDITIONAL_ELEMENT_SKIPPED', severity: 'info', status: 'supported', element: localName(element), id: attr(element, 'id'),
      message: `${safeName(element, `<${localName(element) || '?'}>`)} wurde durch SVG-Bedingungen ausgeblendet: ${reason}`
    });
    return false;
  }

  function convertSwitch(element, parentMatrix, parentStyle, context) {
    const style = resolveStyle(element, parentStyle, context); const matrix = elementMatrix(element, parentMatrix, context, style);
    const selected = childrenOf(element).find((child) => passesConditionalProcessing(child, context));
    if (!selected) return null;
    const object = convertElement(selected, matrix, style, context); if (!object) return null;
    const group = groupFromObjects([object], safeName(element, 'SVG-Switch'), context, style);
    if (group) context.diagnose({
      code: 'SVG_SWITCH_SELECTED', severity: 'info', status: 'supported', element: 'switch', id: attr(element, 'id'),
      message: '<switch> wurde auf den ersten passenden Zweig reduziert'
    });
    return group ? applyReferences(group, element, matrix, style, context) : null;
  }

  function convertGroup(element, parentMatrix, parentStyle, context) {
    const style = resolveStyle(element, parentStyle, context); const matrix = elementMatrix(element, parentMatrix, context, style);
    const objects = childrenOf(element).map((child) => convertElement(child, matrix, style, context)).filter(Boolean);
    const group = groupFromObjects(objects, safeName(element, 'SVG-Gruppe'), context, style);
    return group ? applyReferences(group, element, matrix, style, context) : null;
  }

  function translatedMatrix(matrix, x, y) {
    return SvgPath.multiplyMatrices(matrix, [1, 0, 0, 1, finite(x), finite(y)]);
  }

  function withViewport(context, viewport, callback) {
    const previous = context.viewport; context.viewport = viewport;
    try { return callback(); }
    finally { context.viewport = previous; }
  }

  function viewportDimension(instance, target, name, relative, viewport, fontSize = 16) {
    for (const element of [instance, target]) {
      const value = element ? String(attr(element, name) || '').trim() : '';
      if (!value || /^auto$/i.test(value)) continue;
      const parsed = parseLength(value, relative, NaN, fontSize, viewport);
      if (Number.isFinite(parsed)) return Math.max(0, parsed);
    }
    return Math.max(0, relative);
  }

  function clipToViewport(object, matrix, width, height, name, context) {
    const clipPaths = SvgPath.transformSubpaths([{
      points: [[0, 0], [width, 0], [width, height], [0, height]],
      segments: [null, null, null, null], closed: true
    }], matrix);
    const clipStyle = { ...DEFAULT_STYLE, fill: '#000000', stroke: 'none', strokeWidth: 0 };
    const clipSource = objectsFromPaths(
      clipPaths, 'rect', `${name} – Viewport`, clipStyle, SvgPath.identityMatrix(),
      { x: 0, y: 0, width, height }, context
    );
    return clipSource
      ? maskGroup(object, { source: clipSource, maskType: 'luminance', region: null }, 'clip', context)
      : object;
  }

  function convertViewportContainer(element, baseMatrix, parentStyle, context, instance = null, matrixIncludesElementTransform = false) {
    const outerViewport = context.viewport; const viewBox = parseViewBox(attr(element, 'viewBox'));
    const style = resolveStyle(element, parentStyle, context);
    const width = viewportDimension(instance, element, 'width', outerViewport.width, outerViewport, style.fontSize);
    const height = viewportDimension(instance, element, 'height', outerViewport.height, outerViewport, style.fontSize);
    if (!(width > 0 && height > 0)) return null;
    let matrix = matrixIncludesElementTransform ? baseMatrix : elementMatrix(element, baseMatrix, context, style);
    const viewportMatrix = matrix;
    matrix = SvgPath.multiplyMatrices(matrix, viewBoxMatrix(
      viewBox, width, height,
      attr(instance, 'preserveAspectRatio') || attr(element, 'preserveAspectRatio')
    ));
    const sourceViewport = viewBox ? { width: viewBox.width, height: viewBox.height } : { width, height };
    const objects = withViewport(context, sourceViewport, () => childrenOf(element)
      .map((child) => convertElement(child, matrix, style, context)).filter(Boolean));
    const name = safeName(element, localName(element) === 'symbol' ? 'SVG-Symbol' : 'Verschachteltes SVG');
    const group = groupFromObjects(objects, name, context, style);
    if (!group) return null;
    const clipped = String(style.overflow || 'hidden').trim().toLowerCase() === 'visible'
      ? group
      : clipToViewport(group, viewportMatrix, width, height, name, context);
    return applyReferences(clipped, element, matrix, style, context);
  }

  function reapplyContextPaints(rootObject, context, requestedBounds = null) {
    if (!rootObject) return;
    const contextBounds = requestedBounds || {
      x: finite(rootObject.x), y: finite(rootObject.y),
      width: Math.max(.0001, finite(rootObject.width, 1)), height: Math.max(.0001, finite(rootObject.height, 1))
    };
    const visit = (object, parentX = 0, parentY = 0) => {
      if (!object) return;
      const absoluteX = parentX + finite(object.x); const absoluteY = parentY + finite(object.y);
      if (Array.isArray(object.children)) object.children.forEach((child) => visit(child, absoluteX, absoluteY));
      const synthetic = { ...object, x: absoluteX, y: absoluteY };
      const replace = (kind) => {
        const suffix = kind === 'fill' ? 'Fill' : 'Stroke';
        const value = object[`svgContext${suffix}Paint`]; if (!value) return;
        const paint = gradientPaint(value, synthetic, SvgPath.identityMatrix(), contextBounds, 1, context)
          || patternPaint(value, synthetic, SvgPath.identityMatrix(), contextBounds, 1, context);
        if (paint && paint.type !== 'svg-none') object[`${kind}Paint`] = paint;
        else delete object[`${kind}Paint`];
        delete object[`svgContext${suffix}Paint`]; delete object[`svgContext${suffix}Opacity`];
      };
      replace('fill'); replace('stroke');
    };
    visit(rootObject);
  }

  function convertUse(element, parentMatrix, parentStyle, context) {
    const href = hrefOf(element); const targetId = localHrefId(href);
    if (!targetId) {
      if (!href) context.diagnose({
        code: 'SVG_USE_REFERENCE_MISSING', severity: 'error', status: 'missing', element: 'use', attribute: 'href', id: attr(element, 'id'),
        message: `<use> „${safeText(attr(element, 'id'), 128) || '(ohne ID)'}“ besitzt kein lokales Referenzziel`
      });
      return null;
    }
    const target = context.definitions.get(targetId); if (!target) return null;
    if (context.referenceStack.has(targetId)) {
      const chain = [...context.referenceStack, targetId].map((id) => `#${id}`).join(' → ');
      context.diagnose({
        code: 'SVG_USE_REFERENCE_CYCLE', severity: 'error', status: 'blocked', element: 'use', attribute: 'href', id: targetId,
        message: `Zyklische <use>-Referenz wurde sicher abgebrochen: ${chain}`
      });
      return null;
    }
    if (context.referenceStack.size >= 128) {
      context.diagnose({
        code: 'SVG_USE_REFERENCE_DEPTH_EXCEEDED', severity: 'error', status: 'blocked', element: 'use', attribute: 'href', id: targetId,
        message: `<use>-Referenz #${targetId} überschreitet die sichere Verschachtelungstiefe`
      });
      return null;
    }

    const style = resolveStyle(element, parentStyle, context);
    style.contextFill = style.fill; style.contextStroke = style.stroke;
    let matrix = elementMatrix(element, parentMatrix, context, style);
    matrix = translatedMatrix(
      matrix,
      parseLength(style.x, context.viewport.width, 0, style.fontSize, context.viewport),
      parseLength(style.y, context.viewport.height, 0, style.fontSize, context.viewport)
    );
    context.referenceStack.add(targetId);
    const previousCssSelectorBoundary = context.cssSelectorBoundary || null;
    context.cssSelectorBoundary = target;
    let expanded = null;
    try {
      const targetTag = localName(target);
      if (targetTag === 'svg') {
        const targetStyle = resolveStyle(target, style, context);
        let targetMatrix = elementMatrix(target, matrix, context, targetStyle);
        targetMatrix = translatedMatrix(
          targetMatrix,
          parseLength(targetStyle.x, context.viewport.width, 0, targetStyle.fontSize, context.viewport),
          parseLength(targetStyle.y, context.viewport.height, 0, targetStyle.fontSize, context.viewport)
        );
        expanded = convertViewportContainer(target, targetMatrix, style, context, element, true);
      }
      else if (targetTag === 'symbol') expanded = convertViewportContainer(target, matrix, style, context, element);
      else expanded = convertElement(target, matrix, style, context);
    } finally {
      context.referenceStack.delete(targetId);
      context.cssSelectorBoundary = previousCssSelectorBoundary;
    }
    if (!expanded) {
      const targetTag = localName(target);
      if (DEFINITION_TAGS.has(targetTag) && targetTag !== 'symbol') context.diagnose({
        code: 'SVG_USE_TARGET_UNSUPPORTED', status: 'ignored', element: 'use', attribute: 'href', id: targetId,
        message: `<use> kann das Definitionsziel <${targetTag}> #${targetId} nicht als Grafik expandieren`
      });
      return null;
    }
    reapplyContextPaints(expanded, context);
    const group = groupFromObjects([expanded], safeName(element, `SVG-Instanz #${targetId}`), context, style);
    if (!group) return null;
    context.diagnose({
      code: 'SVG_USE_EXPANDED', severity: 'info', status: 'supported', element: 'use', attribute: attr(element, 'href') ? 'href' : 'xlink:href', id: attr(element, 'id') || targetId,
      message: `<use> #${targetId} wurde als eigenständig editierbare Kopie importiert`
    });
    return applyReferences(group, element, matrix, style, context);
  }

  function convertNestedSvg(element, parentMatrix, parentStyle, context) {
    const style = resolveStyle(element, parentStyle, context);
    let matrix = elementMatrix(element, parentMatrix, context, style);
    matrix = translatedMatrix(
      matrix,
      parseLength(style.x, context.viewport.width, 0, style.fontSize, context.viewport),
      parseLength(style.y, context.viewport.height, 0, style.fontSize, context.viewport)
    );
    return convertViewportContainer(element, matrix, parentStyle, context, null, true);
  }

  function convertElement(element, parentMatrix, parentStyle, context) {
    if (!checkElementSafety(element, context)) return null;
    if (!passesConditionalProcessing(element, context)) return null;
    const tag = localName(element);
    if (DEFINITION_TAGS.has(tag) || tag === 'style' || tag === 'metadata' || tag === 'title' || tag === 'desc') return null;
    if (tag === 'use') return convertUse(element, parentMatrix, parentStyle, context);
    if (tag === 'svg') return convertNestedSvg(element, parentMatrix, parentStyle, context);
    if (tag === 'image') return convertImage(element, parentMatrix, parentStyle, context);
    if (GEOMETRY_TAGS.has(tag)) return convertPrimitive(element, parentMatrix, parentStyle, context);
    if (tag === 'text') return convertText(element, parentMatrix, parentStyle, context);
    if (tag === 'switch') return convertSwitch(element, parentMatrix, parentStyle, context);
    if (CONTAINER_TAGS.has(tag)) return convertGroup(element, parentMatrix, parentStyle, context);
    return null;
  }

  function isLayerGroup(element) {
    return localName(element) === 'g' && Boolean(attr(element, 'data-layer-id') || attr(element, 'data-layer-name') || attr(element, 'inkscape:groupmode') === 'layer');
  }

  function convertLayer(element, rootMatrix, rootStyle, context) {
    if (!passesConditionalProcessing(element, context)) return null;
    const style = resolveStyle(element, rootStyle, context); const matrix = elementMatrix(element, rootMatrix, context, style);
    const deferredStart = context.deferredObjects.length;
    let objects = childrenOf(element).map((child) => convertElement(child, matrix, style, context)).filter(Boolean);
    const deferred = context.deferredObjects.splice(deferredStart); if (deferred.length) objects = [...deferred, ...objects];
    if (style.opacity < 1 && objects.length) {
      const opacityGroup = groupFromObjects(objects, safeName(element, translated('ui.svgLayer', 'SVG-Ebene')), context, style); objects = opacityGroup ? [opacityGroup] : [];
    }
    return {
      id: context.createId('layer'), name: safeName(element, translated('ui.svgLayer', 'SVG-Ebene')),
      visible: style.display !== 'none' && style.visibility !== 'hidden' && style.visibility !== 'collapse', locked: false, blendMode: normalizeBlendMode(style.blendMode), objects
    };
  }

  function parse(markup, options = {}) {
    const document = parseSvgDocument(markup, options);
    const svg = document.documentElement;
    const prepared = { elements: documentElements(document), stylesheet: null };
    const analysis = analyzeDocument(document, prepared);
    const viewBox = parseViewBox(attr(svg, 'viewBox'));
    const attributeWidth = Math.max(1, parseLength(attr(svg, 'width'), viewBox?.width || 300, viewBox?.width || 300));
    const attributeHeight = Math.max(1, parseLength(attr(svg, 'height'), viewBox?.height || 150, viewBox?.height || 150));
    const sizingViewport = { width: attributeWidth, height: attributeHeight };
    const sizingStyle = resolveStyle(svg, DEFAULT_STYLE, { cssRules: prepared.stylesheet.rules });
    const width = Math.max(1, round(parseLength(sizingStyle.width, attributeWidth, attributeWidth, sizingStyle.fontSize, sizingViewport)));
    const height = Math.max(1, round(parseLength(sizingStyle.height, attributeHeight, attributeHeight, sizingStyle.fontSize, sizingViewport)));
    const context = createContext(options, document, viewBox ? { width: viewBox.width, height: viewBox.height } : { width, height }, analysis.diagnostics, prepared.stylesheet);
    prepared.elements.forEach((element) => {
      const id = attr(element, 'id'); if (id && !context.definitions.has(id)) context.definitions.set(id, element);
    });
    const rootStyle = resolveStyle(svg, DEFAULT_STYLE, context);
    let rootMatrix = viewBoxMatrix(viewBox, width, height, attr(svg, 'preserveAspectRatio'));
    rootMatrix = elementMatrix(svg, rootMatrix, context, rootStyle);
    const preserveRootMatrix = rootMatrix.some((value, index) => Math.abs(value - [1, 0, 0, 1, 0, 0][index]) > 1e-10);
    const conversionRootMatrix = preserveRootMatrix ? SvgPath.identityMatrix() : rootMatrix;
    const direct = childrenOf(svg).filter((element) => !DEFINITION_TAGS.has(localName(element)) && !['style', 'metadata', 'title', 'desc'].includes(localName(element)));
    const layerElements = direct.filter(isLayerGroup); const layers = [];
    if (layerElements.length) {
      const nonLayers = direct.filter((element) => !isLayerGroup(element));
      if (nonLayers.length) {
        const deferredStart = context.deferredObjects.length;
        const objects = nonLayers.map((element) => convertElement(element, conversionRootMatrix, rootStyle, context)).filter(Boolean);
        const deferred = context.deferredObjects.splice(deferredStart);
        if (objects.length || deferred.length) layers.push({ id: context.createId('layer'), name: 'SVG', visible: true, locked: false, blendMode: 'normal', objects: [...deferred, ...objects] });
      }
      layerElements.forEach((element) => { const layer = convertLayer(element, conversionRootMatrix, rootStyle, context); if (layer) layers.push(layer); });
    } else {
      const deferredStart = context.deferredObjects.length;
      const objects = direct.map((element) => convertElement(element, conversionRootMatrix, rootStyle, context)).filter(Boolean);
      const deferred = context.deferredObjects.splice(deferredStart);
      layers.push({ id: context.createId('layer'), name: safeName(svg, 'SVG'), visible: true, locked: false, blendMode: 'normal', objects: [...deferred, ...objects] });
    }
    if (!layers.length) layers.push({ id: context.createId('layer'), name: 'SVG', visible: true, locked: false, blendMode: 'normal', objects: [] });
    if (preserveRootMatrix) layers.forEach((layer) => layer.objects.forEach((object) => {
      const existing = Array.isArray(object.svgTransformMatrix) && object.svgTransformMatrix.length >= 6 ? object.svgTransformMatrix : SvgPath.identityMatrix();
      object.svgTransformMatrix = SvgPath.multiplyMatrices(rootMatrix, existing);
    }));
    const diagnostics = context.getDiagnostics();
    return {
      width, height, layers, warnings: context.warnings, diagnostics,
      analysis: { ...analysis, diagnostics, diagnosticSummary: summarizeDiagnostics(diagnostics) }
    };
  }

  const api = Object.freeze({
    parse, analyze, parseLength, parseOpacity, normalizeColor, parseStyleAttribute, resolveStyle,
    parseViewBox, viewBoxMatrix, referenceId
  });
  root.PyroSvgImport = api;
  if (typeof module === 'object' && module?.exports) module.exports = api;
})(typeof globalThis !== 'undefined' ? globalThis : window);
