printmaker
Version:
Generate PDF documents and from JavaScript objects
176 lines (157 loc) • 4.77 kB
text/typescript
import { PDFImage } from 'pdf-lib';
import { Pos } from './box.js';
import { Color, parseColor } from './colors.js';
import {
asArray,
asBoolean,
asNonNegNumber,
asNumber,
asObject,
check,
getFrom,
Obj,
optional,
pickDefined,
required,
typeError,
} from './types.js';
export type GraphicsObject = RectObject | LineObject | PolylineObject | ImageObject;
export type RectObject = {
type: 'rect';
x: number;
y: number;
width: number;
height: number;
strokeWidth?: number;
strokeColor?: Color;
fillColor?: Color;
};
export type LineObject = {
type: 'line';
x1: number;
y1: number;
x2: number;
y2: number;
strokeWidth?: number;
strokeColor?: Color;
};
export type PolylineObject = {
type: 'polyline';
points: { x: number; y: number }[];
closePath?: boolean;
strokeWidth?: number;
strokeColor?: Color;
fillColor?: Color;
};
export type ImageObject = {
type: 'image';
x: number;
y: number;
width: number;
height: number;
image: PDFImage;
};
export function parseGraphics(input: unknown): GraphicsObject[] {
return asArray(input)?.map((el, idx) => check(el, `[${idx}]`, parseGraphicsObject));
}
/**
* Parses a given input as a graphics shape object. Throws if the input cannot be parsed.
*
* @param input The input value to parse.
* @returns A graphics shape object.
*/
export function parseGraphicsObject(input: unknown): GraphicsObject {
const shape = asObject(input);
const type = getFrom(shape, 'type', required(asGraphicsType));
switch (type) {
case 'rect':
return parseRect(shape);
case 'line':
return parseLine(shape);
case 'polyline':
return parsePolyline(shape);
}
}
function asGraphicsType(input: unknown): string {
if (input === 'rect' || input === 'line' || input === 'polyline') return input;
throw typeError("'rect', 'line', or 'polyline'", input);
}
function parseRect(input: Obj): RectObject {
return pickDefined({
type: 'rect',
x: getFrom(input, 'x', required(asNumber)),
y: getFrom(input, 'y', required(asNumber)),
width: getFrom(input, 'width', required(asNumber)),
height: getFrom(input, 'height', required(asNumber)),
strokeWidth: getFrom(input, 'strokeWidth', optional(asNonNegNumber)),
strokeColor: getFrom(input, 'strokeColor', optional(parseColor)),
fillColor: getFrom(input, 'fillColor', optional(parseColor)),
}) as RectObject;
}
function parseLine(input: Obj): LineObject {
return pickDefined({
type: 'line',
x1: getFrom(input, 'x1', required(asNumber)),
x2: getFrom(input, 'x2', required(asNumber)),
y1: getFrom(input, 'y1', required(asNumber)),
y2: getFrom(input, 'y2', required(asNumber)),
strokeWidth: getFrom(input, 'strokeWidth', optional(asNonNegNumber)),
strokeColor: getFrom(input, 'strokeColor', optional(parseColor)),
}) as LineObject;
}
function parsePolyline(input: Obj): PolylineObject {
return pickDefined({
type: 'polyline',
points: getFrom(input, 'points', required(asPoints)),
closePath: getFrom(input, 'closePath', optional(asBoolean)),
strokeWidth: getFrom(input, 'strokeWidth', optional(asNonNegNumber)),
strokeColor: getFrom(input, 'strokeColor', optional(parseColor)),
fillColor: getFrom(input, 'fillColor', optional(parseColor)),
}) as PolylineObject;
}
/**
* Shifts the a graphics object to a given position by adding the position's `x` and `y` values
* to the coordinates of the graphics object.
*
* @param rect The input graphics object to shift
* @param pos The position to shift to
* @returns The new graphics object
*/
export function shiftGraphicsObject<T extends GraphicsObject>(shape: T, pos: Pos): T {
switch (shape.type) {
case 'rect':
return shiftRect(shape, pos) as T;
case 'line':
return shiftLine(shape, pos) as T;
case 'polyline':
return shiftPolyline(shape, pos) as T;
}
}
function shiftRect(rect: RectObject, pos: Pos): RectObject {
return { ...rect, x: rect.x + pos.x, y: rect.y + pos.y };
}
function shiftLine(line: LineObject, pos: Pos): LineObject {
return {
...line,
x1: line.x1 + pos.x,
x2: line.x2 + pos.x,
y1: line.y1 + pos.y,
y2: line.y2 + pos.y,
};
}
function shiftPolyline(polyline: PolylineObject, pos: Pos): PolylineObject {
return {
...polyline,
points: polyline.points.map((p) => ({ x: p.x + pos.x, y: p.y + pos.y })),
};
}
function asPoints(input: unknown): { x: number; y: number }[] {
return asArray(input).map((point, idx) => check(point, `[${idx}]`, asPoint));
}
function asPoint(input: unknown): { x: number; y: number } {
const obj = asObject(input);
return {
x: getFrom(obj, 'x', required(asNumber)),
y: getFrom(obj, 'y', required(asNumber)),
};
}