feature: generate pdf from actor
This commit is contained in:
@@ -16,6 +16,10 @@ export abstract class AbstractElement {
|
||||
return size / doc.internal.scaleFactor;
|
||||
}
|
||||
|
||||
public getPxFromSize(doc: jsPDF, size: number) {
|
||||
return size * doc.internal.scaleFactor;
|
||||
}
|
||||
|
||||
protected updateMaxWidth(maxWidth?: number) {
|
||||
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 { Text } from './text';
|
||||
import { MultilineText } from './multiline-text';
|
||||
|
||||
export class LabelledValue extends Row {
|
||||
public label: string;
|
||||
@@ -9,13 +10,14 @@ export class LabelledValue extends Row {
|
||||
label: string,
|
||||
value: number,
|
||||
widthPercents?: number[],
|
||||
multiline = false,
|
||||
maxWidth?: number
|
||||
) {
|
||||
super(
|
||||
0,
|
||||
0,
|
||||
[
|
||||
new Text(0, 0, label),
|
||||
multiline ? new MultilineText(0, 0, label) : new Text(0, 0, label),
|
||||
new Text(0, 0, value.toString(), {
|
||||
align: 'right',
|
||||
}),
|
||||
|
||||
@@ -12,7 +12,8 @@ export class LabelledValues extends Row {
|
||||
x: number,
|
||||
y: number,
|
||||
labelledValues: { label: string; value: number }[],
|
||||
nbrOfCol?: number
|
||||
nbrOfCol?: number,
|
||||
multiline = false
|
||||
) {
|
||||
super(x, y, []);
|
||||
this.labelledValues = labelledValues;
|
||||
@@ -47,7 +48,8 @@ export class LabelledValues extends Row {
|
||||
new LabelledValue(
|
||||
labelledValues[i].label,
|
||||
labelledValues[i].value,
|
||||
widthPercent
|
||||
widthPercent,
|
||||
multiline
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -61,7 +63,8 @@ export class LabelledValues extends Row {
|
||||
new LabelledValue(
|
||||
libelledValue.label,
|
||||
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 {
|
||||
doc.setFontSize(TEXT_SIZE);
|
||||
let finalText: string[] = [i18nLocalize(this.text)];
|
||||
if (this.maxWidth != null) {
|
||||
finalText = doc.splitTextToSize(finalText[0], this.maxWidth ?? 0);
|
||||
finalText = doc.splitTextToSize(finalText[0], this.maxWidth);
|
||||
}
|
||||
if (finalText.length > 1) {
|
||||
finalText[0] = finalText[0].replace(/(.){3}$/, '...');
|
||||
}
|
||||
const yText = this.y + this.getHeightFromPx(doc, TEXT_SIZE);
|
||||
doc
|
||||
.setFontSize(TEXT_SIZE)
|
||||
.text(finalText[0], this.x, yText, this.textOptions);
|
||||
doc.text(finalText[0], this.x, yText, this.textOptions);
|
||||
return doc;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,19 @@ import { Column } from './column';
|
||||
import jsPDF from 'jspdf';
|
||||
import { MARGINS } from '../constants';
|
||||
import { Text } from './text';
|
||||
import { MultilineText } from './multiline-text';
|
||||
|
||||
export class Texts extends Row {
|
||||
public texts: string[];
|
||||
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, []);
|
||||
this.texts = texts;
|
||||
this.nbrOfCol = nbrOfCol ?? 4;
|
||||
@@ -39,7 +46,11 @@ export class Texts extends Row {
|
||||
currentIndex = 3;
|
||||
}
|
||||
(<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 {
|
||||
@@ -47,7 +58,14 @@ export class Texts extends Row {
|
||||
new Column(
|
||||
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 { Box } from './elements/box';
|
||||
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 { LabelledValues } from './elements/labelled-values';
|
||||
import { Text } from './elements/text';
|
||||
import { Texts } from './elements/texts';
|
||||
import { Column } from './elements/column';
|
||||
import { Separator } from './elements/separator';
|
||||
import { Blank } from './elements/blank';
|
||||
|
||||
Hooks.on(
|
||||
'renderActorSheetWfrp4eCharacter',
|
||||
@@ -46,7 +47,9 @@ Hooks.on(
|
||||
actor.itemCategories.skill
|
||||
.map((item) => {
|
||||
return {
|
||||
label: item.name,
|
||||
label: `${item.name} (${i18nLocalize(
|
||||
item.characteristic.abrev
|
||||
)})`,
|
||||
value: item.data.data.total.value,
|
||||
};
|
||||
})
|
||||
@@ -67,7 +70,8 @@ Hooks.on(
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.label.localeCompare(b.label)),
|
||||
1
|
||||
1,
|
||||
true
|
||||
);
|
||||
|
||||
const traits = new Texts(
|
||||
@@ -87,14 +91,15 @@ Hooks.on(
|
||||
actor.itemCategories.weapon
|
||||
.filter((w) => w.isMelee)
|
||||
.map((item) => {
|
||||
return `${item.name} : ${item.data.data.damage.meleeValue} (${
|
||||
item.mountDamage
|
||||
}), ${item.OriginalQualities.concat(item.OriginalFlaws).join(
|
||||
', '
|
||||
)}`;
|
||||
return `${item.name} : ${item.WeaponGroup}, ${item.Reach}, ${
|
||||
item.data.data.damage.meleeValue
|
||||
} (${item.mountDamage}), ${item.OriginalQualities.concat(
|
||||
item.OriginalFlaws
|
||||
).join(', ')}`;
|
||||
})
|
||||
.sort((a, b) => a.localeCompare(b)),
|
||||
1
|
||||
1,
|
||||
true
|
||||
);
|
||||
|
||||
const weaponsRanged = new Texts(
|
||||
@@ -103,12 +108,17 @@ Hooks.on(
|
||||
actor.itemCategories.weapon
|
||||
.filter((w) => w.isRanged)
|
||||
.map((item) => {
|
||||
return `${item.name} : ${item.data.data.damage.rangedValue}, ${
|
||||
return `${item.name} : ${item.WeaponGroup}, ${
|
||||
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)),
|
||||
1
|
||||
1,
|
||||
true
|
||||
);
|
||||
|
||||
const ammunitions = new Texts(
|
||||
@@ -117,15 +127,51 @@ Hooks.on(
|
||||
actor.itemCategories.ammunition
|
||||
.map((item) => {
|
||||
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
|
||||
: '+0'
|
||||
}, (${item.data.data.range.value}), ${item.OriginalQualities.concat(
|
||||
item.OriginalFlaws
|
||||
).join(', ')}`;
|
||||
}, ${item.OriginalQualities.concat(item.OriginalFlaws).join(', ')}`;
|
||||
})
|
||||
.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;
|
||||
@@ -190,6 +236,7 @@ Hooks.on(
|
||||
`${actorDetails?.starsign?.value}`
|
||||
),
|
||||
]),
|
||||
Blank.heightBlank(2),
|
||||
new Row(0, 0, [
|
||||
new LabelledText(
|
||||
0,
|
||||
@@ -261,20 +308,51 @@ Hooks.on(
|
||||
new Text(0, 0, 'Skills'),
|
||||
skills,
|
||||
new Separator(0, 0),
|
||||
new Text(0, 0, 'Talents'),
|
||||
new Text(
|
||||
0,
|
||||
0,
|
||||
`${i18nLocalize('Talents')} : ${i18nLocalize('Tests')}`
|
||||
),
|
||||
talents,
|
||||
new Separator(0, 0),
|
||||
new Text(0, 0, 'Traits'),
|
||||
traits,
|
||||
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,
|
||||
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,
|
||||
new Separator(0, 0),
|
||||
new Text(0, 0, 'Ammunition'),
|
||||
new Text(
|
||||
0,
|
||||
0,
|
||||
`${i18nLocalize('Ammunition')} : ${i18nLocalize(
|
||||
'Range'
|
||||
)}, ${i18nLocalize('Damage')}, ${i18nLocalize(
|
||||
'Qualities'
|
||||
)}, ${i18nLocalize('Flaws')}`
|
||||
),
|
||||
ammunitions,
|
||||
new Separator(0, 0),
|
||||
new Text(0, 0, 'Armour'),
|
||||
armours,
|
||||
]),
|
||||
]);
|
||||
docBuilder.doc.save(`${app.actor.name}.pdf`);
|
||||
|
||||
Reference in New Issue
Block a user