Reads the numbers of an ASCII DXF file into drawing objects: coordinates, angles, hatch boundaries and pattern lines, block arrays, dimensions, viewports, lineweights and sheet sizes.
How the precision is kept
Every real number is read with parseFloat, which gives the nearest 64-bit number to the decimal text, so the file's own digits set the accuracy. Angles are written in degrees in DXF and are converted once with × π ÷ 180. Arc edges of a hatch boundary are sampled at 32 chords per full turn and bulge arcs at 16 per half turn, so hatch outlines become polygons.
Open item. Guard the bulge arc of a hatch boundary against a zero-length chord, as the DWG reader does, so two coincident vertices cannot produce points that are not numbers.
Calculations under this policy
15 calculations 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.
Reads one real number from the file's text, or a default when the group is absent.
value = parseFloat(text)
parseFloat returns the nearest 64-bit number to the decimal text, so a file written with 17 significant digits reads back exactly and one written with fewer reads back to those digits. Text that is not a number gives NaN here; it is not replaced by 0.
dxf.js
line 571
CadCraft 0.8.2
const num = (g, code, d = 0) => { const v = val(g, code); return v === undefined ? d : parseFloat(v); };
Arc angles
64-bit
An arc's center, radius and start and end angles.
a = (degrees × π ÷ 180) mod 2π
The file's degrees are multiplied by Math.PI and divided by 180, then reduced into 0 to 2π. The conversion rounds once; an angle such as 90 degrees becomes the 64-bit number nearest π ÷ 2.
Turns a hatch's boundary paths (polylines with bulges, lines and arcs) into rings of points.
arc edge: steps = max(4, ⌈32 × (a₁ − a₀) ÷ 2π⌉); p = c + r × (cos a, sin a)
Arc edges become equal-angle chords, an inscribed polygon (32 chords for a full turn). A clockwise edge has both angles negated; then the end angle is raised by 2π when it is not above the start. A path with an ellipse or spline edge is skipped and counted in the report.
dxf.js
lines 918 to 958
CadCraft 0.8.2
function hatchLoops(g, rep) {
// Walk the boundary paths in order (the codes repeat, so position matters).
let k = g.findIndex(([c]) => c === 91);
if (k < 0) return [];
const loops = [];
const n = parseInt(g[k][1], 10); k++;
const next = (code) => { while (k < g.length && g[k][0] !== code) k++; return k < g.length ? parseFloat(g[k++][1]) : NaN; };
for (let p = 0; p < n && k < g.length; p++) {
const flags = next(92);
const ring = [];
if (flags & 2) {
const hasBulge = next(72) === 1, closed = next(73) === 1, cnt = next(93);
const vs = [];
for (let j = 0; j < cnt; j++) { const x = next(10), y = next(20), b = hasBulge ? next(42) : 0; vs.push({ x, y, b }); }
for (let j = 0; j < vs.length; j++) {
const a = vs[j], b2 = vs[(j + 1) % vs.length];
ring.push(a.x, a.y);
if (Math.abs(a.b) > 1e-12 && (closed || j + 1 < vs.length)) arcInto(ring, a, b2, a.b);
}
if (vs.length) ring.push(vs[0].x, vs[0].y);
} else {
const edges = next(93);
let ok = true;
for (let j = 0; j < edges; j++) {
const t = next(72);
if (t === 1) { const x1 = next(10), y1 = next(20), x2 = next(11), y2 = next(21); ring.push(x1, y1, x2, y2); }
else if (t === 2) {
const cx = next(10), cy = next(20), r = next(40), s = rad(next(50)), e = rad(next(51)), ccw = next(73) === 1;
let a0 = s, a1 = e;
if (!ccw) { a0 = -s; a1 = -e; }
if (a1 <= a0) a1 += TAU;
const steps = Math.max(4, Math.ceil(32 * (a1 - a0) / TAU));
for (let q = 0; q <= steps; q++) { const a = a0 + (a1 - a0) * q / steps; ring.push(cx + r * Math.cos(a), cy + r * Math.sin(a)); }
} else { ok = false; break; } // ellipse / spline edges: not yet
}
if (!ok) { skip(rep, t('dxf.hatch_boundary_ellipse_spline')); continue; }
}
if (ring.length >= 6) loops.push(ring);
}
return loops;
}
Bulge arc to points
64-bit
The points along a bulged segment of a hatch boundary polyline.
θ = 4 × atan(b); r = |c ÷ (2 × sin(θ ÷ 2))|; center = midpoint + normal × r × cos(θ ÷ 2); steps = max(2, ⌈16 × |θ| ÷ π⌉)
Closed form from the chord length c and the bulge. The direction is divided by c with no check: two coincident vertices with a bulge give 0 ÷ 0 and the points added are NaN. The reader of DWG files returns early in that case; this one does not.
dxf.js
lines 960 to 967
CadCraft 0.8.2
function arcInto(ring, p, q, b) {
const theta = 4 * Math.atan(b), steps = Math.max(2, Math.ceil(16 * Math.abs(theta) / Math.PI));
const c = Math.hypot(q.x - p.x, q.y - p.y), r = Math.abs(c / (2 * Math.sin(theta / 2)));
const mx = (p.x + q.x) / 2, my = (p.y + q.y) / 2, h = r * Math.cos(theta / 2) * (b > 0 ? 1 : -1);
const ux = (q.x - p.x) / c, uy = (q.y - p.y) / c, cx = mx - uy * h, cy = my + ux * h;
const a0 = Math.atan2(p.y - cy, p.x - cx);
for (let s = 1; s < steps; s++) { const a = a0 + theta * s / steps; ring.push(cx + r * Math.cos(a), cy + r * Math.sin(a)); }
}
Hatch pattern scale, angle and lines
64-bit
A pattern hatch's scale, angle and its own pattern lines.
pattern angle = (degrees × π ÷ 180) mod 2π; dx = o_x cos a + o_y sin a; dy = −o_x sin a + o_y cos a, a = line angle × π ÷ 180
Each line's angle stays in degrees as read; its offset is rotated from world axes into the line's frame. A hatch with more than 64 line families keeps none of them, each family keeps at most 32 dashes, and a family with any value that is not a finite number is left out.
dxf.js
lines 517 to 545
CadCraft 0.8.2
/**
* A pattern HATCH's scale (41), angle (52, degrees → radians) and its own pattern lines after group 78 (53 angle, 43/44
* base, 45/46 offset in world axes, 79 dash count, 49 dashes) → { pscale, pangle, patlines } in hatch.js's shape
* (angle in degrees, offset along / across the line), so hatch.js draws the file's lines as they are.
*/
function hatchPattern(g) {
const out = {};
const sc = num(g, 41, 1), an = num(g, 52, 0);
if (sc > 0 && sc !== 1) out.pscale = sc;
if (an) out.pangle = normAngle(rad(an));
const k = g.findIndex(([c]) => c === 78);
if (k < 0) return out;
const lines = [];
let cur = null;
for (let j = k + 1; j < g.length; j++) {
const [c, v] = g[j], x = parseFloat(v);
if (c === 53) { cur = { angle: x, x: 0, y: 0, ox: 0, oy: 0, dashes: [] }; lines.push(cur); }
else if (!cur) break;
else if (c === 43) cur.x = x; else if (c === 44) cur.y = x; else if (c === 45) cur.ox = x; else if (c === 46) cur.oy = x;
else if (c === 49) cur.dashes.push(x);
else if (c !== 79) break;
}
const ok = lines.filter((l) => [l.angle, l.x, l.y, l.ox, l.oy, ...l.dashes].every(Number.isFinite));
if (ok.length && ok.length <= 64) out.patlines = ok.map((l) => {
const a = l.angle * Math.PI / 180, ca = Math.cos(a), sa = Math.sin(a);
return { angle: l.angle, x: l.x, y: l.y, dx: l.ox * ca + l.oy * sa, dy: -l.ox * sa + l.oy * ca, dashes: l.dashes.slice(0, 32) };
});
return out;
}
Block array positions
64-bit
The insertion point of each copy of a block placed as a rows-by-columns array.
p = T(insert) × R(rot) × (c × column spacing, r × row spacing)
Each offset is index × spacing, not a running sum, and goes through one translate-rotate matrix built once.
dxf.js
lines 797 to 802
CadCraft 0.8.2
const cols = Math.max(1, parseInt(val(g, 70) ?? '1', 10)), rows = Math.max(1, parseInt(val(g, 71) ?? '1', 10));
const out = [];
if (cols * rows > 1) {
const dc = num(g, 44), dr = num(g, 45), m = mMul(mTranslate(ins.x, ins.y), mRotate(ins.rot));
for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) { const p = mApply(m, { x: c * dc, y: r * dr }); out.push({ ...ins, x: p.x, y: p.y }); }
} else out.push(ins);
Dimension defining points
64-bit
The points a dimension measures from, by dimension type.
linear: p₁ = 13, p₂ = 14, line point = 10, angle = group 50 × π ÷ 180; diameter center = (point 15 + point 10) ÷ 2; four-point angular: vertex from the two lines
The points are read as written and the dimension measures again from them. The measurement stored in the file (group 42) is not read here, so a file whose points and stored value disagree shows the value of its points.
dxf.js
lines 827 to 835
CadCraft 0.8.2
if (kind === 0 || kind === 1) d = { kind: kind ? 'aligned' : 'linear', p1: pt(13), p2: pt(14), loc: pt(10), rot: normAngle(rad(num(g, 50))) };
else if (kind === 4) d = { kind: 'radius', c: pt(10), p1: pt(15), loc: pt(11) };
else if (kind === 3) { const a = pt(15), b = pt(10); d = { kind: 'diameter', c: { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }, p1: a, loc: pt(11) }; }
else if (kind === 5) d = { kind: 'angular', c: pt(15), p1: pt(13), p2: pt(14), loc: pt(10) };
else if (kind === 2) {
const L = (a, b) => ({ x1: num(g, a), y1: num(g, a + 10), x2: num(g, b), y2: num(g, b + 10) });
const r = angularFromLines(L(13, 14), L(15, 10), pt(16));
if (r) d = { kind: 'angular', ...r, loc: pt(16) };
}
Can the dimension be measured
64-bit
Checks that a dimension's points are finite and far enough apart to measure.
|a − b| > 1e-12
Absolute 1e-12 drawing units between the points that span the measurement. A dimension that fails is drawn from the file's block, or skipped when it has none.
dxf.js
lines 881 to 889
CadCraft 0.8.2
/** Can CadCraft draw this dimension itself (finite points, something to measure)? */
function dimUsable(d) {
const pts = ['p1', 'p2', 'c', 'loc'].filter((k) => d[k]).map((k) => d[k]);
if (!pts.every((p) => Number.isFinite(p.x) && Number.isFinite(p.y))) return false;
const far = (a, b) => Math.hypot(b.x - a.x, b.y - a.y) > 1e-12;
if (d.kind === 'linear' || d.kind === 'aligned') return far(d.p1, d.p2) && Number.isFinite(d.rot ?? 0);
if (d.kind === 'radius' || d.kind === 'diameter') return far(d.c, d.p1);
return far(d.c, d.p1) && far(d.c, d.p2) && far(d.c, d.loc);
}
Viewport scale, center and twist
64-bit
A sheet viewport's scale, the model point at its center and its view twist.
s = viewport height ÷ view height; twist = degrees × π ÷ 180 (or radians from R12 data)
One division for the scale; without a positive view height the scale is 1 and the center is the viewport's own. R12 files carry the view in extended data, where the twist is already in radians. A twist below 1e-12 radians (absolute) is dropped.
An object's own lineweight in millimeters and its linetype scale.
lw = n ÷ 100; linetype scale kept when |s − 1| > 1e-12
The lineweight is a whole number of hundredths divided by 100. A linetype scale within 1e-12 of 1 is treated as 1 and not stored.
dxf.js
lines 586 to 596
CadCraft 0.8.2
/** An object's own linetype (6), lineweight (370, 1/100 mm) and linetype scale (48) — absent = by layer (0.4 C8). */
function styleOf(g) {
const o = {};
const lt = val(g, 6);
if (lt && !/^bylayer$/i.test(lt)) o.lt = /^byblock$/i.test(lt) ? 'ByBlock' : /^continuous$/i.test(lt) ? 'Continuous' : lt;
const lw = val(g, 370);
if (lw !== undefined) { const n = parseInt(lw, 10); if (n === -2 || n === -3) o.lw = n; else if (n >= 0 && n <= 211) o.lw = n / 100; }
const s = parseFloat(val(g, 48));
if (s > 0 && Math.abs(s - 1) > 1e-12) o.lts = s;
return o;
}
Sheet size from the plot settings
64-bit
A sheet's size in its own unit when the layout gives no limits, from the paper size in millimeters.
size = paper mm ÷ 25.4 (inch sheets) or ÷ 1; width and height swapped when the plot rotation code is odd
25.4 is exact; the division rounds once. The layout's limits are used unchanged when they span a positive width and height.
dxf.js
lines 457 to 458
CadCraft 0.8.2
let lim = l.max.x > l.min.x && l.max.y > l.min.y ? l : null;
if (!lim && l.paperMm.w > 0) { const k = unit === 'in' ? 25.4 : 1, [w, h] = l.rot % 2 ? [l.paperMm.h, l.paperMm.w] : [l.paperMm.w, l.paperMm.h]; lim = { min: { x: 0, y: 0 }, max: { x: w / k, y: h / k } }; }
Dimension style numbers
64-bit
Brings a dimension style's scale, unit format and decimal places into range.
scale ≤ 0 → 1; dec, adec, tad, zin → max(0, round(value)); dec ≤ 8
The number of decimal places is rounded to a whole number and capped at 8. Sizes are left as the 64-bit numbers read.
dxf.js
lines 507 to 515
CadCraft 0.8.2
function fixStyle(st) {
if (!(st.scale > 0)) st.scale = 1;
// DIMLUNIT (277, R2000+) is units.js' code; R12's DIMUNIT (270) adds 6 / 7 for the unstacked architectural / fractional
if (st.unit !== undefined) { const u = Math.round(st.unit); if (st.lunit === undefined) st.lunit = u === 6 ? 4 : u === 7 ? 5 : u; delete st.unit; }
if (st.lunit !== undefined) { const u = Math.round(+st.lunit); if (u >= 1 && u <= 5) st.lunit = u; else if (u === 6) st.lunit = 2; else delete st.lunit; }
for (const k of ['dec', 'adec', 'tad', 'zin']) st[k] = Math.max(0, Math.round(st[k] || 0));
if (st.dec > 8) st.dec = 8;
return st;
}
Construction line direction
64-bit
The unit direction of an infinite line or ray.
direction = (dx, dy) ÷ √(dx² + dy²)
Normalized again with Math.hypot. A direction not longer than 1e-12 (absolute) is skipped and counted.
dxf.js
lines 696 to 698
CadCraft 0.8.2
const dx = num(g, 11), dy = num(g, 21), n = Math.hypot(dx, dy);
if (!(n > 1e-12)) { skip(rep, type); return null; }
return { type: type === 'XLINE' ? 'xline' : 'ray', ...base, x: num(g, 10), y: num(g, 20), dx: dx / n, dy: dy / n };
Extra sheet read back from a block
64-bit
Rebuilds a sheet and its viewports from the block an R12 file keeps them in.
viewport center = ((x_min + x_max) ÷ 2, (y_min + y_max) ÷ 2); w = x_max − x_min; h = y_max − y_min
A viewport's rectangle is recovered from the corners of its frame polyline, so its center and size are a mean and a difference of written coordinates. Its view center and scale are read from numbers this app wrote as text with 15 significant digits.
parseFloat of each group value; degrees to radians; closed-form arc from a bulge (θ = 4 atan b); block arrays through one translate-rotate matrix.
Number types
64-bit numbers. Lineweights are whole hundredths of a millimeter in the file and are divided by 100.
Tolerances
Zero lengths 1e-12 (absolute, drawing units); a bulge counts as an arc above 1e-12; a view twist or linetype scale is kept when it differs from 0 or 1 by more than 1e-12.
Limits
Arc edges of hatch boundaries become chords; a boundary with ellipse or spline edges is skipped and counted. Unlike the DWG reader, a dimension's stored measurement is not compared with its points.
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.