diff --git a/package-lock.json b/package-lock.json
index 1427173..fde5e27 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -519,6 +519,12 @@
"integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==",
"dev": true
},
+ "@types/file-saver": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.5.tgz",
+ "integrity": "sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ==",
+ "dev": true
+ },
"@types/jquery": {
"version": "3.5.11",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.11.tgz",
@@ -1224,6 +1230,11 @@
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz",
"integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA=="
},
+ "file-saver": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
+ "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
+ },
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
diff --git a/package.json b/package.json
index 2e20b57..b2d7fb3 100644
--- a/package.json
+++ b/package.json
@@ -14,7 +14,8 @@
"typescript": "4.5.4",
"ts-loader": "9.2.6",
"webpack": "5.65.0",
- "webpack-cli": "4.9.1"
+ "webpack-cli": "4.9.1",
+ "@types/file-saver": "2.0.5"
},
"husky": {
"hooks": {
@@ -22,6 +23,7 @@
}
},
"dependencies": {
- "jspdf": "2.4.0"
+ "jspdf": "2.4.0",
+ "file-saver": "2.0.5"
}
}
diff --git a/src/abstract-builder.ts b/src/abstract-builder.ts
new file mode 100644
index 0000000..66283e5
--- /dev/null
+++ b/src/abstract-builder.ts
@@ -0,0 +1,9 @@
+import { AbstractElement } from './elements/abstract-element';
+
+export abstract class AbstractBuilder {
+ public abstract build(elements: AbstractElement[]);
+
+ public abstract getLabelledRowHeight(): number;
+
+ public abstract save(name: string);
+}
diff --git a/src/constants.ts b/src/constants.ts
index dd738c1..b93c829 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -6,4 +6,6 @@ export const isGM = () => user()?.isGM ?? false;
export const TEXT_SIZE = 8;
export const LABEL_SIZE = 6;
+export const HTML_TEXT_SIZE = 1;
+export const HTML_LABEL_SIZE = 0.75;
export const MARGINS = { top: 10, left: 10, bottom: 10, right: 10 };
diff --git a/src/elements/abstract-element.ts b/src/elements/abstract-element.ts
index a443d04..aeb80d5 100644
--- a/src/elements/abstract-element.ts
+++ b/src/elements/abstract-element.ts
@@ -28,6 +28,13 @@ export abstract class AbstractElement {
public abstract render(doc: jsPDF, maxWidth?: number): jsPDF;
+ public abstract renderHtml(
+ doc: Document,
+ parent: HTMLElement,
+ cssRules: string[],
+ sheet: HTMLStyleElement
+ ): Document;
+
public abstract getHeight(doc?: jsPDF): number;
public abstract getCheckNewPageHeight(doc?: jsPDF): number;
diff --git a/src/elements/box.ts b/src/elements/box.ts
index e0534f7..cd059f8 100644
--- a/src/elements/box.ts
+++ b/src/elements/box.ts
@@ -20,6 +20,33 @@ export class Box extends AbstractElement {
return doc;
}
+ public renderHtml(
+ doc: Document,
+ parent: HTMLElement,
+ cssRules: string[],
+ sheet: HTMLStyleElement
+ ): Document {
+ const div = doc.createElement('div');
+ const css = `box-${this.w ?? 0}-${this.h ?? 0}`;
+ div.classList.add(`box`);
+ div.classList.add(css);
+ if (!cssRules.includes(css)) {
+ cssRules.push(css);
+ let rule = 'width: 100%;';
+ if (this.w > 0) {
+ rule += `max-width: ${this.w}px;`;
+ rule += `min-width: ${this.w}px;`;
+ }
+ if (this.h > 0) {
+ rule += `max-height: ${this.h}px;`;
+ rule += `min-height: ${this.h}px;`;
+ }
+ sheet.innerHTML += ` .${css} { ${rule} }`;
+ }
+ parent.append(div);
+ return doc;
+ }
+
public getHeight(_doc): number {
return this.h;
}
diff --git a/src/elements/column.ts b/src/elements/column.ts
index 1c32baf..4af3854 100644
--- a/src/elements/column.ts
+++ b/src/elements/column.ts
@@ -29,6 +29,23 @@ export class Column extends AbstractElement {
return doc;
}
+ public renderHtml(
+ doc: Document,
+ parent: HTMLElement,
+ cssRules: string[],
+ sheet: HTMLStyleElement
+ ): Document {
+ const div = doc.createElement('div');
+ div.classList.add(`column`);
+ const elements = this.elements ?? [];
+ for (let i = 0; i < elements.length; i++) {
+ const element = elements[i];
+ element.renderHtml(doc, div, cssRules, sheet);
+ }
+ parent.append(div);
+ return doc;
+ }
+
public getHeight(doc): number {
return this.elements.length > 0
? this.elements
diff --git a/src/elements/image.ts b/src/elements/image.ts
index b7fd76a..86680f6 100644
--- a/src/elements/image.ts
+++ b/src/elements/image.ts
@@ -21,6 +21,34 @@ export class Image extends Box {
return doc;
}
+ public renderHtml(
+ doc: Document,
+ parent: HTMLElement,
+ cssRules: string[],
+ sheet: HTMLStyleElement
+ ): Document {
+ const img = doc.createElement('img');
+ img.src = this.imageData;
+ const css = `img-${this.w ?? 0}-${this.h ?? 0}`;
+ img.classList.add(`img`);
+ img.classList.add(css);
+ if (!cssRules.includes(css)) {
+ cssRules.push(css);
+ let rule = '';
+ if (this.w > 0) {
+ rule += `width: ${this.w}px;`;
+ }
+ if (this.h > 0) {
+ rule += `height: ${this.h}px;`;
+ }
+ if (rule.length > 0) {
+ sheet.innerHTML += ` .${css} { ${rule} }`;
+ }
+ }
+ parent.append(img);
+ return doc;
+ }
+
public getElements(): AbstractElement[] {
return [this];
}
diff --git a/src/elements/labelled-text.ts b/src/elements/labelled-text.ts
index 8b23966..92ceb5b 100644
--- a/src/elements/labelled-text.ts
+++ b/src/elements/labelled-text.ts
@@ -1,6 +1,12 @@
import jsPDF, { TextOptionsLight } from 'jspdf';
import { Text } from './text';
-import { i18nLocalize, LABEL_SIZE, TEXT_SIZE } from '../constants';
+import {
+ HTML_LABEL_SIZE,
+ HTML_TEXT_SIZE,
+ i18nLocalize,
+ LABEL_SIZE,
+ TEXT_SIZE,
+} from '../constants';
import { AbstractElement } from './abstract-element';
export class LabelledText extends Text {
@@ -40,6 +46,36 @@ export class LabelledText extends Text {
return doc;
}
+ public renderHtml(
+ doc: Document,
+ parent: HTMLElement,
+ cssRules: string[],
+ sheet: HTMLStyleElement
+ ): Document {
+ const div = doc.createElement('div');
+ div.classList.add(`column`);
+ const label = doc.createElement('p');
+ const text = doc.createElement('p');
+ const labelCss = `label-${LABEL_SIZE}`;
+ const textCss = `text-${TEXT_SIZE}`;
+ label.classList.add(labelCss);
+ text.classList.add(textCss);
+ if (!cssRules.includes(labelCss)) {
+ cssRules.push(labelCss);
+ sheet.innerHTML += ` .${labelCss} { font-size: ${HTML_LABEL_SIZE}rem }`;
+ }
+ if (!cssRules.includes(textCss)) {
+ cssRules.push(textCss);
+ sheet.innerHTML += ` .${textCss} { font-size: ${HTML_TEXT_SIZE}rem }`;
+ }
+ label.innerHTML = i18nLocalize(this.label);
+ text.innerHTML = i18nLocalize(this.text);
+ div.append(label);
+ div.append(text);
+ parent.append(div);
+ return doc;
+ }
+
protected updateMaxWidth(maxWidth?: number) {
if (maxWidth != null && maxWidth > 0) {
this.maxWidth = maxWidth;
diff --git a/src/elements/multiline-text.ts b/src/elements/multiline-text.ts
index e748de2..e273f35 100644
--- a/src/elements/multiline-text.ts
+++ b/src/elements/multiline-text.ts
@@ -1,6 +1,6 @@
import { AbstractElement } from './abstract-element';
import jsPDF, { TextOptionsLight } from 'jspdf';
-import { i18nLocalize, TEXT_SIZE } from '../constants';
+import { HTML_TEXT_SIZE, i18nLocalize, TEXT_SIZE } from '../constants';
import { Text } from './text';
export class MultilineText extends Text {
@@ -32,6 +32,25 @@ export class MultilineText extends Text {
return doc;
}
+ public renderHtml(
+ doc: Document,
+ parent: HTMLElement,
+ cssRules: string[],
+ sheet: HTMLStyleElement
+ ): Document {
+ const text = doc.createElement('p');
+ const css = `text-${TEXT_SIZE}`;
+ text.classList.add(`multiline-text`);
+ text.classList.add(css);
+ if (!cssRules.includes(css)) {
+ cssRules.push(css);
+ sheet.innerHTML += ` .${css} { font-size: ${HTML_TEXT_SIZE}rem }`;
+ }
+ text.innerHTML = i18nLocalize(this.text);
+ parent.append(text);
+ return doc;
+ }
+
public getHeight(doc): number {
return this.getHeightFromPx(doc, TEXT_SIZE) * this.nbrLine;
}
diff --git a/src/elements/row.ts b/src/elements/row.ts
index 6ca13a3..7c4c836 100644
--- a/src/elements/row.ts
+++ b/src/elements/row.ts
@@ -60,6 +60,23 @@ export class Row extends AbstractElement {
return doc;
}
+ public renderHtml(
+ doc: Document,
+ parent: HTMLElement,
+ cssRules: string[],
+ sheet: HTMLStyleElement
+ ): Document {
+ const div = doc.createElement('div');
+ div.classList.add(`row`);
+ const elements = this.elements ?? [];
+ for (let i = 0; i < elements.length; i++) {
+ const element = elements[i];
+ element.renderHtml(doc, div, cssRules, sheet);
+ }
+ parent.append(div);
+ return doc;
+ }
+
public getHeight(doc?: jsPDF): number {
let maxHeight = 0;
for (const element of this.elements) {
diff --git a/src/elements/separator.ts b/src/elements/separator.ts
index e0b045b..b2f41dd 100644
--- a/src/elements/separator.ts
+++ b/src/elements/separator.ts
@@ -33,6 +33,18 @@ export class Separator extends AbstractElement {
return doc;
}
+ public renderHtml(
+ doc: Document,
+ parent: HTMLElement,
+ _cssRules: string[],
+ _sheet: HTMLStyleElement
+ ): Document {
+ const div = doc.createElement('div');
+ div.classList.add(`separator`);
+ parent.append(div);
+ return doc;
+ }
+
public getElements(): AbstractElement[] {
return [this];
}
diff --git a/src/elements/text.ts b/src/elements/text.ts
index 1267bc7..0e164ec 100644
--- a/src/elements/text.ts
+++ b/src/elements/text.ts
@@ -1,6 +1,6 @@
import { AbstractElement } from './abstract-element';
import jsPDF, { TextOptionsLight } from 'jspdf';
-import { i18nLocalize, TEXT_SIZE } from '../constants';
+import { HTML_TEXT_SIZE, i18nLocalize, TEXT_SIZE } from '../constants';
export class Text extends AbstractElement {
public text: string;
@@ -36,6 +36,25 @@ export class Text extends AbstractElement {
return doc;
}
+ public renderHtml(
+ doc: Document,
+ parent: HTMLElement,
+ cssRules: string[],
+ sheet: HTMLStyleElement
+ ): Document {
+ const text = doc.createElement('p');
+ const css = `text-${TEXT_SIZE}`;
+ text.classList.add(`ellipsis`);
+ text.classList.add(css);
+ text.innerHTML = i18nLocalize(i18nLocalize(this.text));
+ if (!cssRules.includes(css)) {
+ cssRules.push(css);
+ sheet.innerHTML += ` .${css} { font-size: ${HTML_TEXT_SIZE}rem }`;
+ }
+ parent.append(text);
+ return doc;
+ }
+
protected updateMaxWidth(maxWidth?: number) {
if (maxWidth != null && maxWidth > 0) {
this.maxWidth = maxWidth;
diff --git a/src/html-builder.ts b/src/html-builder.ts
new file mode 100644
index 0000000..a4b4c4a
--- /dev/null
+++ b/src/html-builder.ts
@@ -0,0 +1,50 @@
+import { AbstractElement } from './elements/abstract-element';
+import { AbstractBuilder } from './abstract-builder';
+import { saveAs } from 'file-saver';
+import { i18n } from './constants';
+
+export class HtmlBuilder extends AbstractBuilder {
+ public doc: Document;
+ public styleSheet: HTMLStyleElement;
+
+ constructor() {
+ super();
+ this.doc = document.implementation.createHTMLDocument();
+ const style = document.createElement('style');
+ style.innerHTML = '.column { display: flex; flex-direction: column; }';
+ style.innerHTML +=
+ ' .row { display: flex; flex-direction: row; gap: 10px; }';
+ style.innerHTML +=
+ ' .separator { border: 1px solid; width: 100%; height: 0px }';
+ style.appendChild(document.createTextNode(''));
+ this.doc.head.appendChild(style);
+ this.styleSheet = style;
+ const meta = document.createElement('meta');
+ meta.setAttribute('charset', 'UTF-8');
+ this.doc.head.appendChild(meta);
+ }
+
+ public getLabelledRowHeight(): number {
+ return 0;
+ }
+
+ public save(name: string) {
+ this.doc.title = name;
+ const blob = new Blob(
+ [
+ `
+ ${this.doc.documentElement.innerHTML}
+ `,
+ ],
+ { type: 'text/html;charset=utf-8' }
+ );
+ saveAs(blob, `${name}.html`);
+ }
+
+ public build(elements: AbstractElement[]) {
+ const cssList: string[] = [];
+ for (const element of elements) {
+ element.renderHtml(this.doc, this.doc.body, cssList, this.styleSheet);
+ }
+ }
+}
diff --git a/src/main.ts b/src/main.ts
index 14e3489..12fe30c 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -4,13 +4,7 @@ import { Row } from './elements/row';
import { Image } from './elements/image';
import { Box } from './elements/box';
import { Util } from './util';
-import {
- i18nLocalize,
- isGM,
- LABEL_SIZE,
- MARGINS,
- TEXT_SIZE,
-} from './constants';
+import { i18nLocalize, isGM, MARGINS } 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';
@@ -18,30 +12,39 @@ import { Texts } from './elements/texts';
import { Column } from './elements/column';
import { Separator } from './elements/separator';
import { Blank } from './elements/blank';
+import { AbstractBuilder } from './abstract-builder';
+import { HtmlBuilder } from './html-builder';
Hooks.on('getActorDirectoryEntryContext', async (_, options) => {
- options.push({
- name: i18nLocalize('WFRP4SHEETPRINT.export.pdf'),
- condition: isGM(),
- icon: '',
- callback: async (target) => {
- const actor: Actor & any = (game).actors.get(
- target.attr('data-document-id')
- );
- await generatePdf(actor);
+ options.push(
+ {
+ name: i18nLocalize('WFRP4SHEETPRINT.export.pdf'),
+ condition: isGM(),
+ icon: '',
+ callback: async (target) => {
+ const actor: Actor & any = (game).actors.get(
+ target.attr('data-document-id')
+ );
+ const docBuilder = new PdfBuilder({
+ orientation: 'p',
+ unit: 'mm',
+ });
+ await generate(actor, docBuilder);
+ },
},
- },
- {
+ {
name: i18nLocalize('WFRP4SHEETPRINT.export.html'),
condition: isGM(),
icon: '',
callback: async (target) => {
- const actor: Actor & any = (game).actors.get(
- target.attr('data-document-id')
- );
- await generateHtml(actor);
+ const actor: Actor & any = (game).actors.get(
+ target.attr('data-document-id')
+ );
+ const docBuilder = new HtmlBuilder();
+ await generate(actor, docBuilder);
},
- });
+ }
+ );
});
Hooks.on(
@@ -50,7 +53,11 @@ Hooks.on(
const actor: Actor & any = app.actor;
addActorSheetActionButton(html, 'print', async () => {
- await generatePdf(actor);
+ const docBuilder = new PdfBuilder({
+ orientation: 'p',
+ unit: 'mm',
+ });
+ await generate(actor, docBuilder);
});
}
);
@@ -71,11 +78,7 @@ function addActorSheetActionButton(
title.after(button);
}
-async function generateHtml(actor: Actor & any) {
- console.dir(actor);
-}
-
-async function generatePdf(actor: Actor & any) {
+async function generate(actor: Actor & any, docBuilder: AbstractBuilder) {
const actorData = actor.data;
// @ts-ignore
const actorDetails = actorData.data.details;
@@ -91,14 +94,6 @@ async function generatePdf(actor: Actor & any) {
const careerData: ItemData = currentCareer?.data;
const careerDetail: any = careerData?.data;
- const docBuilder = new PdfBuilder({
- orientation: 'p',
- unit: 'mm',
- });
-
- const labelledRowHeight =
- Util.getHeightFromPx(docBuilder.doc, TEXT_SIZE + LABEL_SIZE) + 1;
-
const skills = new LabelledValues(
0,
0,
@@ -379,6 +374,8 @@ async function generatePdf(actor: Actor & any) {
3
);
+ const labelledRowHeight = docBuilder.getLabelledRowHeight();
+
const imageWidth = 25;
const imageY = labelledRowHeight + MARGINS.top + 2;
const actorImageElement =
@@ -624,5 +621,5 @@ async function generatePdf(actor: Actor & any) {
mutationM,
]),
]);
- docBuilder.doc.save(`${actor.name}.pdf`);
+ docBuilder.save(`${actor.name}`);
}
diff --git a/src/pdf-builder.ts b/src/pdf-builder.ts
index 3055b3c..877747f 100644
--- a/src/pdf-builder.ts
+++ b/src/pdf-builder.ts
@@ -1,14 +1,25 @@
import jsPDF, { jsPDFOptions } from 'jspdf';
import { AbstractElement } from './elements/abstract-element';
-import { MARGINS } from './constants';
+import { LABEL_SIZE, MARGINS, TEXT_SIZE } from './constants';
+import { AbstractBuilder } from './abstract-builder';
+import { Util } from './util';
-export class PdfBuilder {
+export class PdfBuilder extends AbstractBuilder {
public doc: jsPDF;
constructor(options: jsPDFOptions) {
+ super();
this.doc = new jsPDF(options);
}
+ public getLabelledRowHeight(): number {
+ return Util.getHeightFromPx(this.doc, TEXT_SIZE + LABEL_SIZE) + 1;
+ }
+
+ public save(name: string) {
+ this.doc.save(`${name}.pdf`);
+ }
+
public build(elements: AbstractElement[]) {
const finalElements: AbstractElement[] = [];
for (const element of elements) {