(function (root, factory) {
  const api = factory();
  if (typeof module === "object" && module.exports) module.exports = api;
  root.JianglaiVectorExporters = api;
})(typeof globalThis !== "undefined" ? globalThis : this, function () {
  "use strict";

  const MM_TO_PT = 72 / 25.4;
  const MAX_COORDINATE = 10_000_000;
  const MAX_NODES = 500_000;
  const FORMAT_INFO = Object.freeze({
    pdf: Object.freeze({ format: "pdf", mime: "application/pdf", extension: "pdf", label: "PDF" }),
    eps: Object.freeze({ format: "eps", mime: "application/postscript", extension: "eps", label: "EPS" }),
    dxf: Object.freeze({ format: "dxf", mime: "application/dxf", extension: "dxf", label: "DXF" })
  });

  const finiteNumber = (value, label) => {
    if (value === null || value === "" || typeof value === "boolean") throw new TypeError(`${label}必须是有限数值`);
    const number = Number(value);
    if (!Number.isFinite(number)) throw new TypeError(`${label}必须是有限数值`);
    if (Math.abs(number) > MAX_COORDINATE) throw new RangeError(`${label}超出可安全导出的范围`);
    return number;
  };

  const positiveNumber = (value, label) => {
    const number = finiteNumber(value, label);
    if (number <= 0) throw new RangeError(`${label}必须大于 0`);
    return number;
  };

  const formatNumber = value => {
    const number = Math.abs(value) < 1e-9 ? 0 : value;
    return Number(number.toFixed(6)).toString();
  };

  function getFormatInfo(format) {
    const key = String(format || "").trim().toLowerCase();
    const info = FORMAT_INFO[key];
    if (!info) throw new RangeError(`不支持的生产格式：${format || "未指定"}`);
    return { ...info };
  }

  function parseColor(value) {
    if (Array.isArray(value) && value.length >= 3) {
      return value.slice(0, 3).map((item, index) => {
        const number = finiteNumber(item, `颜色通道 ${index + 1}`);
        return Math.max(0, Math.min(255, Math.round(number)));
      });
    }
    const source = String(value || "#000000").trim();
    const short = /^#([0-9a-f]{3})$/i.exec(source);
    if (short) return short[1].split("").map(character => parseInt(character + character, 16));
    const full = /^#([0-9a-f]{6})$/i.exec(source);
    if (full) return [0, 2, 4].map(offset => parseInt(full[1].slice(offset, offset + 2), 16));
    throw new TypeError(`图层颜色无效：${source}`);
  }

  function parseCmyk(value) {
    if (!Array.isArray(value) || value.length < 4) return null;
    return value.slice(0, 4).map((item, index) => {
      const number = finiteNumber(item, `CMYK 通道 ${index + 1}`);
      return Math.max(0, Math.min(1, number));
    });
  }

  function normalizedContour(contour, layerIndex, contourIndex, budget) {
    const source = Array.isArray(contour) ? contour : contour?.points;
    if (!Array.isArray(source)) {
      throw new TypeError(`图层 ${layerIndex + 1} 的路径 ${contourIndex + 1} 缺少节点数据`);
    }
    const points = source.map((point, pointIndex) => ({
      x: finiteNumber(point?.x, `路径 ${contourIndex + 1} 节点 ${pointIndex + 1} x`),
      y: finiteNumber(point?.y, `路径 ${contourIndex + 1} 节点 ${pointIndex + 1} y`)
    }));
    if (points.length > 3) {
      const first = points[0];
      const last = points[points.length - 1];
      if (first.x === last.x && first.y === last.y) points.pop();
    }
    if (points.length < 3) {
      throw new RangeError(`图层 ${layerIndex + 1} 的路径 ${contourIndex + 1} 至少需要 3 个节点`);
    }
    budget.nodes += points.length;
    if (budget.nodes > MAX_NODES) throw new RangeError(`生产文件节点不能超过 ${MAX_NODES.toLocaleString("en-US")} 个`);
    return points;
  }

  function normalizedStroke(stroke, layerIndex, strokeIndex, budget) {
    const source = Array.isArray(stroke) ? stroke : stroke?.points;
    if (!Array.isArray(source)) throw new TypeError(`图层 ${layerIndex + 1} 的开放线 ${strokeIndex + 1} 缺少节点数据`);
    const points = source.map((point, pointIndex) => ({
      x: finiteNumber(point?.x, `开放线 ${strokeIndex + 1} 节点 ${pointIndex + 1} x`),
      y: finiteNumber(point?.y, `开放线 ${strokeIndex + 1} 节点 ${pointIndex + 1} y`)
    }));
    if (points.length < 2) throw new RangeError(`图层 ${layerIndex + 1} 的开放线 ${strokeIndex + 1} 至少需要 2 个节点`);
    budget.nodes += points.length;
    if (budget.nodes > MAX_NODES) throw new RangeError(`生产文件节点不能超过 ${MAX_NODES.toLocaleString("en-US")} 个`);
    return points;
  }

  function transformedPoint(x, y, transform, label) {
    const [a, b, c, d, e, f] = transform;
    return {
      x: finiteNumber(a * x + c * y + e, `${label} x`),
      y: finiteNumber(b * x + d * y + f, `${label} y`)
    };
  }

  function nativePathTokens(source, layerIndex) {
    const value = String(source || "").trim();
    if (!value || !/^[0-9eE+.,\-\sMmLlHhVvCcSsQqTtZz]+$/.test(value)) {
      throw new TypeError(`图层 ${layerIndex + 1} 的字体贝塞尔路径无效`);
    }
    const tokens = value.match(/[MmLlHhVvCcSsQqTtZz]|[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g) || [];
    const compactSource = value.replace(/[\s,]+/g, "");
    const compactTokens = tokens.join("");
    if (!tokens.length || compactSource !== compactTokens) {
      throw new TypeError(`图层 ${layerIndex + 1} 的字体贝塞尔路径包含不支持的数据`);
    }
    return tokens;
  }

  function normalizedNativePath(source, matrix, layerIndex, budget) {
    const transform = Array.isArray(matrix) ? matrix.map((value, index) => finiteNumber(value, `图层 ${layerIndex + 1} 字体矩阵 ${index + 1}`)) : [];
    if (transform.length !== 6) throw new TypeError(`图层 ${layerIndex + 1} 的字体变换矩阵必须包含 6 个数值`);
    const tokens = nativePathTokens(source, layerIndex);
    const argumentCount = { M: 2, L: 2, H: 1, V: 1, C: 6, S: 4, Q: 4, T: 2, Z: 0 };
    const output = [];
    let index = 0;
    let command = "";
    let current = { x: 0, y: 0 };
    let subpathStart = { x: 0, y: 0 };
    let previousCubicControl = null;
    let previousQuadraticControl = null;
    const number = label => {
      if (index >= tokens.length || /^[A-Za-z]$/.test(tokens[index])) throw new TypeError(`图层 ${layerIndex + 1} 的字体路径缺少${label}`);
      return finiteNumber(tokens[index++], `图层 ${layerIndex + 1} 字体路径 ${label}`);
    };
    const absolute = (x, y, relative) => relative ? { x: current.x + x, y: current.y + y } : { x, y };
    while (index < tokens.length) {
      if (/^[A-Za-z]$/.test(tokens[index])) command = tokens[index++];
      if (!command) throw new TypeError(`图层 ${layerIndex + 1} 的字体路径缺少命令`);
      const upper = command.toUpperCase();
      const relative = command !== upper;
      const count = argumentCount[upper];
      if (count == null) throw new TypeError(`图层 ${layerIndex + 1} 的字体路径命令 ${command} 暂不支持`);
      if (upper === "Z") {
        output.push({ op: "Z", points: [] });
        current = { ...subpathStart };
        previousCubicControl = previousQuadraticControl = null;
        command = "";
        continue;
      }
      if (index + count > tokens.length) throw new TypeError(`图层 ${layerIndex + 1} 的字体路径命令 ${command} 参数不完整`);
      if (upper === "M" || upper === "L") {
        const end = absolute(number("x"), number("y"), relative);
        const op = upper === "M" ? "M" : "L";
        output.push({ op, points: [transformedPoint(end.x, end.y, transform, "字体路径节点")] });
        current = end;
        if (upper === "M") {
          subpathStart = { ...end };
          command = relative ? "l" : "L";
        }
        previousCubicControl = previousQuadraticControl = null;
      } else if (upper === "H") {
        const end = { x: relative ? current.x + number("x") : number("x"), y: current.y };
        output.push({ op: "L", points: [transformedPoint(end.x, end.y, transform, "字体路径节点")] });
        current = end;
        previousCubicControl = previousQuadraticControl = null;
      } else if (upper === "V") {
        const end = { x: current.x, y: relative ? current.y + number("y") : number("y") };
        output.push({ op: "L", points: [transformedPoint(end.x, end.y, transform, "字体路径节点")] });
        current = end;
        previousCubicControl = previousQuadraticControl = null;
      } else if (upper === "C") {
        const control1 = absolute(number("c1x"), number("c1y"), relative);
        const control2 = absolute(number("c2x"), number("c2y"), relative);
        const end = absolute(number("x"), number("y"), relative);
        output.push({ op: "C", points: [control1, control2, end].map((point, pointIndex) => transformedPoint(point.x, point.y, transform, `字体曲线节点 ${pointIndex + 1}`)) });
        current = end;
        previousCubicControl = control2;
        previousQuadraticControl = null;
      } else if (upper === "S") {
        const control1 = previousCubicControl
          ? { x: current.x * 2 - previousCubicControl.x, y: current.y * 2 - previousCubicControl.y }
          : { ...current };
        const control2 = absolute(number("c2x"), number("c2y"), relative);
        const end = absolute(number("x"), number("y"), relative);
        output.push({ op: "C", points: [control1, control2, end].map((point, pointIndex) => transformedPoint(point.x, point.y, transform, `字体曲线节点 ${pointIndex + 1}`)) });
        current = end;
        previousCubicControl = control2;
        previousQuadraticControl = null;
      } else if (upper === "Q" || upper === "T") {
        const control = upper === "Q"
          ? absolute(number("qx"), number("qy"), relative)
          : previousQuadraticControl
            ? { x: current.x * 2 - previousQuadraticControl.x, y: current.y * 2 - previousQuadraticControl.y }
            : { ...current };
        const end = absolute(number("x"), number("y"), relative);
        const control1 = {
          x: current.x + (control.x - current.x) * 2 / 3,
          y: current.y + (control.y - current.y) * 2 / 3
        };
        const control2 = {
          x: end.x + (control.x - end.x) * 2 / 3,
          y: end.y + (control.y - end.y) * 2 / 3
        };
        output.push({ op: "C", points: [control1, control2, end].map((point, pointIndex) => transformedPoint(point.x, point.y, transform, `字体曲线节点 ${pointIndex + 1}`)) });
        current = end;
        previousQuadraticControl = control;
        previousCubicControl = null;
      }
      budget.nodes += output[output.length - 1].points.length || 1;
      if (budget.nodes > MAX_NODES) throw new RangeError(`生产文件节点不能超过 ${MAX_NODES.toLocaleString("en-US")} 个`);
    }
    if (!output.length || output[0].op !== "M") throw new TypeError(`图层 ${layerIndex + 1} 的字体路径必须从 M 命令开始`);
    return output;
  }

  function normalizeDocument(result, options = {}) {
    if (!result || !Array.isArray(result.layers)) throw new TypeError("请先生成包含图层的矢量结果");
    const sourceWidth = positiveNumber(result.width, "矢量画布宽度");
    const sourceHeight = positiveNumber(result.height, "矢量画布高度");
    const widthMm = positiveNumber(options.widthMm ?? options.physicalWidthMm, "成品宽度（mm）");
    if (widthMm < 0.1 || widthMm > 100_000) throw new RangeError("成品宽度必须在 0.1–100000 mm 之间");
    const heightMm = widthMm * sourceHeight / sourceWidth;
    if (!Number.isFinite(heightMm) || heightMm < 0.1 || heightMm > 100_000) {
      throw new RangeError("按原图比例计算的成品高度超出 0.1–100000 mm 范围");
    }
    const smoothing = Math.max(0, Math.min(1, finiteNumber(options.smoothing ?? result.options?.smoothing ?? 0, "平滑度")));
    const budget = { nodes: 0 };
    const layers = [];
    result.layers.forEach((layer, layerIndex) => {
      if (layer?.visible === false) return;
      const hasContours = Array.isArray(layer?.contours);
      const hasStrokes = Array.isArray(layer?.strokes);
      const hasNativeStrokes = Array.isArray(layer?.nativeStrokePaths);
      if (!hasContours && !hasStrokes && !hasNativeStrokes) throw new TypeError(`图层 ${layerIndex + 1} 缺少 contours、strokes 或 nativeStrokePaths，无法生成可编辑的生产文件`);
      const contours = (layer.contours || []).map((contour, contourIndex) => normalizedContour(contour, layerIndex, contourIndex, budget));
      const strokes = (layer.strokes || []).map((stroke, strokeIndex) => normalizedStroke(stroke, layerIndex, strokeIndex, budget));
      const nativePath = layer.nativePathD
        ? normalizedNativePath(layer.nativePathD, layer.nativeTransform, layerIndex, budget)
        : [];
      const nativeStrokes = (layer.nativeStrokePaths || []).map(item =>
        normalizedNativePath(item?.d, item?.transform, layerIndex, budget)
      );
      if (!contours.length && !strokes.length && !nativePath.length && !nativeStrokes.length) return;
      layers.push({
        id: String(layer.id || `layer-${layerIndex + 1}`),
        name: String(layer.name || layer.id || `VEC_${layerIndex + 1}`),
        role: String(layer.role || "artwork"),
        color: parseColor(layer.color),
        cmyk: parseCmyk(layer.cmyk),
        spotName: String(layer.spotName || "").replace(/[^A-Za-z0-9_.-]+/g, "_").slice(0, 64),
        contours,
        strokes,
        nativePath,
        nativeStrokes,
        strokeWidth: Math.max(0.1, Math.min(1000, finiteNumber(layer.strokeWidth ?? 1, `图层 ${layerIndex + 1} 线宽`))),
        strokeWidthMm: layer.strokeWidthMm == null ? null : Math.max(0.01, Math.min(100, finiteNumber(layer.strokeWidthMm, `图层 ${layerIndex + 1} 毫米线宽`))),
        smoothing: Math.max(0, Math.min(1, finiteNumber(layer.smoothing ?? smoothing, `图层 ${layerIndex + 1} 平滑度`)))
      });
    });
    if (!layers.length) throw new Error("没有可导出的可见闭合路径");
    return {
      sourceWidth,
      sourceHeight,
      widthMm,
      heightMm,
      widthPt: widthMm * MM_TO_PT,
      heightPt: heightMm * MM_TO_PT,
      layers,
      nodeCount: budget.nodes,
      geometryContract:result.geometryContract ? JSON.parse(JSON.stringify(result.geometryContract)) : null,
      filename: safeFilename(options.filename || options.name || "jianglai-vector")
    };
  }

  function safeFilename(value) {
    const cleaned = String(value || "jianglai-vector")
      .replace(/[\\/:*?"<>|\u0000-\u001f]/g, "-")
      .replace(/^\.+|\.+$/g, "")
      .trim()
      .slice(0, 120);
    return cleaned || "jianglai-vector";
  }

  function appendContourCommands(output, points, smoothing, operators) {
    const point = item => `${formatNumber(item.x)} ${formatNumber(item.y)}`;
    output.push(`${point(points[0])} ${operators.move}`);
    if (smoothing < 0.06 || points.length < 4) {
      for (let index = 1; index < points.length; index += 1) output.push(`${point(points[index])} ${operators.line}`);
    } else {
      const factor = smoothing * .3;
      for (let index = 0; index < points.length; index += 1) {
        const p0 = points[(index - 1 + points.length) % points.length];
        const p1 = points[index];
        const p2 = points[(index + 1) % points.length];
        const p3 = points[(index + 2) % points.length];
        const segmentLength = Math.max(.001, Math.hypot(p2.x - p1.x, p2.y - p1.y));
        const previousLength = Math.max(.001, Math.hypot(p1.x - p0.x, p1.y - p0.y));
        const nextLength = Math.max(.001, Math.hypot(p3.x - p2.x, p3.y - p2.y));
        const tangent1Length = Math.max(.001, Math.hypot(p2.x - p0.x, p2.y - p0.y));
        const tangent2Length = Math.max(.001, Math.hypot(p3.x - p1.x, p3.y - p1.y));
        const handle1 = Math.min(segmentLength * .32, Math.min(segmentLength, previousLength) * factor);
        const handle2 = Math.min(segmentLength * .32, Math.min(segmentLength, nextLength) * factor);
        const c1 = {
          x: p1.x + (p2.x - p0.x) / tangent1Length * handle1,
          y: p1.y + (p2.y - p0.y) / tangent1Length * handle1
        };
        const c2 = {
          x: p2.x - (p3.x - p1.x) / tangent2Length * handle2,
          y: p2.y - (p3.y - p1.y) / tangent2Length * handle2
        };
        output.push(`${point(c1)} ${point(c2)} ${point(p2)} ${operators.curve}`);
      }
    }
    output.push(operators.close);
  }

  function appendNativePathCommands(output, commands, operators) {
    for (const command of commands) {
      if (command.op === "M" || command.op === "L") {
        output.push(`${formatNumber(command.points[0].x)} ${formatNumber(command.points[0].y)} ${command.op === "M" ? operators.move : operators.line}`);
      } else if (command.op === "C") {
        const [control1, control2, end] = command.points;
        output.push(`${formatNumber(control1.x)} ${formatNumber(control1.y)} ${formatNumber(control2.x)} ${formatNumber(control2.y)} ${formatNumber(end.x)} ${formatNumber(end.y)} ${operators.curve}`);
      } else if (command.op === "Z") {
        output.push(operators.close);
      }
    }
  }

  function flattenedNativeSubpaths(commands, steps = 12) {
    const output = [];
    let points = [];
    let current = null;
    let start = null;
    let closed = false;
    const finish = () => {
      if (points.length > 1) output.push({ points, closed });
      points = [];
      current = start = null;
      closed = false;
    };
    for (const command of commands) {
      if (command.op === "M") {
        finish();
        current = { ...command.points[0] };
        start = { ...current };
        points.push({ ...current });
      } else if (command.op === "L" && current) {
        current = { ...command.points[0] };
        points.push({ ...current });
      } else if (command.op === "C" && current) {
        const origin = { ...current };
        const [control1, control2, end] = command.points;
        for (let step = 1; step <= steps; step += 1) {
          const t = step / steps;
          const inverse = 1 - t;
          points.push({
            x: inverse ** 3 * origin.x + 3 * inverse ** 2 * t * control1.x + 3 * inverse * t ** 2 * control2.x + t ** 3 * end.x,
            y: inverse ** 3 * origin.y + 3 * inverse ** 2 * t * control1.y + 3 * inverse * t ** 2 * control2.y + t ** 3 * end.y
          });
        }
        current = { ...end };
      } else if (command.op === "Z" && current && start) {
        closed = true;
        current = { ...start };
        finish();
      }
    }
    finish();
    return output;
  }

  function asciiBytes(source) {
    const output = new Uint8Array(source.length);
    for (let index = 0; index < source.length; index += 1) {
      const code = source.charCodeAt(index);
      if (code > 255) throw new TypeError("PDF 内部数据必须为单字节字符");
      output[index] = code;
    }
    return output;
  }

  function outputRecord(format, document, data) {
    const info = getFormatInfo(format);
    return {
      ...info,
      filename: `${document.filename}.${info.extension}`,
      widthMm: document.widthMm,
      heightMm: document.heightMm,
      nodeCount: document.nodeCount,
      geometryContract:document.geometryContract,
      data
    };
  }

  function sourceStrokeWidth(layer, document) {
    if (!(layer.strokeWidthMm > 0)) return layer.strokeWidth;
    return layer.strokeWidthMm / (document.widthMm / document.sourceWidth);
  }

  function isStructureRole(role) {
    return ["dieline", "cutline", "crease", "fold"].includes(String(role || "").toLowerCase());
  }

  function spotResourceName(spotName) {
    return `CS_${String(spotName || "").replace(/[^A-Za-z0-9_.-]+/g, "_")}`;
  }

  function structureSpotLayers(document) {
    const spots = [];
    for (const layer of document.layers) {
      if (!isStructureRole(layer.role) || !layer.spotName || (!layer.strokes.length && !layer.nativeStrokes.length)) continue;
      if (spots.some(item => item.name === layer.spotName)) continue;
      spots.push(layer);
    }
    return spots;
  }

  function pdfFillColor(layer) {
    if (layer.spotName === "FOIL_WHITE") return "/CS_FOIL_WHITE cs 1 scn";
    if (layer.cmyk) return `${layer.cmyk.map(formatNumber).join(" ")} k`;
    return `${layer.color.map(value => formatNumber(value / 255)).join(" ")} rg`;
  }

  function pdfStrokeColor(layer) {
    if (isStructureRole(layer.role) && layer.spotName) return `/${spotResourceName(layer.spotName)} CS 1 SCN`;
    if (layer.cmyk) return `${layer.cmyk.map(formatNumber).join(" ")} K`;
    return `${layer.color.map(value => formatNumber(value / 255)).join(" ")} RG`;
  }

  function exportPdf(result, options = {}) {
    const document = normalizeDocument(result, options);
    const commands = [
      "q",
      `${formatNumber(document.widthPt / document.sourceWidth)} 0 0 ${formatNumber(-document.heightPt / document.sourceHeight)} 0 ${formatNumber(document.heightPt)} cm`
    ];
    const structureSpots = structureSpotLayers(document);
    const hasFoilWhiteSpot = document.layers.some(layer => layer.spotName === "FOIL_WHITE" && layer.contours.length);
    for (const layer of document.layers) {
      if (layer.nativePath.length) {
        commands.push(pdfFillColor(layer));
        appendNativePathCommands(commands, layer.nativePath, { move: "m", line: "l", curve: "c", close: "h" });
        commands.push("f*");
      }
      if (layer.contours.length) {
        commands.push(pdfFillColor(layer));
        for (const contour of layer.contours) {
          appendContourCommands(commands, contour, layer.smoothing, { move: "m", line: "l", curve: "c", close: "h" });
        }
        commands.push("f*");
      }
      if (layer.strokes.length) {
        commands.push(pdfStrokeColor(layer), `${formatNumber(sourceStrokeWidth(layer, document))} w`, "0 J", "0 j");
        for (const stroke of layer.strokes) {
          commands.push(`${formatNumber(stroke[0].x)} ${formatNumber(stroke[0].y)} m`);
          for (let index = 1; index < stroke.length; index += 1) commands.push(`${formatNumber(stroke[index].x)} ${formatNumber(stroke[index].y)} l`);
          commands.push("S");
        }
      }
      if (layer.nativeStrokes.length) {
        commands.push(pdfStrokeColor(layer), `${formatNumber(sourceStrokeWidth(layer, document))} w`, "0 J", "0 j");
        for (const path of layer.nativeStrokes) {
          appendNativePathCommands(commands, path, { move:"m", line:"l", curve:"c", close:"h" });
          commands.push("S");
        }
      }
    }
    commands.push("Q");
    const content = `${commands.join("\n")}\n`;
    const spots = [
      ...structureSpots.map(layer => ({
        resource: spotResourceName(layer.spotName),
        name: layer.spotName,
        alternate: (layer.cmyk || [0, 0, 0, 1]).map(formatNumber).join(" ")
      })),
      ...(hasFoilWhiteSpot ? [{ resource: "CS_FOIL_WHITE", name: "FOIL_WHITE", alternate: "0 0 0 0" }] : [])
    ];
    const resourceColorSpace = spots.length
      ? ` /ColorSpace << ${spots.map((spot, index) => `/${spot.resource} ${5 + index * 2} 0 R`).join(" ")} >>`
      : "";
    const objects = [
      "<< /Type /Catalog /Pages 2 0 R >>",
      "<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
      `<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${formatNumber(document.widthPt)} ${formatNumber(document.heightPt)}] /Resources << /ProcSet [/PDF]${resourceColorSpace} >> /Contents 4 0 R >>`,
      `<< /Length ${content.length} >>\nstream\n${content}endstream`
    ];
    for (const spot of spots) {
      objects.push(`[/Separation /${spot.name} /DeviceCMYK ${objects.length + 2} 0 R]`);
      objects.push(`<< /FunctionType 2 /Domain [0 1] /C0 [0 0 0 0] /C1 [${spot.alternate}] /N 1 >>`);
    }
    let source = "%PDF-1.4\n%\xE2\xE3\xCF\xD3\n";
    const offsets = [0];
    objects.forEach((object, index) => {
      offsets.push(source.length);
      source += `${index + 1} 0 obj\n${object}\nendobj\n`;
    });
    const xrefOffset = source.length;
    source += `xref\n0 ${objects.length + 1}\n0000000000 65535 f \n`;
    for (let index = 1; index < offsets.length; index += 1) {
      source += `${String(offsets[index]).padStart(10, "0")} 00000 n \n`;
    }
    source += `trailer\n<< /Size ${objects.length + 1} /Root 1 0 R >>\nstartxref\n${xrefOffset}\n%%EOF\n`;
    return outputRecord("pdf", document, asciiBytes(source));
  }

  function exportEps(result, options = {}) {
    const document = normalizeDocument(result, options);
    const structureSpots = structureSpotLayers(document);
    const hasFoilWhiteSpot = document.layers.some(layer => layer.spotName === "FOIL_WHITE" && layer.contours.length);
    const customSpotNames = [
      ...structureSpots.map(layer => `(${layer.spotName})`),
      ...(hasFoilWhiteSpot ? ["(FOIL_WHITE)"] : [])
    ];
    const output = [
      "%!PS-Adobe-3.0 EPSF-3.0",
      `%%BoundingBox: 0 0 ${Math.ceil(document.widthPt)} ${Math.ceil(document.heightPt)}`,
      `%%HiResBoundingBox: 0 0 ${formatNumber(document.widthPt)} ${formatNumber(document.heightPt)}`,
      "%%LanguageLevel: 2",
      "%%Pages: 1",
      "%%DocumentData: Clean7Bit",
      ...(customSpotNames.length ? [`%%DocumentCustomColors: ${customSpotNames.join(" ")}`] : []),
      ...structureSpots.map(layer => `%%CMYKCustomColor: ${(layer.cmyk || [0, 0, 0, 1]).map(formatNumber).join(" ")} (${layer.spotName})`),
      ...(hasFoilWhiteSpot ? ["%%CMYKCustomColor: 0 0 0 0 (FOIL_WHITE)"] : []),
      "%%EndComments",
      ...structureSpots.map(layer => `/${layer.spotName} ${(layer.cmyk || [0, 0, 0, 1]).map(formatNumber).join(" ")} (${layer.spotName}) findcmykcustomcolor def`),
      ...(hasFoilWhiteSpot ? ["/FOIL_WHITE 0 0 0 0 (FOIL_WHITE) findcmykcustomcolor def"] : []),
      "gsave",
      `[${formatNumber(document.widthPt / document.sourceWidth)} 0 0 ${formatNumber(-document.heightPt / document.sourceHeight)} 0 ${formatNumber(document.heightPt)}] concat`
    ];
    for (const layer of document.layers) {
      if (isStructureRole(layer.role) && layer.spotName) output.push(`${layer.spotName} 1 setcustomcolor`);
      else if (layer.spotName === "FOIL_WHITE") output.push("FOIL_WHITE 1 setcustomcolor");
      else if (layer.cmyk) output.push(`${layer.cmyk.map(formatNumber).join(" ")} setcmykcolor`);
      else output.push(`${layer.color.map(value => formatNumber(value / 255)).join(" ")} setrgbcolor`);
      if (layer.nativePath.length) {
        output.push("newpath");
        appendNativePathCommands(output, layer.nativePath, { move: "moveto", line: "lineto", curve: "curveto", close: "closepath" });
        output.push("eofill");
      }
      if (layer.contours.length) {
        output.push("newpath");
        for (const contour of layer.contours) {
          appendContourCommands(output, contour, layer.smoothing, { move: "moveto", line: "lineto", curve: "curveto", close: "closepath" });
        }
        output.push("eofill");
      }
      if (layer.strokes.length) {
        output.push(`${formatNumber(sourceStrokeWidth(layer, document))} setlinewidth`, "0 setlinecap", "0 setlinejoin");
        for (const stroke of layer.strokes) {
          output.push("newpath", `${formatNumber(stroke[0].x)} ${formatNumber(stroke[0].y)} moveto`);
          for (let index = 1; index < stroke.length; index += 1) output.push(`${formatNumber(stroke[index].x)} ${formatNumber(stroke[index].y)} lineto`);
          output.push("stroke");
        }
      }
      if (layer.nativeStrokes.length) {
        output.push(`${formatNumber(sourceStrokeWidth(layer, document))} setlinewidth`, "0 setlinecap", "0 setlinejoin");
        for (const path of layer.nativeStrokes) {
          output.push("newpath");
          appendNativePathCommands(output, path, { move:"moveto", line:"lineto", curve:"curveto", close:"closepath" });
          output.push("stroke");
        }
      }
    }
    output.push("grestore", "showpage", "%%EOF", "");
    return outputRecord("eps", document, output.join("\n"));
  }

  function safeDxfLayerName(value, index) {
    const cleaned = String(value || "")
      .replace(/[<>\\/:;?*|=\",]/g, "_")
      .replace(/[\u0000-\u001f]/g, "")
      .trim()
      .slice(0, 80);
    return cleaned || `VEC_${index + 1}`;
  }

  function trueColorValue(color) {
    return color[0] * 65536 + color[1] * 256 + color[2];
  }

  function appendDxfLine(output, layerName, color, start, end, scaleX, scaleY, heightMm) {
    output.push(
      "0", "LINE", "100", "AcDbEntity", "8", layerName,
      "420", String(trueColorValue(color)), "100", "AcDbLine",
      "10", formatNumber(start.x * scaleX), "20", formatNumber(heightMm - start.y * scaleY), "30", "0",
      "11", formatNumber(end.x * scaleX), "21", formatNumber(heightMm - end.y * scaleY), "31", "0"
    );
  }

  function appendDxfCubicSpline(output, layerName, color, points, scaleX, scaleY, heightMm) {
    output.push(
      "0", "SPLINE", "100", "AcDbEntity", "8", layerName,
      "420", String(trueColorValue(color)), "100", "AcDbSpline",
      "70", "8", "71", "3", "72", "8", "73", "4", "74", "0",
      "40", "0", "40", "0", "40", "0", "40", "0",
      "40", "1", "40", "1", "40", "1", "40", "1"
    );
    for (const point of points) {
      output.push(
        "10", formatNumber(point.x * scaleX),
        "20", formatNumber(heightMm - point.y * scaleY),
        "30", "0"
      );
    }
  }

  function appendDxfNativeStroke(output, commands, layerName, color, scaleX, scaleY, heightMm) {
    let current = null;
    let start = null;
    for (const command of commands) {
      if (command.op === "M") {
        current = { ...command.points[0] };
        start = { ...current };
      } else if (command.op === "L" && current) {
        const end = { ...command.points[0] };
        appendDxfLine(output, layerName, color, current, end, scaleX, scaleY, heightMm);
        current = end;
      } else if (command.op === "C" && current) {
        const [control1, control2, end] = command.points;
        appendDxfCubicSpline(
          output, layerName, color,
          [current, control1, control2, end],
          scaleX, scaleY, heightMm
        );
        current = { ...end };
      } else if (command.op === "Z" && current && start) {
        if (Math.hypot(current.x - start.x, current.y - start.y) > 1e-7) {
          appendDxfLine(output, layerName, color, current, start, scaleX, scaleY, heightMm);
        }
        current = { ...start };
      }
    }
  }

  function exportDxf(result, options = {}) {
    const document = normalizeDocument(result, options);
    const scaleX = document.widthMm / document.sourceWidth;
    const scaleY = document.heightMm / document.sourceHeight;
    const layerNames = document.layers.map((layer, index) => safeDxfLayerName(layer.name, index));
    const output = [
      "0", "SECTION", "2", "HEADER",
      "9", "$ACADVER", "1", "AC1015",
      "9", "$INSUNITS", "70", "4",
      "9", "$EXTMIN", "10", "0", "20", "0", "30", "0",
      "9", "$EXTMAX", "10", formatNumber(document.widthMm), "20", formatNumber(document.heightMm), "30", "0",
      "0", "ENDSEC",
      "0", "SECTION", "2", "TABLES",
      "0", "TABLE", "2", "LAYER", "70", String(document.layers.length)
    ];
    document.layers.forEach((layer, index) => {
      output.push(
        "0", "LAYER", "100", "AcDbSymbolTableRecord", "100", "AcDbLayerTableRecord",
        "2", layerNames[index], "70", "0", "62", "7", "420", String(trueColorValue(layer.color)), "370", String(layer.strokeWidthMm > 0 ? Math.round(layer.strokeWidthMm * 100) : -3), "6", "CONTINUOUS"
      );
    });
    output.push("0", "ENDTAB", "0", "ENDSEC", "0", "SECTION", "2", "ENTITIES");
    document.layers.forEach((layer, layerIndex) => {
      for (const subpath of flattenedNativeSubpaths(layer.nativePath)) {
        output.push(
          "0", "LWPOLYLINE", "100", "AcDbEntity", "8", layerNames[layerIndex],
          "420", String(trueColorValue(layer.color)), "100", "AcDbPolyline",
          "90", String(subpath.points.length), "70", subpath.closed ? "1" : "0"
        );
        for (const point of subpath.points) {
          output.push("10", formatNumber(point.x * scaleX), "20", formatNumber(document.heightMm - point.y * scaleY));
        }
      }
      for (const contour of layer.contours) {
        output.push(
          "0", "LWPOLYLINE", "100", "AcDbEntity", "8", layerNames[layerIndex],
          "420", String(trueColorValue(layer.color)), "100", "AcDbPolyline",
          "90", String(contour.length), "70", "1"
        );
        for (const point of contour) {
          output.push("10", formatNumber(point.x * scaleX), "20", formatNumber(document.heightMm - point.y * scaleY));
        }
      }
      for (const stroke of layer.strokes) {
        output.push(
          "0", "LWPOLYLINE", "100", "AcDbEntity", "8", layerNames[layerIndex],
          "420", String(trueColorValue(layer.color)), "100", "AcDbPolyline",
          "90", String(stroke.length), "70", "0"
        );
        for (const point of stroke) {
          output.push("10", formatNumber(point.x * scaleX), "20", formatNumber(document.heightMm - point.y * scaleY));
        }
      }
      for (const path of layer.nativeStrokes) {
        appendDxfNativeStroke(
          output,
          path,
          layerNames[layerIndex],
          layer.color,
          scaleX,
          scaleY,
          document.heightMm
        );
      }
    });
    output.push("0", "ENDSEC", "0", "EOF", "");
    return outputRecord("dxf", document, output.join("\n"));
  }

  function exportResult(result, format, options = {}) {
    const key = getFormatInfo(format).format;
    if (key === "pdf") return exportPdf(result, options);
    if (key === "eps") return exportEps(result, options);
    return exportDxf(result, options);
  }

  function toUint8Array(output) {
    const data = output && Object.prototype.hasOwnProperty.call(output, "data") ? output.data : output;
    if (data instanceof Uint8Array) return data;
    if (typeof data !== "string") throw new TypeError("导出数据必须是字符串或 Uint8Array");
    if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(data);
    return Uint8Array.from(unescape(encodeURIComponent(data)).split("").map(character => character.charCodeAt(0)));
  }

  return {
    FORMAT_INFO,
    getFormatInfo,
    exportResult,
    exportPdf,
    exportEps,
    exportDxf,
    toUint8Array
  };
});
