Resources Numerical accuracy Calculation policy

PDF underlay in a plot

Plots the lines, curves, fills and text of a PDF underlay at its place in the drawing.

How the precision is kept

Points go through the underlay's 2 × 3 matrix in 64-bit numbers. Every Bézier curve of the PDF is plotted as 8 straight chords whatever its size, so the chord count sets how closely curves are followed.

Open item. Choose the number of chords from the curve's size and the plot's scale.

Calculations under this policy

1 calculation in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.

pdf-emit.js CadCraft 0.8.2 64-bit 1 calculation Reviewed

Tests None found by the review

Underlay shapes and text

64-bit

The underlay's paths as plotted segments and fills, and its text.

B(t) = (1 − t)³·P₀ + 3(1 − t)²t·P₁ + 3(1 − t)t²·P₂ + t³·P₃, t = s ÷ 8; line width = max(0.05, w × √|det|); text height = h × √|det| ÷ FONT_EM

Each curve is evaluated at 8 equal parameter steps and joined by straight lines; the number does not depend on the curve's length or the plot's scale. Line width and text height are scaled by √|det| of the matrix, and text height is divided by FONT_EM, which geom.js defines as 1 ÷ 0.718.

  • pdf-emit.js
  • lines 14 to 52
  • CadCraft 0.8.2
export function emitPdf(vec, p, out0) {
  const m = p.m, X = (x, y) => m[0] * x + m[2] * y + m[4], Y = (x, y) => m[1] * x + m[3] * y + m[5];
  const k = Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2])) || 1, rot0 = Math.atan2(m[1], m[0]);
  const clip = p.clipped && p.clip && p.clip.length >= 6 ? p.clip.reduce((a, v, i) => (i % 2 ? (a[a.length - 1].y = v, a) : (a.push({ x: v, y: 0 }), a)), []) : null;
  const out = clip ? clipOut(out0, clip) : out0;
  const o = { alpha: p.alpha ?? 1 };
  const col = (hex) => (p.mono || !hex || hex === '#000000' ? 'ink' : hex === '#ffffff' ? 'ink' : hex);
  for (const sh of vec.shapes) {
    const strokes = [], loops = [];
    for (const sub of sh.subs) {
      const ring = [];
      let px = 0, py = 0;
      for (const op of sub.ops) {
        if (op[0] === 'M') { px = op[1]; py = op[2]; ring.push(X(px, py), Y(px, py)); }
        else if (op[0] === 'L') { px = op[1]; py = op[2]; ring.push(X(px, py), Y(px, py)); }
        else {
          for (let s = 1; s <= STEPS; s++) {
            const t = s / STEPS, u = 1 - t;
            const x = u * u * u * px + 3 * u * u * t * op[1] + 3 * u * t * t * op[3] + t * t * t * op[5];
            const y = u * u * u * py + 3 * u * u * t * op[2] + 3 * u * t * t * op[4] + t * t * t * op[6];
            ring.push(X(x, y), Y(x, y));
          }
          px = op[5]; py = op[6];
        }
      }
      if (ring.length < 4) continue;
      if (sh.fill && ring.length >= 6) loops.push(ring);
      if (sh.stroke) {
        for (let i = 0; i + 3 < ring.length; i += 2) strokes.push({ k: 'L', x1: ring[i], y1: ring[i + 1], x2: ring[i + 2], y2: ring[i + 3] });
        if (sub.closed && ring.length >= 6) strokes.push({ k: 'L', x1: ring[ring.length - 2], y1: ring[ring.length - 1], x2: ring[0], y2: ring[1] });
      }
    }
    if (loops.length) out.fill(col(sh.fill), loops, true, o);
    if (strokes.length) out.stroke(col(sh.stroke), strokes, Math.max(0.05, sh.lw * k), o);
  }
  for (const tx of vec.texts) {
    out.text('ink', { type: 'text', x: X(tx.x, tx.y), y: Y(tx.x, tx.y), h: tx.h * k / FONT_EM, rot: (((tx.rot + rot0) % (2 * Math.PI)) + 2 * Math.PI) % (2 * Math.PI), s: tx.s, ha: 'left', va: 'baseline', layer: '0' }, o);
  }
}

In detail

Method
Cubic Bézier in Bernstein form evaluated at t = 1/8 to 8/8.
Number types
64-bit numbers.
Limits
8 chords per curve, fixed. Line widths under 0.05 output units are raised to 0.05. Pictures inside the PDF are not plotted.

Approach: 64-bit, stated limit

The arithmetic is 64-bit, but the method or the data sets the accuracy: a solver tolerance, a mesh, a step size, a datum shift, a table. The limit is stated where the result is shown.

Every policy that takes this approach