@mlightcad/shx-parser
Version:
A TypeScript library for parsing AutoCAD SHX font files
1,055 lines (1,054 loc) • 32.8 kB
JavaScript
class f {
/**
* Converts an unsigned byte to a signed byte as used in SHX format.
* Values > 127 are converted to their signed equivalent (-128 to -1).
* @param value - The unsigned byte value to convert
* @returns The signed byte value
*/
static byteToSByte(t) {
return (t & 127) - (t & 128 ? 128 : 0);
}
/**
* Creates a new ShxFileReader instance.
* @param arraybuffer - The ArrayBuffer to read from
*/
constructor(t) {
this.position = 0, this.data = new DataView(t);
}
/**
* Reads a specified number of bytes from the current position.
* @param length - Number of bytes to read (optional)
* @returns A Uint8Array containing the read bytes
* @throws Error if reading beyond buffer bounds
*/
readBytes(t = 1) {
this.data.byteLength < this.position + t && this.throwOutOfRangeError(this.position + t);
const e = new Uint8Array(this.data.buffer, this.position, t);
return this.position += t, e;
}
/**
* Skips a specified number of bytes from the current position.
* @param length - Number of bytes to skip
* @throws Error if skipping beyond buffer bounds
*/
skip(t) {
this.data.byteLength < this.position + t && this.throwOutOfRangeError(this.position + t), this.position += t;
}
/**
* Reads an unsigned 8-bit integer.
* @returns The read uint8 value
* @throws Error if reading beyond buffer bounds
*/
readUint8() {
this.data.byteLength < this.position + 1 && this.throwOutOfRangeError(this.position + 1);
const t = this.data.getUint8(this.position);
return this.position += 1, t;
}
/**
* Reads a signed 8-bit integer.
* @returns The read int8 value
* @throws Error if reading beyond buffer bounds
*/
readInt8() {
this.data.byteLength < this.position + 1 && this.throwOutOfRangeError(this.position + 1);
const t = this.data.getInt8(this.position);
return this.position += 1, t;
}
/**
* Reads an unsigned 16-bit integer.
* @param littleEndian If false, a big-endian value should be read.
* @returns The read uint16 value
* @throws Error if reading beyond buffer bounds
*/
readUint16(t = !0) {
this.data.byteLength < this.position + 2 && this.throwOutOfRangeError(this.position + 2);
const e = this.data.getUint16(this.position, t);
return this.position += 2, e;
}
/**
* Reads a signed 16-bit integer.
* @returns The read int16 value
* @throws Error if reading beyond buffer bounds
*/
readInt16() {
this.data.byteLength < this.position + 2 && this.throwOutOfRangeError(this.position + 2);
const t = this.data.getInt16(this.position, !0);
return this.position += 2, t;
}
/**
* Reads an unsigned 32-bit integer.
* @returns The read uint32 value
* @throws Error if reading beyond buffer bounds
*/
readUint32() {
this.data.byteLength < this.position + 4 && this.throwOutOfRangeError(this.position + 4);
const t = this.data.getUint32(this.position, !0);
return this.position += 4, t;
}
/**
* Reads a signed 32-bit integer.
* @returns The read int32 value
* @throws Error if reading beyond buffer bounds
*/
readInt32() {
this.data.byteLength < this.position + 4 && this.throwOutOfRangeError(this.position + 4);
const t = this.data.getInt32(this.position, !0);
return this.position += 4, t;
}
/**
* Reads a 32-bit floating point number.
* @returns The read float32 value
* @throws Error if reading beyond buffer bounds
*/
readFloat32() {
this.data.byteLength < this.position + 4 && this.throwOutOfRangeError(this.position + 4);
const t = this.data.getFloat32(this.position, !0);
return this.position += 4, t;
}
/**
* Reads a 64-bit floating point number.
* @returns The read float64 value
* @throws Error if reading beyond buffer bounds
*/
readFloat64() {
this.data.byteLength < this.position + 8 && this.throwOutOfRangeError(this.position + 8);
const t = this.data.getFloat64(this.position, !0);
return this.position += 8, t;
}
/**
* Sets the current read position in the buffer.
* @param position - The new position to set
*/
setPosition(t) {
this.data.byteLength < t && this.throwOutOfRangeError(t), this.position = t;
}
/**
* Checks if the current position is at the end of the buffer.
* @returns True if at the end of the buffer, false otherwise
*/
isEnd() {
return this.position === this.data.byteLength - 1;
}
/**
* Gets the current position in the buffer.
* @returns The current position
*/
get currentPosition() {
return this.position;
}
/**
* Gets the total length of the buffer.
* @returns The buffer length in bytes
*/
get length() {
return this.data.byteLength;
}
/**
* Throws an error when attempting to read beyond buffer bounds.
* @param position - The position that caused the error
* @throws Error with details about the out of range access
*/
throwOutOfRangeError(t) {
throw new Error(
`Position ${t} is out of range for the data length ${this.data.byteLength}!`
);
}
}
var y = /* @__PURE__ */ ((d) => (d.SHAPES = "shapes", d.BIGFONT = "bigfont", d.UNIFONT = "unifont", d))(y || {});
class E {
parse(t) {
const i = this.parseHeader(t).split(" "), n = i[1].toLocaleLowerCase();
if (!Object.values(y).includes(n))
throw new Error(`Invalid font type: ${n}`);
return {
fileHeader: i[0],
fontType: n,
fileVersion: i[2]
};
}
parseHeader(t) {
let e = "", n = 0;
for (; t.currentPosition < t.length - 2 && n < 1024; ) {
const s = t.readUint8();
if (s === 13) {
const o = t.currentPosition, r = t.readUint8(), c = t.readUint8();
if (r === 10 && c === 26)
break;
t.setPosition(o), e += String.fromCharCode(s);
} else
e += String.fromCharCode(s);
n++;
}
return e.trim();
}
}
class I {
parse(t) {
try {
t.readBytes(4);
const e = t.readInt16();
if (e <= 0)
throw new Error("Invalid shape count in font file");
const i = [];
for (let o = 0; o < e; o++) {
const r = t.readUint16(), c = t.readUint16();
c > 0 && i.push({ code: r, length: c });
}
const n = {};
for (const o of i)
try {
const r = t.readBytes(o.length);
r.length === o.length && (n[o.code] = r);
} catch {
console.warn(`Failed to read shape data for code ${o.code}`);
}
const s = {
data: n,
info: "",
baseUp: 8,
// Default values
baseDown: 2,
orientation: "horizontal",
isExtended: !1
};
if (0 in n) {
const o = n[0];
try {
const r = new TextDecoder().decode(o);
let c = r.indexOf("\0");
c >= 0 && (s.info = r.substring(0, c), c + 3 < o.length && (s.baseUp = o[c + 1], s.baseDown = o[c + 2], s.orientation = o[c + 3] === 0 ? "horizontal" : "vertical"));
} catch {
console.warn("Failed to parse font info block");
}
}
return s;
} catch (e) {
return console.error("Error parsing shape font:", e), {
data: {},
info: "Failed to parse font file",
baseUp: 8,
baseDown: 2,
orientation: "horizontal",
isExtended: !1
};
}
}
}
class B {
parse(t) {
try {
t.readInt16();
const e = t.readInt16(), i = t.readInt16();
if (e <= 0)
throw new Error("Invalid character count in font file");
t.skip(i * 4);
const n = [];
for (let r = 0; r < e; r++) {
const c = t.readUint16(!1), a = t.readUint16(), h = t.readUint32();
(c !== 0 || a !== 0 || h !== 0) && n.push({ code: c, length: a, offset: h });
}
const s = {};
for (const r of n)
try {
t.setPosition(r.offset);
const c = t.readBytes(r.length);
c.length === r.length && (s[r.code] = c);
} catch {
console.warn(`Failed to read bigfont data for code ${r.code}`);
}
const o = {
data: s,
info: "",
baseUp: 8,
baseDown: 2,
orientation: "horizontal",
isExtended: !1
};
if (0 in s) {
const r = s[0];
try {
const c = this.utf8ArrayToStr(r);
let a = c.indexOf("\0");
a >= 0 && (o.info = c.substring(0, a), a++, a + 3 < r.length && (r.length - a === 4 ? (o.baseUp = r[a++], o.baseDown = r[a++], o.orientation = r[a++] === 0 ? "horizontal" : "vertical") : (o.baseUp = r[a++], a++, o.orientation = r[a++] === 0 ? "horizontal" : "vertical", o.baseDown = r[a++], o.isExtended = !0)));
} catch {
console.warn("Failed to parse bigfont info block");
}
}
return o;
} catch (e) {
return console.error("Error parsing big font:", e), {
data: {},
info: "Failed to parse font file",
baseUp: 8,
baseDown: 2,
orientation: "horizontal",
isExtended: !1
};
}
}
utf8ArrayToStr(t) {
let e = "", i = 0;
for (; i < t.length; ) {
const n = t[i++];
switch (n >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
e += String.fromCharCode(n);
break;
case 12:
case 13: {
const s = t[i++];
e += String.fromCharCode((n & 31) << 6 | s & 63);
break;
}
case 14: {
const s = t[i++], o = t[i++];
e += String.fromCharCode(
(n & 15) << 12 | (s & 63) << 6 | (o & 63) << 0
);
break;
}
}
}
return e;
}
}
class U {
parse(t) {
try {
const e = t.readInt32();
if (e <= 0)
throw new Error("Invalid character count in font file");
const i = t.readInt16(), n = t.readBytes(i), s = {
data: {},
info: "",
baseUp: 8,
baseDown: 2,
orientation: "horizontal",
isExtended: !1
};
try {
const r = new TextDecoder().decode(n);
let c = r.indexOf("\0");
c >= 0 && (s.info = r.substring(0, c), c + 3 < n.length && (s.baseUp = n[c + 1], s.baseDown = n[c + 2], s.orientation = n[c + 3] === 0 ? "horizontal" : "vertical"));
} catch {
console.warn("Failed to parse unifont info block");
}
const o = {};
for (let r = 0; r < e - 1; r++)
try {
const c = t.readUint16(), a = t.readUint16();
if (a > 0) {
const h = t.readBytes(a);
h.length === a && (o[c] = h);
}
} catch {
console.warn("Failed to read unifont character data");
break;
}
return s.data = o, s;
} catch (e) {
return console.error("Error parsing unifont:", e), {
data: {},
info: "Failed to parse font file",
baseUp: 8,
baseDown: 2,
orientation: "horizontal",
isExtended: !1
};
}
}
}
class T {
static createParser(t) {
switch (t) {
case y.SHAPES:
return new I();
case y.BIGFONT:
return new B();
case y.UNIFONT:
return new U();
default:
throw new Error(`Unsupported font type: ${t}`);
}
}
}
class l {
/**
* Creates a new Point instance.
* @param x - The x-coordinate (defaults to 0)
* @param y - The y-coordinate (defaults to 0)
*/
constructor(t = 0, e = 0) {
this.x = t, this.y = e;
}
/**
* Sets the coordinates of the point.
* @param x - The new x-coordinate
* @param y - The new y-coordinate
* @returns The point instance for method chaining
*/
set(t, e) {
return this.x = t, this.y = e, this;
}
/**
* Calculates the length (magnitude) of the vector from origin to this point.
* @returns The length of the vector
*/
length() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
/**
* Normalizes the point vector to have a length of 1.
* @returns The point instance for method chaining
*/
normalize() {
const t = this.length();
return t !== 0 && (this.x /= t, this.y /= t), this;
}
/**
* Creates a new Point instance with the same coordinates.
* @returns A new Point instance with the same x and y values
*/
clone() {
return new l(this.x, this.y);
}
/**
* Adds another point's coordinates to this point.
* @param point - The point to add
* @returns The point instance for method chaining
*/
add(t) {
return this.x += t.x, this.y += t.y, this;
}
/**
* Subtracts another point's coordinates from this point.
* @param point - The point to subtract
* @returns The point instance for method chaining
*/
subtract(t) {
return this.x -= t.x, this.y -= t.y, this;
}
/**
* Multiplies both coordinates by a scalar value.
* @param scalar - The scalar value to multiply by
* @returns The point instance for method chaining
*/
multiply(t) {
return this.x *= t, this.y *= t, this;
}
/**
* Divides both coordinates by a scalar value.
* @param scalar - The scalar value to divide by
* @returns The point instance for method chaining
*/
divide(t) {
return t !== 0 && (this.x /= t, this.y /= t), this;
}
/**
* Multiplies x and y coordinates by different scalar values.
* @param xScalar - The scalar value to multiply x-coordinate by
* @param yScalar - The scalar value to multiply y-coordinate by
* @returns The point instance for method chaining
*/
multiplyScalars(t, e) {
return this.x *= t, this.y *= e, this;
}
/**
* Divides x and y coordinates by different scalar values.
* @param xScalar - The scalar value to divide x-coordinate by
* @param yScalar - The scalar value to divide y-coordinate by
* @returns The point instance for method chaining
*/
divideScalars(t, e) {
return t !== 0 && (this.x /= t), e !== 0 && (this.y /= e), this;
}
/**
* Calculates the Euclidean distance to another point.
* @param point - The point to calculate distance to
* @returns The distance between the two points
*/
distanceTo(t) {
const e = this.x - t.x, i = this.y - t.y;
return Math.sqrt(e * e + i * i);
}
}
const A = Math.PI / 4;
class C {
/**
* Creates a bulge-defined arc
* @param start Start point
* @param end End point
* @param bulge Bulge factor (-1 to 1, where 1 is a semicircle)
*/
static fromBulge(t, e, i) {
const n = Math.max(-1, Math.min(1, i));
return new C({
start: t,
end: e,
bulge: n
});
}
/**
* Creates an octant-defined arc
* @param center Center point of the arc
* @param radius Radius of the arc
* @param startOctant Starting octant (0-7)
* @param octantCount Number of octants to span (0-8, where 0 means 8 octants)
* @param isClockwise Whether the arc goes clockwise
*/
static fromOctant(t, e, i, n, s) {
return new C({
center: t,
radius: e,
startOctant: i,
octantCount: n,
isClockwise: s
});
}
constructor(t) {
if (t.start && t.end && t.bulge !== void 0) {
this.start = t.start.clone(), this.end = t.end.clone(), this.bulge = t.bulge, this.isClockwise = t.bulge < 0;
const e = this.end.clone().subtract(this.start), i = e.length();
if (Math.abs(this.bulge) * i / 2 === 0) {
this.radius = 0, this.center = this.start.clone(), this.startAngle = Math.atan2(e.y, e.x), this.endAngle = this.startAngle;
return;
}
const s = 4 * Math.atan(Math.abs(this.bulge));
this.radius = i / (2 * Math.sin(s / 2));
const o = this.start.clone().add(e.clone().divide(2)), r = new l(-e.y, e.x);
r.normalize(), r.multiply(Math.abs(this.radius * Math.cos(s / 2))), this.center = o.clone(), this.isClockwise ? this.center.subtract(r) : this.center.add(r), this.startAngle = Math.atan2(this.start.y - this.center.y, this.start.x - this.center.x), this.endAngle = Math.atan2(this.end.y - this.center.y, this.end.x - this.center.x), this.isClockwise ? this.endAngle >= this.startAngle && (this.endAngle -= 2 * Math.PI) : this.endAngle <= this.startAngle && (this.endAngle += 2 * Math.PI);
} else if (t.center && t.radius !== void 0 && t.startOctant !== void 0 && t.octantCount !== void 0 && t.isClockwise !== void 0) {
this.center = t.center.clone(), this.radius = t.radius, this.isClockwise = t.isClockwise, this.startAngle = t.startOctant * A;
const e = (t.octantCount === 0 ? 8 : t.octantCount) * A;
this.endAngle = this.startAngle + (this.isClockwise ? -e : e), this.start = this.center.clone().add(
new l(
this.radius * Math.cos(this.startAngle),
this.radius * Math.sin(this.startAngle)
)
), this.end = this.center.clone().add(
new l(this.radius * Math.cos(this.endAngle), this.radius * Math.sin(this.endAngle))
);
} else
throw new Error("Invalid arc parameters");
}
/**
* Tessellates the arc into a series of points that approximate the arc.
* @param circleSpan The angle span between tessellated points (default Math.PI / 18)
* @returns Array of points representing the tessellated arc
*/
tessellate(t = Math.PI / 18) {
if (this.radius === 0)
return [this.start.clone(), this.end.clone()];
const e = [this.start.clone()], i = Math.abs(this.endAngle - this.startAngle), n = Math.max(1, Math.floor(i / t));
for (let s = 1; s < n; s++) {
const o = s / n, r = this.isClockwise ? this.startAngle - o * i : this.startAngle + o * i;
e.push(
this.center.clone().add(new l(this.radius * Math.cos(r), this.radius * Math.sin(r)))
);
}
return e.push(
this.end ? this.end.clone() : this.center.clone().add(
new l(
this.radius * Math.cos(this.endAngle),
this.radius * Math.sin(this.endAngle)
)
)
), e;
}
}
class S {
constructor(t, e = []) {
this.lastPoint = t, this.polylines = e;
}
/**
* Get the bounding box of the shape
* @returns Bounding box of the shape
*/
get bbox() {
let t = 1 / 0, e = -1 / 0, i = 1 / 0, n = -1 / 0;
return this.polylines.forEach((s) => {
s.forEach((o) => {
t = Math.min(t, o.x), e = Math.max(e, o.x), i = Math.min(i, o.y), n = Math.max(n, o.y);
});
}), { minX: t, minY: i, maxX: e, maxY: n };
}
/**
* Offset the shape by a point
* @param p The point to offset the shape by
* @param isNewInstance Whether to return a new instance of the shape or modify the current instance
* @returns The offset shape
*/
offset(t, e = !0) {
var i, n;
return e ? new S(
(i = this.lastPoint) == null ? void 0 : i.clone().add(t),
this.polylines.map((s) => s.map((o) => o.clone().add(t)))
) : ((n = this.lastPoint) == null || n.add(t), this.polylines.forEach((s) => s.forEach((o) => o.add(t))), this);
}
/**
* Converts the shape to an SVG string
* @param options SVG rendering options
* @returns SVG string
*/
toSVG(t = {}) {
const { strokeWidth: e = "0.5%", strokeColor: i = "black", isAutoFit: n = !1 } = t;
let s, o;
if (n) {
const r = this.bbox, c = 0.2, a = r.maxX - r.minX, h = r.maxY - r.minY, b = r.minX - a * c, p = r.maxX + a * c, g = r.minY - h * c, k = r.maxY + h * c;
o = this.polylines.map((w) => {
let M = "";
return w.forEach((x, P) => {
const m = x.x, u = -x.y;
M += P === 0 ? `M ${m} ${u} ` : `L ${m} ${u} `;
}), `<path d="${M}" stroke="${i}" stroke-width="${e}" fill="none"/>`;
}).join(""), s = `${b} ${-k} ${p - b} ${k - g}`;
} else
s = "0 0 20 20", o = this.polylines.map((r) => {
let c = "";
return r.forEach((a, h) => {
const b = a.x + 5, p = -a.y + 15;
c += h === 0 ? `M ${b} ${p} ` : `L ${b} ${p} `;
}), `<path d="${c}" stroke="${i}" stroke-width="${e}" fill="none"/>`;
}).join("");
return `<svg width="100%" height="100%" viewBox="${s}" preserveAspectRatio="xMidYMid meet">${o}</svg>`;
}
}
const F = Math.PI / 18, O = 12;
class $ {
constructor(t) {
this.shapeCache = /* @__PURE__ */ new Map(), this.shapeData = /* @__PURE__ */ new Map(), this.fontData = t;
}
/**
* Releases parsed shapes and cached shapes
*/
release() {
this.shapeCache.clear(), this.shapeData.clear();
}
/**
* Parses a character's shape
* @param code - The character code
* @param size - The font size
* @returns The parsed shape or undefined if the character is not found
*/
parse(t, e) {
var o;
const i = `${t}_${e}`;
if (this.shapeCache.has(i))
return this.shapeCache.get(i);
if (t === 0)
return;
const n = this.fontData.content.data;
let s;
if (!this.shapeData.has(t) && n[t]) {
const r = n[t], c = O / this.fontData.content.baseUp;
s = this.parseShape(r, c), this.shapeData.set(t, s);
}
if (this.shapeData.has(t)) {
const r = e / O, c = this.shapeData.get(t);
s = new S(
(o = c.lastPoint) == null ? void 0 : o.clone().multiply(r),
c.polylines.map((a) => a.map((h) => h.clone().multiply(r)))
);
}
return s;
}
/**
* Parses the shape of a character.
* @param data - The data of the character
* @param scale - The scale of the font
* @returns The parsed shape
*/
parseShape(t, e) {
const c = {
currentPoint: new l(),
polylines: [],
currentPolyline: [],
sp: [],
isPenDown: !1,
scale: e
};
for (let a = 0; a < t.length; a++) {
const h = t[a];
h <= 15 ? a = this.handleSpecialCommand(h, t, a, c) : this.handleVectorCommand(h, c);
}
return new S(c.currentPoint, c.polylines);
}
/**
* Please refer to special codes reference in the following link for more information.
* https://help.autodesk.com/view/OARX/2023/ENU/?guid=GUID-06832147-16BE-4A66-A6D0-3ADF98DC8228
* @param command - The command byte
* @param data - The data of the character
* @param index - The index of the command byte
* @param state - The state of the parser
* @returns The index of the next command byte
*/
handleSpecialCommand(t, e, i, n) {
let s = i;
switch (t) {
case 0:
n.currentPolyline = [], n.isPenDown = !1;
break;
case 1:
n.isPenDown = !0, n.currentPolyline.push(n.currentPoint.clone());
break;
case 2:
n.isPenDown = !1, n.currentPolyline.length > 1 && n.polylines.push(n.currentPolyline.slice()), n.currentPolyline = [];
break;
case 3:
s++, n.scale /= e[s];
break;
case 4:
s++, n.scale *= e[s];
break;
case 5:
if (n.sp.length === 4)
throw new Error("The position stack is only four locations deep");
n.sp.push(n.currentPoint.clone());
break;
case 6:
n.currentPoint = n.sp.pop() ?? n.currentPoint;
break;
case 7:
s = this.handleSubshapeCommand(e, s, n);
break;
case 8:
s = this.handleXYDisplacement(e, s, n);
break;
case 9:
s = this.handleMultipleXYDisplacements(e, s, n);
break;
case 10:
s = this.handleOctantArc(e, s, n);
break;
case 11:
s = this.handleFractionalArc(e, s, n);
break;
case 12:
s = this.handleBulgeArc(e, s, n);
break;
case 13:
s = this.handleMultipleBulgeArcs(e, s, n);
break;
case 14:
s = this.skipCode(e, ++s);
break;
}
return s;
}
handleVectorCommand(t, e) {
const i = (t & 240) >> 4, n = t & 15, s = this.getVectorForDirection(n);
e.currentPoint.add(s.multiply(i * e.scale)), e.isPenDown && e.currentPolyline.push(e.currentPoint.clone());
}
/**
* Get the vector for the given direction code. Please refer to the following link for more information.
* https://help.autodesk.com/view/OARX/2023/ENU/?guid=GUID-0A8E12A1-F4AB-44AD-8A9B-2140E0D5FD23
* @param dir - The direction code of the vector
* @returns Returns the vector for the given direction code
*/
getVectorForDirection(t) {
const e = new l();
switch (t) {
case 0:
e.x = 1;
break;
case 1:
e.x = 1, e.y = 0.5;
break;
case 2:
e.x = 1, e.y = 1;
break;
case 3:
e.x = 0.5, e.y = 1;
break;
case 4:
e.y = 1;
break;
case 5:
e.x = -0.5, e.y = 1;
break;
case 6:
e.x = -1, e.y = 1;
break;
case 7:
e.x = -1, e.y = 0.5;
break;
case 8:
e.x = -1;
break;
case 9:
e.x = -1, e.y = -0.5;
break;
case 10:
e.x = -1, e.y = -1;
break;
case 11:
e.x = -0.5, e.y = -1;
break;
case 12:
e.y = -1;
break;
case 13:
e.x = 0.5, e.y = -1;
break;
case 14:
e.x = 1, e.y = -1;
break;
case 15:
e.x = 1, e.y = -0.5;
break;
}
return e;
}
handleSubshapeCommand(t, e, i) {
let n = e, s = 0, o, r = i.scale * this.fontData.content.baseUp, c = r;
const a = i.currentPoint.clone();
switch (i.currentPolyline.length > 1 && (i.polylines.push(i.currentPolyline.slice()), i.currentPolyline = []), this.fontData.header.fontType) {
case y.SHAPES:
n++, s = t[n];
break;
case y.BIGFONT:
n++, s = t[n], s === 0 && (n++, s = t[n++] | t[n++] << 8, a.x = t[n++] * i.scale, a.y = t[n++] * i.scale, this.fontData.content.isExtended && (c = t[n++] * i.scale), r = t[n] * i.scale);
break;
case y.UNIFONT:
n++, s = t[n++] | t[n++] << 8;
break;
}
return s !== 0 && (o = this.getShapeByCodeWithOffset(s, c, r, a), o && (i.polylines.push(...o.polylines.slice()), i.currentPoint = o.lastPoint ? o.lastPoint.clone() : a.clone())), i.currentPolyline = [], n;
}
handleXYDisplacement(t, e, i) {
let n = e;
const s = new l();
return s.x = f.byteToSByte(t[++n]), s.y = f.byteToSByte(t[++n]), i.currentPoint.add(s.multiply(i.scale)), i.isPenDown && i.currentPolyline.push(i.currentPoint.clone()), n;
}
handleMultipleXYDisplacements(t, e, i) {
let n = e;
for (; ; ) {
const s = new l();
if (s.x = f.byteToSByte(t[++n]), s.y = f.byteToSByte(t[++n]), s.x === 0 && s.y === 0)
break;
i.currentPoint.add(s.multiply(i.scale)), i.isPenDown && i.currentPolyline.push(i.currentPoint.clone());
}
return n;
}
handleOctantArc(t, e, i) {
var g;
let n = e;
const s = t[++n] * i.scale, o = f.byteToSByte(t[++n]), r = (o & 112) >> 4;
let c = o & 7;
const a = o < 0, h = Math.PI / 4 * r, b = i.currentPoint.clone().subtract(new l(Math.cos(h) * s, Math.sin(h) * s)), p = C.fromOctant(b, s, r, c, a);
if (i.isPenDown) {
const k = p.tessellate();
i.currentPolyline.pop(), i.currentPolyline.push(...k.slice());
}
return i.currentPoint = (g = p.tessellate().pop()) == null ? void 0 : g.clone(), n;
}
handleFractionalArc(t, e, i) {
let n = e;
const s = t[++n], o = t[++n], r = t[++n], c = t[++n], a = (r * 255 + c) * i.scale, h = f.byteToSByte(t[++n]), b = (h & 112) >> 4;
let p = h & 7;
p === 0 && (p = 8), o !== 0 && p--;
const g = Math.PI / 4;
let k = g * p, w = F, M = 1;
h < 0 && (w = -w, k = -k, M = -1);
let x = g * b, P = x + k;
x += g * s / 256 * M, P += g * o / 256 * M;
const m = i.currentPoint.clone().subtract(new l(a * Math.cos(x), a * Math.sin(x)));
if (i.currentPoint = m.clone().add(new l(a * Math.cos(P), a * Math.sin(P))), i.isPenDown) {
let u = x;
const D = [];
if (D.push(
m.clone().add(new l(a * Math.cos(u), a * Math.sin(u)))
), w > 0)
for (; u + w < P; )
u += w, D.push(
m.clone().add(new l(a * Math.cos(u), a * Math.sin(u)))
);
else
for (; u + w > P; )
u += w, D.push(
m.clone().add(new l(a * Math.cos(u), a * Math.sin(u)))
);
D.push(m.clone().add(new l(a * Math.cos(P), a * Math.sin(P)))), i.currentPolyline.push(...D);
}
return n;
}
handleBulgeArc(t, e, i) {
let n = e;
const s = new l();
s.x = f.byteToSByte(t[++n]), s.y = f.byteToSByte(t[++n]);
const o = f.byteToSByte(t[++n]);
return i.currentPoint = this.handleArcSegment(
i.currentPoint,
s,
o,
i.scale,
i.isPenDown,
i.currentPolyline
), n;
}
handleMultipleBulgeArcs(t, e, i) {
let n = e;
for (; ; ) {
const s = new l();
if (s.x = f.byteToSByte(t[++n]), s.y = f.byteToSByte(t[++n]), s.x === 0 && s.y === 0)
break;
const o = f.byteToSByte(t[++n]);
i.currentPoint = this.handleArcSegment(
i.currentPoint,
s,
o,
i.scale,
i.isPenDown,
i.currentPolyline
);
}
return n;
}
skipCode(t, e) {
switch (t[e]) {
case 0:
break;
case 1:
break;
case 2:
break;
case 3:
case 4:
e++;
break;
case 5:
break;
case 6:
break;
case 7:
switch (this.fontData.header.fontType) {
case y.SHAPES:
e++;
break;
case y.BIGFONT:
e++, t[e] === 0 && (e += 5);
break;
case y.UNIFONT:
e += 2;
break;
}
break;
case 8:
e += 2;
break;
case 9:
for (; ; ) {
const s = t[++e], o = t[++e];
if (s === 0 && o === 0)
break;
}
break;
case 10:
e += 2;
break;
case 11:
e += 5;
break;
case 12:
e += 3;
break;
case 13:
for (; ; ) {
const s = t[++e], o = t[++e];
if (s === 0 && o === 0)
break;
e++;
}
break;
}
return e;
}
getShapeByCodeWithOffset(t, e, i, n) {
var o;
const s = this.parse(t, i);
if (s) {
if (e === i)
return s.offset(n);
{
const r = (o = s.lastPoint) == null ? void 0 : o.clone();
r && (r.x *= e / i);
const c = s.polylines.map((a) => a.map((h) => h.clone()));
return c.forEach((a) => a.forEach((h) => h.x *= e / i)), new S(
r == null ? void 0 : r.add(n),
c.map((a) => a.map((h) => h.add(n)))
);
}
}
}
/**
* Handles drawing an arc segment with the given vector and bulge
* @param currentPoint The starting point of the arc
* @param vec The displacement vector
* @param bulge The bulge value (will be normalized by 127.0)
* @param scale The current scale factor
* @param isPenDown Whether the pen is currently down (drawing)
* @param currentPolyline The current polyline being built
* @returns The new current point after the arc
*/
handleArcSegment(t, e, i, n, s, o) {
e.x *= n, e.y *= n, i < -127 && (i = -127);
const r = t.clone();
if (s)
if (i === 0)
o.push(r.clone().add(e));
else {
const c = r.clone().add(e), h = C.fromBulge(r, c, i / 127).tessellate();
o.push(...h.slice(1));
}
return r.add(e), r;
}
}
class v {
/**
* Creates a new ShxFont instance.
* @param data - Either raw binary data of the SHX font file (ArrayBuffer) or pre-parsed font data (ShxFontData)
* @throws {Error} If the font data is invalid or cannot be parsed
*/
constructor(t) {
if (t instanceof ArrayBuffer) {
const e = new f(t), n = new E().parse(e), o = T.createParser(n.fontType).parse(e);
this.fontData = {
header: n,
content: o
};
} else
this.fontData = t;
this.shapeParser = new $(this.fontData);
}
/**
* Gets the shape data for a specific character at a given size.
* @param code - The character code to get the shape for
* @param size - The desired size of the character in drawing units
* @returns The shape data for the character, or undefined if the character is not found in the font
*/
getCharShape(t, e) {
return this.shapeParser.parse(t, e);
}
/**
* Releases resources used by the font.
* This should be called when the font is no longer needed to free up memory.
*/
release() {
this.shapeParser.release();
}
}
export {
l as Point,
v as ShxFont,
y as ShxFontType,
S as ShxShape,
$ as ShxShapeParser
};
//# sourceMappingURL=index.es.js.map