feature: generate pdf from actor
This commit is contained in:
1108
package-lock.json
generated
1108
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@@ -1,18 +1,25 @@
|
|||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc -p tsconfig.json && cp -r ./src ./dist",
|
"old-build": "tsc -p tsconfig.json && cp -r ./src ./dist",
|
||||||
"package": "sh ./package.sh"
|
"package": "sh ./package.sh",
|
||||||
|
"build": "webpack"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@league-of-foundry-developers/foundry-vtt-types": "0.8.8-8",
|
"@league-of-foundry-developers/foundry-vtt-types": "0.8.8-8",
|
||||||
"husky": "4.3.7",
|
"husky": "4.3.7",
|
||||||
"prettier": "2.2.1",
|
"prettier": "2.2.1",
|
||||||
"pretty-quick": "3.1.0",
|
"pretty-quick": "3.1.0",
|
||||||
"typescript": "4.3.5"
|
"typescript": "4.3.5",
|
||||||
|
"ts-loader": "9.2.6",
|
||||||
|
"webpack": "5.58.2",
|
||||||
|
"webpack-cli": "4.9.0"
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"pre-commit": "pretty-quick --staged"
|
"pre-commit": "pretty-quick --staged"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"jspdf": "2.4.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
src/constants.ts
Normal file
7
src/constants.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export const i18n = () => (<any>game).i18n;
|
||||||
|
export const i18nLocalize = (id: string) => i18n().localize(id);
|
||||||
|
export const i18nFormat = (id: string, data?: any) => i18n().format(id, data);
|
||||||
|
|
||||||
|
export const TEXT_SIZE = 10;
|
||||||
|
export const LABEL_SIZE = 8;
|
||||||
|
export const MARGINS = { top: 10, left: 10, bottom: 10, right: 10 };
|
||||||
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
211
src/main.ts
211
src/main.ts
@@ -1,9 +1,208 @@
|
|||||||
|
import { PdfBuilder } from './pdf-builder';
|
||||||
|
import { LabelledText } from './elements/labelled-text';
|
||||||
|
import { Row } from './elements/row';
|
||||||
|
import { Image } from './elements/image';
|
||||||
|
import { Box } from './elements/box';
|
||||||
|
import { Util } from './util';
|
||||||
|
import { LABEL_SIZE, MARGINS, TEXT_SIZE } from './constants';
|
||||||
|
import { ItemData } from '@league-of-foundry-developers/foundry-vtt-types/src/foundry/common/data/data.mjs';
|
||||||
|
import { LabelledValues } from './elements/labelled-values';
|
||||||
|
import { Text } from './elements/text';
|
||||||
|
import { Texts } from './elements/texts';
|
||||||
|
|
||||||
Hooks.on(
|
Hooks.on(
|
||||||
'renderActorSheetWfrp4eCharacter',
|
'renderActorSheetWfrp4eCharacter',
|
||||||
(_app: ActorSheet, html: JQuery) => {
|
async (app: ActorSheet, html: JQuery) => {
|
||||||
console.dir(_app);
|
console.dir(app);
|
||||||
|
const actor: Actor & any = app.actor;
|
||||||
|
const actorData = actor.data;
|
||||||
|
// @ts-ignore
|
||||||
|
const actorDetails = actorData.data.details;
|
||||||
|
const actorStatus = actorData.data.status;
|
||||||
|
const actorCharacs = actor.characteristics;
|
||||||
|
const actorImage = actor.img;
|
||||||
|
let actorImageData: string | null = null;
|
||||||
|
if (actorImage != null) {
|
||||||
|
const texture = await loadTexture(actorImage);
|
||||||
|
actorImageData = ImageHelper.textureToImage(texture);
|
||||||
|
}
|
||||||
|
const currentCareer: Item & any = actor.currentCareer;
|
||||||
|
const careerData: ItemData = currentCareer?.data;
|
||||||
|
const careerDetail: any = careerData?.data;
|
||||||
addActorSheetActionButton(html, 'print', () => {
|
addActorSheetActionButton(html, 'print', () => {
|
||||||
print(html);
|
const docBuilder = new PdfBuilder({
|
||||||
|
orientation: 'p',
|
||||||
|
unit: 'mm',
|
||||||
|
});
|
||||||
|
|
||||||
|
const labelledRowHeight =
|
||||||
|
Util.getHeightFromPx(docBuilder.doc, TEXT_SIZE + LABEL_SIZE) + 1;
|
||||||
|
const textRowHeight = Util.getHeightFromPx(docBuilder.doc, TEXT_SIZE);
|
||||||
|
const row2Y = labelledRowHeight + MARGINS.top + 2;
|
||||||
|
const row3Y = row2Y + labelledRowHeight + 2;
|
||||||
|
const row4Y = row3Y + labelledRowHeight + 2;
|
||||||
|
const row5Y = row4Y + labelledRowHeight + 2;
|
||||||
|
const row6Y = row5Y + labelledRowHeight + 2;
|
||||||
|
const row7Y = row6Y + labelledRowHeight + 2;
|
||||||
|
const row8Y = row7Y + textRowHeight + 2;
|
||||||
|
|
||||||
|
const skills = new LabelledValues(
|
||||||
|
0,
|
||||||
|
row8Y,
|
||||||
|
actor.itemCategories.skill
|
||||||
|
.map((item) => {
|
||||||
|
return {
|
||||||
|
label: item.name,
|
||||||
|
value: item.data.data.total.value,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.sort((a, b) => a.label.localeCompare(b.label))
|
||||||
|
);
|
||||||
|
|
||||||
|
const row9Y = row8Y + skills.getHeight(docBuilder.doc) + 2;
|
||||||
|
const row10Y = row9Y + textRowHeight + 2;
|
||||||
|
|
||||||
|
const talents = new LabelledValues(
|
||||||
|
0,
|
||||||
|
row10Y,
|
||||||
|
actor.itemCategories.talent
|
||||||
|
.map((item) => {
|
||||||
|
return {
|
||||||
|
label:
|
||||||
|
item.data.data.tests.value.length > 0
|
||||||
|
? `${item.name} : ${item.data.data.tests.value}`
|
||||||
|
: item.name,
|
||||||
|
value: item.data.data.advances.value,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.sort((a, b) => a.label.localeCompare(b.label)),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
const row11Y = row10Y + talents.getHeight(docBuilder.doc) + 2;
|
||||||
|
const row12Y = row11Y + textRowHeight + 2;
|
||||||
|
|
||||||
|
const traits = new Texts(
|
||||||
|
0,
|
||||||
|
row12Y,
|
||||||
|
actor.itemCategories.trait
|
||||||
|
.map((item) => {
|
||||||
|
return item.name;
|
||||||
|
})
|
||||||
|
.sort((a, b) => a.localeCompare(b)),
|
||||||
|
4
|
||||||
|
);
|
||||||
|
|
||||||
|
const imageWidth = 25;
|
||||||
|
const actorImageElement =
|
||||||
|
actorImageData != null
|
||||||
|
? new Image(0, row2Y, imageWidth, imageWidth, actorImageData)
|
||||||
|
: new Box(0, row2Y, imageWidth, imageWidth);
|
||||||
|
|
||||||
|
docBuilder.build([
|
||||||
|
new Row(0, 0, [
|
||||||
|
new LabelledText(0, 0, 'Name', `${actor.name}`),
|
||||||
|
new LabelledText(0, 0, 'Species', `${actorDetails?.species?.value}`),
|
||||||
|
new LabelledText(0, 0, 'Gender', `${actorDetails?.gender?.value}`),
|
||||||
|
]),
|
||||||
|
actorImageElement,
|
||||||
|
new Row(imageWidth + MARGINS.left + 1, row2Y, [
|
||||||
|
new LabelledText(0, 0, 'Class', `${careerDetail?.class?.value}`),
|
||||||
|
new LabelledText(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
'Career Group',
|
||||||
|
`${careerDetail?.careergroup?.value}`
|
||||||
|
),
|
||||||
|
new LabelledText(0, 0, 'Career', `${currentCareer?.name}`),
|
||||||
|
]),
|
||||||
|
new Row(imageWidth + MARGINS.left + 1, row3Y, [
|
||||||
|
new LabelledText(0, 0, 'Status', `${actorDetails?.status?.value}`),
|
||||||
|
new LabelledText(0, 0, 'Age', `${actorDetails?.age?.value}`),
|
||||||
|
new LabelledText(0, 0, 'Height', `${actorDetails?.height?.value}`),
|
||||||
|
new LabelledText(0, 0, 'Weight', `${actorDetails?.weight?.value}`),
|
||||||
|
new LabelledText(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
'Hair Colour',
|
||||||
|
`${actorDetails?.haircolour?.value}`
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new Row(imageWidth + MARGINS.left + 1, row4Y, [
|
||||||
|
new LabelledText(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
'Eye Colour',
|
||||||
|
`${actorDetails?.eyecolour?.value}`
|
||||||
|
),
|
||||||
|
new LabelledText(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
'Distinguishing Mark',
|
||||||
|
`${actorDetails?.distinguishingmark?.value}`
|
||||||
|
),
|
||||||
|
new LabelledText(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
'Star Sign',
|
||||||
|
`${actorDetails?.starsign?.value}`
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new Row(0, row5Y, [
|
||||||
|
new LabelledText(0, 0, 'CHARAbbrev.WS', `${actorCharacs?.ws?.value}`),
|
||||||
|
new LabelledText(0, 0, 'CHARAbbrev.BS', `${actorCharacs?.bs?.value}`),
|
||||||
|
new LabelledText(0, 0, 'CHARAbbrev.S', `${actorCharacs?.s?.value}`),
|
||||||
|
new LabelledText(0, 0, 'CHARAbbrev.T', `${actorCharacs?.t?.value}`),
|
||||||
|
new LabelledText(0, 0, 'CHARAbbrev.I', `${actorCharacs?.i?.value}`),
|
||||||
|
new LabelledText(0, 0, 'CHARAbbrev.Ag', `${actorCharacs?.ag?.value}`),
|
||||||
|
new LabelledText(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
'CHARAbbrev.Dex',
|
||||||
|
`${actorCharacs?.dex?.value}`
|
||||||
|
),
|
||||||
|
new LabelledText(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
'CHARAbbrev.Int',
|
||||||
|
`${actorCharacs?.int?.value}`
|
||||||
|
),
|
||||||
|
new LabelledText(0, 0, 'CHARAbbrev.WP', `${actorCharacs?.wp?.value}`),
|
||||||
|
new LabelledText(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
'CHARAbbrev.Fel',
|
||||||
|
`${actorCharacs?.fel?.value}`
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new Row(0, row6Y, [
|
||||||
|
new LabelledText(0, 0, 'Move', `${actorDetails?.move?.value}`),
|
||||||
|
new LabelledText(0, 0, 'Walk', `${actorDetails?.move?.walk}`),
|
||||||
|
new LabelledText(0, 0, 'Run', `${actorDetails?.move?.run}`),
|
||||||
|
new LabelledText(0, 0, 'Fortune', `${actorStatus?.fortune?.value}`),
|
||||||
|
new LabelledText(0, 0, 'Fate', `${actorStatus?.fate?.value}`),
|
||||||
|
new LabelledText(0, 0, 'Resolve', `${actorStatus?.resolve?.value}`),
|
||||||
|
new LabelledText(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
'Resilience',
|
||||||
|
`${actorStatus?.resilience?.value}`
|
||||||
|
),
|
||||||
|
new LabelledText(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
'Wounds',
|
||||||
|
`${actorStatus?.wounds?.value}/${actorStatus?.wounds?.max}`
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new Text(0, row7Y, 'Skills'),
|
||||||
|
skills,
|
||||||
|
new Text(0, row9Y, 'Talents'),
|
||||||
|
talents,
|
||||||
|
new Text(0, row11Y, 'Traits'),
|
||||||
|
traits,
|
||||||
|
]);
|
||||||
|
docBuilder.doc.save(`${app.actor.name}.pdf`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -23,9 +222,3 @@ function addActorSheetActionButton(
|
|||||||
const title = header.find('.window-title');
|
const title = header.find('.window-title');
|
||||||
title.after(button);
|
title.after(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
function print(html: JQuery) {
|
|
||||||
$('.wfrp4e-print').removeClass('wfrp4e-print');
|
|
||||||
html.addClass('wfrp4e-print');
|
|
||||||
window.print();
|
|
||||||
}
|
|
||||||
|
|||||||
18
src/pdf-builder.ts
Normal file
18
src/pdf-builder.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import jsPDF, { jsPDFOptions } from 'jspdf';
|
||||||
|
import { AbstractElement } from './elements/abstract-element';
|
||||||
|
|
||||||
|
export class PdfBuilder {
|
||||||
|
public doc: jsPDF;
|
||||||
|
|
||||||
|
constructor(options: jsPDFOptions) {
|
||||||
|
this.doc = new jsPDF(options);
|
||||||
|
this.doc.advancedAPI();
|
||||||
|
console.dir(this.doc.internal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public build(elements: AbstractElement[]) {
|
||||||
|
for (const element of elements) {
|
||||||
|
element.render(this.doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/util.ts
Normal file
7
src/util.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import jsPDF from 'jspdf';
|
||||||
|
|
||||||
|
export class Util {
|
||||||
|
public static getHeightFromPx(doc: jsPDF, size: number) {
|
||||||
|
return size / doc.internal.scaleFactor;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
background-image: none;
|
background-image: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
/* * {
|
||||||
break-inside: auto !important;
|
break-inside: auto !important;
|
||||||
break-before: auto !important;
|
break-before: auto !important;
|
||||||
break-after: auto !important;
|
break-after: auto !important;
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
-webkit-column-break-inside: auto !important;
|
-webkit-column-break-inside: auto !important;
|
||||||
-webkit-column-break-before: auto !important;
|
-webkit-column-break-before: auto !important;
|
||||||
-webkit-column-break-after: auto !important;
|
-webkit-column-break-after: auto !important;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
.wfrp4e-print .window-header,
|
.wfrp4e-print .window-header,
|
||||||
.wfrp4e-print .window-content form > .header,
|
.wfrp4e-print .window-content form > .header,
|
||||||
|
|||||||
24
webpack.config.js
Normal file
24
webpack.config.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: './src/main.ts',
|
||||||
|
devtool: 'inline-source-map',
|
||||||
|
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.tsx?$/,
|
||||||
|
use: 'ts-loader',
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
publicPath: '',
|
||||||
|
filename: 'main.js',
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user