Initial Commit
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import { PDF_CODES, PdfError } from "../error-codes/error-codes.js";
|
||||
import { ContentStreamEncoder } from "./content-stream.js";
|
||||
|
||||
describe("ContentStreamEncoder — graphics state", () => {
|
||||
it("save / restore graphics state balance", { timeout: 5000 }, () => {
|
||||
const out = new ContentStreamEncoder()
|
||||
.saveGraphicsState()
|
||||
.restoreGraphicsState()
|
||||
.encode();
|
||||
expect(out).toBe("q\nQ");
|
||||
});
|
||||
|
||||
it("restoreGraphicsState without matching q raises", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
expect(() =>
|
||||
new ContentStreamEncoder().restoreGraphicsState()
|
||||
).toThrowError(PdfError);
|
||||
});
|
||||
|
||||
it("encode rejects unmatched save", { timeout: 5000 }, () => {
|
||||
const enc = new ContentStreamEncoder().saveGraphicsState();
|
||||
expect(() => enc.encode()).toThrowError(PdfError);
|
||||
});
|
||||
|
||||
it("concatMatrix and setLineWidth emit numeric ops", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const out = new ContentStreamEncoder()
|
||||
.concatMatrix(1, 0, 0, 1, 100, 200)
|
||||
.setLineWidth(2.5)
|
||||
.encode();
|
||||
expect(out).toContain("1 0 0 1 100 200 cm");
|
||||
expect(out).toContain("2.5000 w");
|
||||
});
|
||||
});
|
||||
|
||||
describe("ContentStreamEncoder — paths", () => {
|
||||
it("rectangle + fill emits expected ops", { timeout: 5000 }, () => {
|
||||
const out = new ContentStreamEncoder()
|
||||
.setFillColor({ r: 1, g: 0, b: 0 })
|
||||
.rectangle(10, 10, 100, 50)
|
||||
.fill()
|
||||
.encode();
|
||||
expect(out).toContain("1 0 0 rg");
|
||||
expect(out).toContain("10 10 100 50 re");
|
||||
expect(out).toContain("f");
|
||||
});
|
||||
|
||||
it("moveTo / lineTo / closePath / stroke chain", { timeout: 5000 }, () => {
|
||||
const out = new ContentStreamEncoder()
|
||||
.moveTo(0, 0)
|
||||
.lineTo(10, 0)
|
||||
.closePath()
|
||||
.stroke()
|
||||
.encode();
|
||||
expect(out).toContain("0 0 m");
|
||||
expect(out).toContain("10 0 l");
|
||||
expect(out).toContain("h");
|
||||
expect(out).toContain("S");
|
||||
});
|
||||
|
||||
it("closeAndStroke and fillAndStroke emit the right ops", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const out = new ContentStreamEncoder()
|
||||
.rectangle(0, 0, 5, 5)
|
||||
.closeAndStroke()
|
||||
.rectangle(0, 0, 5, 5)
|
||||
.fillAndStroke()
|
||||
.encode();
|
||||
expect(out).toContain("s");
|
||||
expect(out).toContain("B");
|
||||
});
|
||||
});
|
||||
|
||||
describe("ContentStreamEncoder — colour", () => {
|
||||
it("setStrokeColor emits RG", { timeout: 5000 }, () => {
|
||||
const out = new ContentStreamEncoder()
|
||||
.setStrokeColor({ r: 0, g: 0.5, b: 1 })
|
||||
.encode();
|
||||
expect(out).toContain("0 0.5000 1 RG");
|
||||
});
|
||||
|
||||
it("out-of-range channel raises INVALID_RGB", { timeout: 5000 }, () => {
|
||||
try {
|
||||
new ContentStreamEncoder().setFillColor({
|
||||
r: 1.5,
|
||||
g: 0,
|
||||
b: 0,
|
||||
});
|
||||
throw new Error("expected throw");
|
||||
} catch (err) {
|
||||
expect(err).toBeInstanceOf(PdfError);
|
||||
expect((err as PdfError).code).toBe(PDF_CODES.INVALID_RGB);
|
||||
}
|
||||
});
|
||||
|
||||
it("NaN channel raises INVALID_RGB on stroke", { timeout: 5000 }, () => {
|
||||
try {
|
||||
new ContentStreamEncoder().setStrokeColor({
|
||||
r: Number.NaN,
|
||||
g: 0,
|
||||
b: 0,
|
||||
});
|
||||
throw new Error("expected throw");
|
||||
} catch (err) {
|
||||
expect(err).toBeInstanceOf(PdfError);
|
||||
expect((err as PdfError).code).toBe(PDF_CODES.INVALID_RGB);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("ContentStreamEncoder — text", () => {
|
||||
it("text-showing operators round-trip", { timeout: 5000 }, () => {
|
||||
const out = new ContentStreamEncoder()
|
||||
.beginText()
|
||||
.setFont("F1", 12)
|
||||
.moveTextCursor(100, 700)
|
||||
.showText("Hello")
|
||||
.endText()
|
||||
.encode();
|
||||
expect(out).toContain("BT");
|
||||
expect(out).toContain("/F1 12 Tf");
|
||||
expect(out).toContain("100 700 Td");
|
||||
expect(out).toContain("(Hello) Tj");
|
||||
expect(out).toContain("ET");
|
||||
});
|
||||
|
||||
it("showText outside text mode raises", { timeout: 5000 }, () => {
|
||||
try {
|
||||
new ContentStreamEncoder().showText("oops");
|
||||
throw new Error("expected throw");
|
||||
} catch (err) {
|
||||
expect(err).toBeInstanceOf(PdfError);
|
||||
expect((err as PdfError).code).toBe(PDF_CODES.RENDER_FAILED);
|
||||
}
|
||||
});
|
||||
|
||||
it("beginText twice without endText raises", { timeout: 5000 }, () => {
|
||||
const enc = new ContentStreamEncoder().beginText();
|
||||
expect(() => enc.beginText()).toThrowError(PdfError);
|
||||
});
|
||||
|
||||
it("endText outside text mode raises", { timeout: 5000 }, () => {
|
||||
expect(() => new ContentStreamEncoder().endText()).toThrowError(
|
||||
PdfError
|
||||
);
|
||||
});
|
||||
|
||||
it("encode with unclosed text object raises", { timeout: 5000 }, () => {
|
||||
const enc = new ContentStreamEncoder().beginText().setFont("F1", 12);
|
||||
expect(() => enc.encode()).toThrowError(PdfError);
|
||||
});
|
||||
|
||||
it("setTextMatrix / setLeading / nextLine emit the right operators", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const out = new ContentStreamEncoder()
|
||||
.beginText()
|
||||
.setFont("/F2", 10)
|
||||
.setTextMatrix(1, 0, 0, 1, 50, 50)
|
||||
.setLeading(14)
|
||||
.showText("line1")
|
||||
.nextLine()
|
||||
.showText("line2")
|
||||
.endText()
|
||||
.encode();
|
||||
expect(out).toContain("1 0 0 1 50 50 Tm");
|
||||
expect(out).toContain("14 TL");
|
||||
expect(out).toContain("T*");
|
||||
expect(out).toContain("(line1) Tj");
|
||||
expect(out).toContain("(line2) Tj");
|
||||
expect(out).toContain("/F2 10 Tf");
|
||||
});
|
||||
|
||||
it("escapes parens + backslashes in showText", { timeout: 5000 }, () => {
|
||||
const out = new ContentStreamEncoder()
|
||||
.beginText()
|
||||
.setFont("F1", 12)
|
||||
.showText("(hi\\there)")
|
||||
.endText()
|
||||
.encode();
|
||||
expect(out).toContain("(\\(hi\\\\there\\)) Tj");
|
||||
});
|
||||
});
|
||||
|
||||
describe("ContentStreamEncoder — XObjects", () => {
|
||||
it("drawImage emits /name Do", { timeout: 5000 }, () => {
|
||||
const out = new ContentStreamEncoder().drawImage("Im0").encode();
|
||||
expect(out).toContain("/Im0 Do");
|
||||
});
|
||||
|
||||
it("drawImage inside text object raises", { timeout: 5000 }, () => {
|
||||
const enc = new ContentStreamEncoder().beginText();
|
||||
try {
|
||||
enc.drawImage("Im0");
|
||||
throw new Error("expected throw");
|
||||
} catch (err) {
|
||||
expect(err).toBeInstanceOf(PdfError);
|
||||
expect((err as PdfError).code).toBe(PDF_CODES.RENDER_FAILED);
|
||||
}
|
||||
});
|
||||
|
||||
it("drawImage accepts an already-prefixed name", { timeout: 5000 }, () => {
|
||||
const out = new ContentStreamEncoder().drawImage("/Im1").encode();
|
||||
expect(out).toContain("/Im1 Do");
|
||||
});
|
||||
});
|
||||
|
||||
describe("ContentStreamEncoder — non-strict mode", () => {
|
||||
it("non-strict allows out-of-order ops", { timeout: 5000 }, () => {
|
||||
const enc = new ContentStreamEncoder({ strict: false });
|
||||
// Sequence below would throw in strict mode.
|
||||
enc.restoreGraphicsState();
|
||||
enc.showText("free-form");
|
||||
enc.endText();
|
||||
const out = enc.encode();
|
||||
expect(out).toContain("Q");
|
||||
expect(out).toContain("(free-form) Tj");
|
||||
expect(out).toContain("ET");
|
||||
});
|
||||
});
|
||||
|
||||
describe("ContentStreamEncoder — appendRaw + length", () => {
|
||||
it("appendRaw injects a custom operator", { timeout: 5000 }, () => {
|
||||
const enc = new ContentStreamEncoder().appendRaw("0 0 0 rg");
|
||||
expect(enc.length).toBe(1);
|
||||
expect(enc.encode()).toBe("0 0 0 rg");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user