feature: generate pdf from actor
This commit is contained in:
@@ -16,6 +16,10 @@ export abstract class AbstractElement {
|
|||||||
return size / doc.internal.scaleFactor;
|
return size / doc.internal.scaleFactor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getPxFromSize(doc: jsPDF, size: number) {
|
||||||
|
return size * doc.internal.scaleFactor;
|
||||||
|
}
|
||||||
|
|
||||||
protected updateMaxWidth(maxWidth?: number) {
|
protected updateMaxWidth(maxWidth?: number) {
|
||||||
this.maxWidth = maxWidth;
|
this.maxWidth = maxWidth;
|
||||||
}
|
}
|
||||||
|
|||||||
29
src/elements/blank.ts
Normal file
29
src/elements/blank.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { AbstractElement } from './abstract-element';
|
||||||
|
import jsPDF from 'jspdf';
|
||||||
|
import { Box } from './box';
|
||||||
|
|
||||||
|
export class Blank extends Box {
|
||||||
|
constructor(x: number, y: number, w: number, h: number) {
|
||||||
|
super(x, y, w, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static heightBlank(h: number) {
|
||||||
|
return new Blank(0, 0, 0, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCheckNewPageHeight(doc?: jsPDF): number {
|
||||||
|
return this.getHeight(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public prepareRender(doc: jsPDF, _maxWidth?: number): jsPDF {
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(doc: jsPDF, _maxWidth?: number): jsPDF {
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getElements(): AbstractElement[] {
|
||||||
|
return [this];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Row } from './row';
|
import { Row } from './row';
|
||||||
import { Text } from './text';
|
import { Text } from './text';
|
||||||
|
import { MultilineText } from './multiline-text';
|
||||||
|
|
||||||
export class LabelledValue extends Row {
|
export class LabelledValue extends Row {
|
||||||
public label: string;
|
public label: string;
|
||||||
@@ -9,13 +10,14 @@ export class LabelledValue extends Row {
|
|||||||
label: string,
|
label: string,
|
||||||
value: number,
|
value: number,
|
||||||
widthPercents?: number[],
|
widthPercents?: number[],
|
||||||
|
multiline = false,
|
||||||
maxWidth?: number
|
maxWidth?: number
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
[
|
[
|
||||||
new Text(0, 0, label),
|
multiline ? new MultilineText(0, 0, label) : new Text(0, 0, label),
|
||||||
new Text(0, 0, value.toString(), {
|
new Text(0, 0, value.toString(), {
|
||||||
align: 'right',
|
align: 'right',
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ export class LabelledValues extends Row {
|
|||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
labelledValues: { label: string; value: number }[],
|
labelledValues: { label: string; value: number }[],
|
||||||
nbrOfCol?: number
|
nbrOfCol?: number,
|
||||||
|
multiline = false
|
||||||
) {
|
) {
|
||||||
super(x, y, []);
|
super(x, y, []);
|
||||||
this.labelledValues = labelledValues;
|
this.labelledValues = labelledValues;
|
||||||
@@ -47,7 +48,8 @@ export class LabelledValues extends Row {
|
|||||||
new LabelledValue(
|
new LabelledValue(
|
||||||
labelledValues[i].label,
|
labelledValues[i].label,
|
||||||
labelledValues[i].value,
|
labelledValues[i].value,
|
||||||
widthPercent
|
widthPercent,
|
||||||
|
multiline
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -61,7 +63,8 @@ export class LabelledValues extends Row {
|
|||||||
new LabelledValue(
|
new LabelledValue(
|
||||||
libelledValue.label,
|
libelledValue.label,
|
||||||
libelledValue.value,
|
libelledValue.value,
|
||||||
widthPercent
|
widthPercent,
|
||||||
|
multiline
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
42
src/elements/multiline-text.ts
Normal file
42
src/elements/multiline-text.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { AbstractElement } from './abstract-element';
|
||||||
|
import jsPDF, { TextOptionsLight } from 'jspdf';
|
||||||
|
import { i18nLocalize, TEXT_SIZE } from '../constants';
|
||||||
|
import { Text } from './text';
|
||||||
|
|
||||||
|
export class MultilineText extends Text {
|
||||||
|
private nbrLine = 1;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
text: string,
|
||||||
|
textOptions?: TextOptionsLight
|
||||||
|
) {
|
||||||
|
super(x, y, text, textOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public prepareRender(doc: jsPDF, maxWidth?: number): jsPDF {
|
||||||
|
doc.setFontSize(TEXT_SIZE);
|
||||||
|
this.updateMaxWidth(maxWidth);
|
||||||
|
let finalText: string[] = [i18nLocalize(this.text)];
|
||||||
|
if (this.maxWidth != null) {
|
||||||
|
finalText = doc.splitTextToSize(finalText[0], this.maxWidth);
|
||||||
|
}
|
||||||
|
this.nbrLine = finalText.length;
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(doc: jsPDF, _maxWidth?: number): jsPDF {
|
||||||
|
const yText = this.y + this.getHeightFromPx(doc, TEXT_SIZE);
|
||||||
|
doc.setFontSize(TEXT_SIZE).text(this.text, this.x, yText, this.textOptions);
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getHeight(doc): number {
|
||||||
|
return this.getHeightFromPx(doc, TEXT_SIZE) * this.nbrLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getElements(): AbstractElement[] {
|
||||||
|
return [this];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,17 +23,16 @@ export class Text extends AbstractElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public render(doc: jsPDF, _maxWidth?: number): jsPDF {
|
public render(doc: jsPDF, _maxWidth?: number): jsPDF {
|
||||||
|
doc.setFontSize(TEXT_SIZE);
|
||||||
let finalText: string[] = [i18nLocalize(this.text)];
|
let finalText: string[] = [i18nLocalize(this.text)];
|
||||||
if (this.maxWidth != null) {
|
if (this.maxWidth != null) {
|
||||||
finalText = doc.splitTextToSize(finalText[0], this.maxWidth ?? 0);
|
finalText = doc.splitTextToSize(finalText[0], this.maxWidth);
|
||||||
}
|
}
|
||||||
if (finalText.length > 1) {
|
if (finalText.length > 1) {
|
||||||
finalText[0] = finalText[0].replace(/(.){3}$/, '...');
|
finalText[0] = finalText[0].replace(/(.){3}$/, '...');
|
||||||
}
|
}
|
||||||
const yText = this.y + this.getHeightFromPx(doc, TEXT_SIZE);
|
const yText = this.y + this.getHeightFromPx(doc, TEXT_SIZE);
|
||||||
doc
|
doc.text(finalText[0], this.x, yText, this.textOptions);
|
||||||
.setFontSize(TEXT_SIZE)
|
|
||||||
.text(finalText[0], this.x, yText, this.textOptions);
|
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,19 @@ import { Column } from './column';
|
|||||||
import jsPDF from 'jspdf';
|
import jsPDF from 'jspdf';
|
||||||
import { MARGINS } from '../constants';
|
import { MARGINS } from '../constants';
|
||||||
import { Text } from './text';
|
import { Text } from './text';
|
||||||
|
import { MultilineText } from './multiline-text';
|
||||||
|
|
||||||
export class Texts extends Row {
|
export class Texts extends Row {
|
||||||
public texts: string[];
|
public texts: string[];
|
||||||
public nbrOfCol: number;
|
public nbrOfCol: number;
|
||||||
|
|
||||||
constructor(x: number, y: number, texts: string[], nbrOfCol?: number) {
|
constructor(
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
texts: string[],
|
||||||
|
nbrOfCol?: number,
|
||||||
|
multiline = false
|
||||||
|
) {
|
||||||
super(x, y, []);
|
super(x, y, []);
|
||||||
this.texts = texts;
|
this.texts = texts;
|
||||||
this.nbrOfCol = nbrOfCol ?? 4;
|
this.nbrOfCol = nbrOfCol ?? 4;
|
||||||
@@ -39,7 +46,11 @@ export class Texts extends Row {
|
|||||||
currentIndex = 3;
|
currentIndex = 3;
|
||||||
}
|
}
|
||||||
(<Column>this.elements[currentIndex]).elements.push(
|
(<Column>this.elements[currentIndex]).elements.push(
|
||||||
new Row(0, 0, [new Text(0, 0, texts[i])])
|
new Row(0, 0, [
|
||||||
|
multiline
|
||||||
|
? new MultilineText(0, 0, texts[i])
|
||||||
|
: new Text(0, 0, texts[i]),
|
||||||
|
])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -47,7 +58,14 @@ export class Texts extends Row {
|
|||||||
new Column(
|
new Column(
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
texts.map((text) => new Row(0, 0, [new Text(0, 0, text)]))
|
texts.map(
|
||||||
|
(text) =>
|
||||||
|
new Row(0, 0, [
|
||||||
|
multiline
|
||||||
|
? new MultilineText(0, 0, text)
|
||||||
|
: new Text(0, 0, text),
|
||||||
|
])
|
||||||
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
118
src/main.ts
118
src/main.ts
@@ -4,13 +4,14 @@ import { Row } from './elements/row';
|
|||||||
import { Image } from './elements/image';
|
import { Image } from './elements/image';
|
||||||
import { Box } from './elements/box';
|
import { Box } from './elements/box';
|
||||||
import { Util } from './util';
|
import { Util } from './util';
|
||||||
import { LABEL_SIZE, MARGINS, TEXT_SIZE } from './constants';
|
import { i18nLocalize, LABEL_SIZE, MARGINS, TEXT_SIZE } from './constants';
|
||||||
import { ItemData } from '@league-of-foundry-developers/foundry-vtt-types/src/foundry/common/data/data.mjs';
|
import { ItemData } from '@league-of-foundry-developers/foundry-vtt-types/src/foundry/common/data/data.mjs';
|
||||||
import { LabelledValues } from './elements/labelled-values';
|
import { LabelledValues } from './elements/labelled-values';
|
||||||
import { Text } from './elements/text';
|
import { Text } from './elements/text';
|
||||||
import { Texts } from './elements/texts';
|
import { Texts } from './elements/texts';
|
||||||
import { Column } from './elements/column';
|
import { Column } from './elements/column';
|
||||||
import { Separator } from './elements/separator';
|
import { Separator } from './elements/separator';
|
||||||
|
import { Blank } from './elements/blank';
|
||||||
|
|
||||||
Hooks.on(
|
Hooks.on(
|
||||||
'renderActorSheetWfrp4eCharacter',
|
'renderActorSheetWfrp4eCharacter',
|
||||||
@@ -46,7 +47,9 @@ Hooks.on(
|
|||||||
actor.itemCategories.skill
|
actor.itemCategories.skill
|
||||||
.map((item) => {
|
.map((item) => {
|
||||||
return {
|
return {
|
||||||
label: item.name,
|
label: `${item.name} (${i18nLocalize(
|
||||||
|
item.characteristic.abrev
|
||||||
|
)})`,
|
||||||
value: item.data.data.total.value,
|
value: item.data.data.total.value,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@@ -67,7 +70,8 @@ Hooks.on(
|
|||||||
};
|
};
|
||||||
})
|
})
|
||||||
.sort((a, b) => a.label.localeCompare(b.label)),
|
.sort((a, b) => a.label.localeCompare(b.label)),
|
||||||
1
|
1,
|
||||||
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
const traits = new Texts(
|
const traits = new Texts(
|
||||||
@@ -87,14 +91,15 @@ Hooks.on(
|
|||||||
actor.itemCategories.weapon
|
actor.itemCategories.weapon
|
||||||
.filter((w) => w.isMelee)
|
.filter((w) => w.isMelee)
|
||||||
.map((item) => {
|
.map((item) => {
|
||||||
return `${item.name} : ${item.data.data.damage.meleeValue} (${
|
return `${item.name} : ${item.WeaponGroup}, ${item.Reach}, ${
|
||||||
item.mountDamage
|
item.data.data.damage.meleeValue
|
||||||
}), ${item.OriginalQualities.concat(item.OriginalFlaws).join(
|
} (${item.mountDamage}), ${item.OriginalQualities.concat(
|
||||||
', '
|
item.OriginalFlaws
|
||||||
)}`;
|
).join(', ')}`;
|
||||||
})
|
})
|
||||||
.sort((a, b) => a.localeCompare(b)),
|
.sort((a, b) => a.localeCompare(b)),
|
||||||
1
|
1,
|
||||||
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
const weaponsRanged = new Texts(
|
const weaponsRanged = new Texts(
|
||||||
@@ -103,12 +108,17 @@ Hooks.on(
|
|||||||
actor.itemCategories.weapon
|
actor.itemCategories.weapon
|
||||||
.filter((w) => w.isRanged)
|
.filter((w) => w.isRanged)
|
||||||
.map((item) => {
|
.map((item) => {
|
||||||
return `${item.name} : ${item.data.data.damage.rangedValue}, ${
|
return `${item.name} : ${item.WeaponGroup}, ${
|
||||||
item.data.data.range.value
|
item.data.data.range.value
|
||||||
}, ${item.OriginalQualities.concat(item.OriginalFlaws).join(', ')}`;
|
} (${item.Range}), ${item.data.data.damage.rangedValue} (${
|
||||||
|
item.Damage
|
||||||
|
}), ${item.OriginalQualities.concat(item.OriginalFlaws).join(
|
||||||
|
', '
|
||||||
|
)}`;
|
||||||
})
|
})
|
||||||
.sort((a, b) => a.localeCompare(b)),
|
.sort((a, b) => a.localeCompare(b)),
|
||||||
1
|
1,
|
||||||
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
const ammunitions = new Texts(
|
const ammunitions = new Texts(
|
||||||
@@ -117,15 +127,51 @@ Hooks.on(
|
|||||||
actor.itemCategories.ammunition
|
actor.itemCategories.ammunition
|
||||||
.map((item) => {
|
.map((item) => {
|
||||||
return `${item.data.data.quantity.value} ${item.name} : ${
|
return `${item.data.data.quantity.value} ${item.name} : ${
|
||||||
|
item.data.data.range.value.length > 0
|
||||||
|
? item.data.data.range.value
|
||||||
|
: 'As Weapon'
|
||||||
|
}, ${
|
||||||
item.data.data.damage.value.length > 0
|
item.data.data.damage.value.length > 0
|
||||||
? item.data.data.damage.value
|
? item.data.data.damage.value
|
||||||
: '+0'
|
: '+0'
|
||||||
}, (${item.data.data.range.value}), ${item.OriginalQualities.concat(
|
}, ${item.OriginalQualities.concat(item.OriginalFlaws).join(', ')}`;
|
||||||
item.OriginalFlaws
|
|
||||||
).join(', ')}`;
|
|
||||||
})
|
})
|
||||||
.sort((a, b) => a.localeCompare(b)),
|
.sort((a, b) => a.localeCompare(b)),
|
||||||
2
|
2,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
const armourLocation: string[] = [];
|
||||||
|
const armourLabels: { [key: string]: string[] } = {};
|
||||||
|
for (const armour of actor.itemCategories.armour) {
|
||||||
|
const maxAp = armour.data.data.maxAP;
|
||||||
|
for (const key of Object.keys(maxAp)) {
|
||||||
|
if (maxAp[key] > 0) {
|
||||||
|
if (!armourLocation.includes(key)) {
|
||||||
|
armourLocation.push(key);
|
||||||
|
}
|
||||||
|
if (armourLabels[key] == null) {
|
||||||
|
armourLabels[key] = [];
|
||||||
|
}
|
||||||
|
armourLabels[key].push(
|
||||||
|
`${armour.name} ${maxAp[key]} ${armour.OriginalQualities.concat(
|
||||||
|
armour.OriginalFlaws
|
||||||
|
).join(' ')}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const armours = new Texts(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
armourLocation.map((al) => {
|
||||||
|
return `${actorStatus?.armour[al]?.label} : ${armourLabels[al]?.join(
|
||||||
|
', '
|
||||||
|
)}`;
|
||||||
|
}),
|
||||||
|
1,
|
||||||
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
const imageWidth = 25;
|
const imageWidth = 25;
|
||||||
@@ -190,6 +236,7 @@ Hooks.on(
|
|||||||
`${actorDetails?.starsign?.value}`
|
`${actorDetails?.starsign?.value}`
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
|
Blank.heightBlank(2),
|
||||||
new Row(0, 0, [
|
new Row(0, 0, [
|
||||||
new LabelledText(
|
new LabelledText(
|
||||||
0,
|
0,
|
||||||
@@ -261,20 +308,51 @@ Hooks.on(
|
|||||||
new Text(0, 0, 'Skills'),
|
new Text(0, 0, 'Skills'),
|
||||||
skills,
|
skills,
|
||||||
new Separator(0, 0),
|
new Separator(0, 0),
|
||||||
new Text(0, 0, 'Talents'),
|
new Text(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
`${i18nLocalize('Talents')} : ${i18nLocalize('Tests')}`
|
||||||
|
),
|
||||||
talents,
|
talents,
|
||||||
new Separator(0, 0),
|
new Separator(0, 0),
|
||||||
new Text(0, 0, 'Traits'),
|
new Text(0, 0, 'Traits'),
|
||||||
traits,
|
traits,
|
||||||
new Separator(0, 0),
|
new Separator(0, 0),
|
||||||
new Text(0, 0, 'SHEET.MeleeWeaponHeader'),
|
new Text(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
`${i18nLocalize('SHEET.MeleeWeaponHeader')} : ${i18nLocalize(
|
||||||
|
'Weapon Group'
|
||||||
|
)}, ${i18nLocalize('Reach')}, ${i18nLocalize(
|
||||||
|
'Damage'
|
||||||
|
)}, ${i18nLocalize('Qualities')}, ${i18nLocalize('Flaws')}`
|
||||||
|
),
|
||||||
weaponsMelee,
|
weaponsMelee,
|
||||||
new Separator(0, 0),
|
new Separator(0, 0),
|
||||||
new Text(0, 0, 'SHEET.RangedWeaponHeader'),
|
new Text(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
`${i18nLocalize('SHEET.RangedWeaponHeader')} : ${i18nLocalize(
|
||||||
|
'Weapon Group'
|
||||||
|
)}, ${i18nLocalize('Range')}, ${i18nLocalize(
|
||||||
|
'Damage'
|
||||||
|
)}, ${i18nLocalize('Qualities')}, ${i18nLocalize('Flaws')}`
|
||||||
|
),
|
||||||
weaponsRanged,
|
weaponsRanged,
|
||||||
new Separator(0, 0),
|
new Separator(0, 0),
|
||||||
new Text(0, 0, 'Ammunition'),
|
new Text(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
`${i18nLocalize('Ammunition')} : ${i18nLocalize(
|
||||||
|
'Range'
|
||||||
|
)}, ${i18nLocalize('Damage')}, ${i18nLocalize(
|
||||||
|
'Qualities'
|
||||||
|
)}, ${i18nLocalize('Flaws')}`
|
||||||
|
),
|
||||||
ammunitions,
|
ammunitions,
|
||||||
|
new Separator(0, 0),
|
||||||
|
new Text(0, 0, 'Armour'),
|
||||||
|
armours,
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
docBuilder.doc.save(`${app.actor.name}.pdf`);
|
docBuilder.doc.save(`${app.actor.name}.pdf`);
|
||||||
|
|||||||
Reference in New Issue
Block a user