feature: generate pdf from actor

This commit is contained in:
Matthieu CAILLEAUX
2021-10-18 22:15:23 +02:00
parent 145207a1c5
commit c66feaf642
8 changed files with 206 additions and 31 deletions

View File

@@ -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
View 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];
}
}

View File

@@ -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',
}), }),

View File

@@ -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
) )
) )
) )

View 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];
}
}

View File

@@ -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;
} }

View File

@@ -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),
])
)
) )
); );
} }

View File

@@ -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`);