feature: generate pdf from actor
This commit is contained in:
26
src/elements/abstract-element.ts
Normal file
26
src/elements/abstract-element.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import jsPDF from 'jspdf';
|
||||
import { MARGINS } from '../constants';
|
||||
|
||||
export abstract class AbstractElement {
|
||||
public x: number;
|
||||
public y: number;
|
||||
public maxWidth?: number;
|
||||
|
||||
constructor(x: number, y: number, maxWidth?: number | undefined) {
|
||||
this.x = x >= MARGINS.left ? x : MARGINS.left;
|
||||
this.y = y >= MARGINS.top ? y : MARGINS.top;
|
||||
this.maxWidth = maxWidth;
|
||||
}
|
||||
|
||||
public getHeightFromPx(doc: jsPDF, size: number) {
|
||||
return size / doc.internal.scaleFactor;
|
||||
}
|
||||
|
||||
protected updateMaxWidth(maxWidth?: number) {
|
||||
this.maxWidth = maxWidth;
|
||||
}
|
||||
|
||||
public abstract render(doc: jsPDF, maxWidth?: number): jsPDF;
|
||||
|
||||
public abstract getHeight(doc?: jsPDF): number;
|
||||
}
|
||||
22
src/elements/box.ts
Normal file
22
src/elements/box.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { AbstractElement } from './abstract-element';
|
||||
import jsPDF from 'jspdf';
|
||||
|
||||
export class Box extends AbstractElement {
|
||||
public w: number;
|
||||
public h: number;
|
||||
|
||||
constructor(x: number, y: number, w: number, h: number) {
|
||||
super(x, y, w);
|
||||
this.w = w;
|
||||
this.h = h;
|
||||
}
|
||||
|
||||
public render(doc: jsPDF, _maxWidth?: number): jsPDF {
|
||||
doc.rect(this.x, this.y, this.w, this.h);
|
||||
return doc;
|
||||
}
|
||||
|
||||
public getHeight(_doc): number {
|
||||
return this.h;
|
||||
}
|
||||
}
|
||||
36
src/elements/column.ts
Normal file
36
src/elements/column.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { AbstractElement } from './abstract-element';
|
||||
import jsPDF from 'jspdf';
|
||||
|
||||
export class Column extends AbstractElement {
|
||||
public elements: AbstractElement[] = [];
|
||||
|
||||
constructor(x: number, y: number, elements: AbstractElement[]) {
|
||||
super(x, y);
|
||||
this.elements = elements;
|
||||
}
|
||||
|
||||
public render(doc: jsPDF, _maxWidth?: number): jsPDF {
|
||||
const elements = this.elements ?? [];
|
||||
|
||||
let currentY = this.y;
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
const element = elements[i];
|
||||
element.x = this.x;
|
||||
element.y = currentY;
|
||||
element.render(doc);
|
||||
currentY += element.getHeight(doc) + 2;
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
public getHeight(doc): number {
|
||||
return this.elements
|
||||
.map((e) => e.getHeight(doc))
|
||||
.reduce((p, c, i) => {
|
||||
if (i === 0) {
|
||||
return c;
|
||||
}
|
||||
return p + c + 2;
|
||||
});
|
||||
}
|
||||
}
|
||||
22
src/elements/image.ts
Normal file
22
src/elements/image.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Box } from './box';
|
||||
import jsPDF from 'jspdf';
|
||||
|
||||
export class Image extends Box {
|
||||
public imageData: string;
|
||||
|
||||
constructor(x: number, y: number, w: number, h: number, imageData: string) {
|
||||
super(x, y, w, h);
|
||||
this.imageData = imageData;
|
||||
}
|
||||
|
||||
public render(doc: jsPDF, _maxWidth?: number): jsPDF {
|
||||
doc.addImage({
|
||||
imageData: this.imageData,
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
width: this.w,
|
||||
height: this.h,
|
||||
});
|
||||
return doc;
|
||||
}
|
||||
}
|
||||
53
src/elements/labelled-text.ts
Normal file
53
src/elements/labelled-text.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import jsPDF, { TextOptionsLight } from 'jspdf';
|
||||
import { Text } from './text';
|
||||
import { i18nLocalize, LABEL_SIZE, TEXT_SIZE } from '../constants';
|
||||
|
||||
export class LabelledText extends Text {
|
||||
public label: string;
|
||||
public labelOptions?: TextOptionsLight;
|
||||
|
||||
constructor(
|
||||
x: number,
|
||||
y: number,
|
||||
label: string,
|
||||
text: string,
|
||||
textOptions?: TextOptionsLight,
|
||||
labelOptions?: TextOptionsLight
|
||||
) {
|
||||
super(x, y, text, textOptions);
|
||||
this.label = label;
|
||||
this.labelOptions = labelOptions;
|
||||
const textMaxWidth = this.textOptions?.maxWidth ?? 0;
|
||||
const labelMaxWidth = this.labelOptions?.maxWidth ?? 0;
|
||||
const maxWidth = Math.max(textMaxWidth, labelMaxWidth);
|
||||
this.updateMaxWidth(maxWidth);
|
||||
}
|
||||
|
||||
public render(doc: jsPDF, maxWidth?: number): jsPDF {
|
||||
this.updateMaxWidth(maxWidth);
|
||||
const yLabel = this.y + this.getHeightFromPx(doc, LABEL_SIZE);
|
||||
const yText = yLabel + this.getHeightFromPx(doc, TEXT_SIZE) + 1;
|
||||
doc
|
||||
.setFontSize(LABEL_SIZE)
|
||||
.text(i18nLocalize(this.label), this.x, yLabel, this.labelOptions)
|
||||
.setFontSize(TEXT_SIZE)
|
||||
.text(this.text, this.x, yText, this.textOptions);
|
||||
return doc;
|
||||
}
|
||||
|
||||
protected updateMaxWidth(maxWidth?: number) {
|
||||
if (maxWidth != null && maxWidth > 0) {
|
||||
this.maxWidth = maxWidth;
|
||||
const textOpts: TextOptionsLight = this.textOptions ?? {};
|
||||
const labelOpts: TextOptionsLight = this.labelOptions ?? {};
|
||||
textOpts.maxWidth = maxWidth;
|
||||
labelOpts.maxWidth = maxWidth;
|
||||
this.textOptions = textOpts;
|
||||
this.labelOptions = labelOpts;
|
||||
}
|
||||
}
|
||||
|
||||
public getHeight(doc): number {
|
||||
return this.getHeightFromPx(doc, TEXT_SIZE + LABEL_SIZE) + 1;
|
||||
}
|
||||
}
|
||||
30
src/elements/labelled-value.ts
Normal file
30
src/elements/labelled-value.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Row } from './row';
|
||||
import { Text } from './text';
|
||||
|
||||
export class LabelledValue extends Row {
|
||||
public label: string;
|
||||
public value: number;
|
||||
|
||||
constructor(
|
||||
label: string,
|
||||
value: number,
|
||||
widthPercents?: number[],
|
||||
maxWidth?: number
|
||||
) {
|
||||
super(
|
||||
0,
|
||||
0,
|
||||
[
|
||||
new Text(0, 0, label),
|
||||
new Text(0, 0, value.toString(), {
|
||||
align: 'right',
|
||||
}),
|
||||
],
|
||||
maxWidth,
|
||||
widthPercents,
|
||||
[]
|
||||
);
|
||||
this.label = label;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
82
src/elements/labelled-values.ts
Normal file
82
src/elements/labelled-values.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { Row } from './row';
|
||||
import { Column } from './column';
|
||||
import { LabelledValue } from './labelled-value';
|
||||
import jsPDF from 'jspdf';
|
||||
import { MARGINS } from '../constants';
|
||||
|
||||
export class LabelledValues extends Row {
|
||||
public labelledValues: { label: string; value: number }[];
|
||||
public nbrOfCol: number;
|
||||
|
||||
constructor(
|
||||
x: number,
|
||||
y: number,
|
||||
labelledValues: { label: string; value: number }[],
|
||||
nbrOfCol?: number
|
||||
) {
|
||||
super(x, y, []);
|
||||
this.labelledValues = labelledValues;
|
||||
this.nbrOfCol = nbrOfCol ?? 3;
|
||||
if (this.nbrOfCol > 3) {
|
||||
this.nbrOfCol = 3;
|
||||
}
|
||||
const valuePercent = 5 * this.nbrOfCol;
|
||||
const labelPercent = 100 - valuePercent;
|
||||
const widthPercent = [labelPercent, valuePercent];
|
||||
let currentIndex = 0;
|
||||
if (labelledValues.length >= this.nbrOfCol) {
|
||||
const nbrPerCol = Math.floor(labelledValues.length / this.nbrOfCol);
|
||||
const rest = labelledValues.length - nbrPerCol * this.nbrOfCol;
|
||||
const nbrPerCols = [
|
||||
rest > 0 ? nbrPerCol + 1 : nbrPerCol,
|
||||
rest > 1 ? nbrPerCol + 1 : nbrPerCol,
|
||||
rest > 2 ? nbrPerCol + 1 : nbrPerCol,
|
||||
];
|
||||
for (let i = 0; i < this.nbrOfCol; i++) {
|
||||
this.elements[i] = new Column(0, 0, []);
|
||||
}
|
||||
for (let i = 0; i < labelledValues.length; i++) {
|
||||
if (i < nbrPerCols[0]) {
|
||||
currentIndex = 0;
|
||||
} else if (i < nbrPerCols[0] + nbrPerCols[1]) {
|
||||
currentIndex = 1;
|
||||
} else {
|
||||
currentIndex = 2;
|
||||
}
|
||||
(<Column>this.elements[currentIndex]).elements.push(
|
||||
new LabelledValue(
|
||||
labelledValues[i].label,
|
||||
labelledValues[i].value,
|
||||
widthPercent
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.elements.push(
|
||||
new Column(
|
||||
0,
|
||||
0,
|
||||
labelledValues.map(
|
||||
(libelledValue) =>
|
||||
new LabelledValue(
|
||||
libelledValue.label,
|
||||
libelledValue.value,
|
||||
widthPercent
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public render(doc: jsPDF, maxWidth?: number): jsPDF {
|
||||
const pageWidth = doc.internal.pageSize.width;
|
||||
const rowWidth = pageWidth - this.x - MARGINS.right;
|
||||
for (const column of this.elements) {
|
||||
for (const labelledValue of (<Column>column).elements) {
|
||||
labelledValue.maxWidth = rowWidth / this.nbrOfCol;
|
||||
}
|
||||
}
|
||||
return super.render(doc, maxWidth);
|
||||
}
|
||||
}
|
||||
66
src/elements/row.ts
Normal file
66
src/elements/row.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { AbstractElement } from './abstract-element';
|
||||
import jsPDF from 'jspdf';
|
||||
import { MARGINS } from '../constants';
|
||||
|
||||
export class Row extends AbstractElement {
|
||||
public elements: AbstractElement[] = [];
|
||||
public widthPercents?: number[];
|
||||
public maxWidths?: number[];
|
||||
|
||||
constructor(
|
||||
x: number,
|
||||
y: number,
|
||||
elements: AbstractElement[],
|
||||
maxWidth?: number | undefined,
|
||||
widthPercents?: number[],
|
||||
maxWidths?: number[]
|
||||
) {
|
||||
super(x, y, maxWidth);
|
||||
this.elements = elements ?? [];
|
||||
this.widthPercents = widthPercents ?? [];
|
||||
this.maxWidths = maxWidths ?? [];
|
||||
}
|
||||
|
||||
public render(doc: jsPDF, maxWidth?: number): jsPDF {
|
||||
const elements = this.elements ?? [];
|
||||
let maxWidths = this.maxWidths ?? [];
|
||||
let widthPercents = this.widthPercents ?? [];
|
||||
|
||||
if (widthPercents.length !== elements.length) {
|
||||
widthPercents = [];
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
if (widthPercents[i] == null) {
|
||||
widthPercents[i] = 100 / elements.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (maxWidths.length !== elements.length) {
|
||||
maxWidths = [];
|
||||
}
|
||||
|
||||
const pageWidth = doc.internal.pageSize.width;
|
||||
const rowWidth =
|
||||
maxWidth ?? this.maxWidth ?? pageWidth - this.x - MARGINS.right;
|
||||
|
||||
let currentX = this.x;
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
const element = elements[i];
|
||||
const percent = widthPercents[i];
|
||||
const percentWidth = (rowWidth * percent) / 100;
|
||||
const maxChildWidth = maxWidths[i] ?? percentWidth;
|
||||
element.x = currentX;
|
||||
element.y = this.y;
|
||||
element.render(doc, maxChildWidth);
|
||||
currentX += percentWidth;
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
public getHeight(doc?: jsPDF): number {
|
||||
let maxHeight = 0;
|
||||
for (const element of this.elements) {
|
||||
maxHeight = Math.max(maxHeight, element.getHeight(doc));
|
||||
}
|
||||
return maxHeight;
|
||||
}
|
||||
}
|
||||
45
src/elements/text.ts
Normal file
45
src/elements/text.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { AbstractElement } from './abstract-element';
|
||||
import jsPDF, { TextOptionsLight } from 'jspdf';
|
||||
import { i18nLocalize, TEXT_SIZE } from '../constants';
|
||||
|
||||
export class Text extends AbstractElement {
|
||||
public text: string;
|
||||
public textOptions?: TextOptionsLight;
|
||||
|
||||
constructor(
|
||||
x: number,
|
||||
y: number,
|
||||
text: string,
|
||||
textOptions?: TextOptionsLight
|
||||
) {
|
||||
super(x, y, textOptions?.maxWidth);
|
||||
this.text = text;
|
||||
this.textOptions = textOptions;
|
||||
}
|
||||
|
||||
public render(doc: jsPDF, maxWidth?: number): jsPDF {
|
||||
this.updateMaxWidth(maxWidth);
|
||||
let finalText = [i18nLocalize(this.text)];
|
||||
if (this.maxWidth != null) {
|
||||
finalText = doc.splitTextToSize(finalText[0], maxWidth ?? 0);
|
||||
}
|
||||
const yText = this.y + this.getHeightFromPx(doc, TEXT_SIZE);
|
||||
doc
|
||||
.setFontSize(TEXT_SIZE)
|
||||
.text(finalText[0], this.x, yText, this.textOptions);
|
||||
return doc;
|
||||
}
|
||||
|
||||
protected updateMaxWidth(maxWidth?: number) {
|
||||
if (maxWidth != null && maxWidth > 0) {
|
||||
this.maxWidth = maxWidth;
|
||||
const options: TextOptionsLight = this.textOptions ?? {};
|
||||
options.maxWidth = this.maxWidth;
|
||||
this.textOptions = options;
|
||||
}
|
||||
}
|
||||
|
||||
public getHeight(doc): number {
|
||||
return this.getHeightFromPx(doc, TEXT_SIZE);
|
||||
}
|
||||
}
|
||||
66
src/elements/texts.ts
Normal file
66
src/elements/texts.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Row } from './row';
|
||||
import { Column } from './column';
|
||||
import jsPDF from 'jspdf';
|
||||
import { MARGINS } from '../constants';
|
||||
import { Text } from './text';
|
||||
|
||||
export class Texts extends Row {
|
||||
public texts: string[];
|
||||
public nbrOfCol: number;
|
||||
|
||||
constructor(x: number, y: number, texts: string[], nbrOfCol?: number) {
|
||||
super(x, y, []);
|
||||
this.texts = texts;
|
||||
this.nbrOfCol = nbrOfCol ?? 4;
|
||||
if (this.nbrOfCol > 4) {
|
||||
this.nbrOfCol = 4;
|
||||
}
|
||||
let currentIndex = 0;
|
||||
if (texts.length >= this.nbrOfCol) {
|
||||
const nbrPerCol = Math.floor(texts.length / this.nbrOfCol);
|
||||
const rest = texts.length - nbrPerCol * this.nbrOfCol;
|
||||
const nbrPerCols = [
|
||||
rest > 0 ? nbrPerCol + 1 : nbrPerCol,
|
||||
rest > 1 ? nbrPerCol + 1 : nbrPerCol,
|
||||
rest > 2 ? nbrPerCol + 1 : nbrPerCol,
|
||||
rest > 3 ? nbrPerCol + 1 : nbrPerCol,
|
||||
];
|
||||
for (let i = 0; i < this.nbrOfCol; i++) {
|
||||
this.elements[i] = new Column(0, 0, []);
|
||||
}
|
||||
for (let i = 0; i < texts.length; i++) {
|
||||
if (i < nbrPerCols[0]) {
|
||||
currentIndex = 0;
|
||||
} else if (i < nbrPerCols[0] + nbrPerCols[1]) {
|
||||
currentIndex = 1;
|
||||
} else if (i < nbrPerCols[0] + nbrPerCols[1] + nbrPerCols[2]) {
|
||||
currentIndex = 2;
|
||||
} else {
|
||||
currentIndex = 3;
|
||||
}
|
||||
(<Column>this.elements[currentIndex]).elements.push(
|
||||
new Row(0, 0, [new Text(0, 0, texts[i])])
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.elements.push(
|
||||
new Column(
|
||||
0,
|
||||
0,
|
||||
texts.map((text) => new Row(0, 0, [new Text(0, 0, text)]))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public render(doc: jsPDF, maxWidth?: number): jsPDF {
|
||||
const pageWidth = doc.internal.pageSize.width;
|
||||
const rowWidth = pageWidth - this.x - MARGINS.right;
|
||||
for (const column of this.elements) {
|
||||
for (const labelledValue of (<Column>column).elements) {
|
||||
labelledValue.maxWidth = rowWidth / this.nbrOfCol;
|
||||
}
|
||||
}
|
||||
return super.render(doc, maxWidth);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user