From c356728d00cbc7dbe39a7491e94f572e8c114c15 Mon Sep 17 00:00:00 2001 From: tahmidrahman-dsi Date: Mon, 4 Nov 2024 15:58:12 +0600 Subject: [PATCH 1/4] feat: handle foreignObject in svg to support html --- packages/client/package.json | 2 + .../src/views/PrintCertificate/PDFUtils.ts | 38 +++++++++++++++- .../usePrintableCertificate.ts | 44 +++++++++---------- 3 files changed, 60 insertions(+), 24 deletions(-) diff --git a/packages/client/package.json b/packages/client/package.json index b23a4f13825..2a7f8da301b 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -40,6 +40,7 @@ "@sentry/tracing": "^7.12.1", "@types/bcryptjs": "^2.4.2", "@types/history": "^4.6.2", + "@types/html-to-pdfmake": "^2.4.4", "@types/nock": "^10.0.3", "@types/pdfmake": "^0.2.0", "@types/react-redux": "^7.1.5", @@ -66,6 +67,7 @@ "graphql-tools": "^4.0.7", "handlebars": "^4.7.6", "history": "^4.7.2", + "html-to-pdfmake": "^2.5.13", "iframe-resizer-react": "^1.1.0", "jsdom-worker": "^0.3.0", "jwt-decode": "^2.2.0", diff --git a/packages/client/src/views/PrintCertificate/PDFUtils.ts b/packages/client/src/views/PrintCertificate/PDFUtils.ts index 51fee86cf6b..b1acdce44ed 100644 --- a/packages/client/src/views/PrintCertificate/PDFUtils.ts +++ b/packages/client/src/views/PrintCertificate/PDFUtils.ts @@ -28,6 +28,8 @@ import isValid from 'date-fns/isValid' import format from 'date-fns/format' import { getHandlebarHelpers } from '@client/forms/handlebarHelpers' import { FontFamilyTypes } from '@client/utils/referenceApi' +import htmlToPdfmake from 'html-to-pdfmake' +import { Content } from 'pdfmake/interfaces' type TemplateDataType = string | MessageDescriptor | Array function isMessageDescriptor( @@ -224,8 +226,19 @@ src: url("${url}") format("truetype"); return serializer.serializeToString(svg) } export function svgToPdfTemplate(svg: string, offlineResource: IOfflineData) { + const initialDefaultFont = offlineResource.templates.fonts + ? Object.keys(offlineResource.templates.fonts)[0] + : null const pdfTemplate: IPDFTemplate = { ...certificateBaseTemplate, + definition: { + ...certificateBaseTemplate.definition, + defaultStyle: { + font: + initialDefaultFont || + certificateBaseTemplate.definition.defaultStyle.font + } + }, fonts: { ...certificateBaseTemplate.fonts, ...offlineResource.templates.fonts @@ -253,9 +266,30 @@ export function svgToPdfTemplate(svg: string, offlineResource: IOfflineData) { } } - pdfTemplate.definition.content = { - svg + const foreignObjects = svgElement.getElementsByTagName('foreignObject') + const absolutelyPositionedHTMLs: Content[] = [] + if (foreignObjects.length) { + for (const foreignObject of foreignObjects) { + const x = Number.parseInt(foreignObject.getAttribute('x')!) + const y = Number.parseInt(foreignObject.getAttribute('y')!) + const htmlContent = foreignObject.innerHTML + const pdfmakeContent = htmlToPdfmake(htmlContent, { + ignoreStyles: ['font-family'] + }) + absolutelyPositionedHTMLs.push({ + stack: pdfmakeContent, + absolutePosition: { x, y } + } as Content) + } } + + pdfTemplate.definition.content = [ + { + svg + }, + ...absolutelyPositionedHTMLs + ] + return pdfTemplate } diff --git a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts index e69966f3389..dbc1ce7d398 100644 --- a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts +++ b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts @@ -146,28 +146,28 @@ export const usePrintableCertificate = (declarationId: string) => { } const draft = cloneDeep(declaration) - draft.submissionStatus = SUBMISSION_STATUS.READY_TO_CERTIFY - draft.action = isPrintInAdvance - ? SubmissionAction.CERTIFY_DECLARATION - : SubmissionAction.CERTIFY_AND_ISSUE_DECLARATION + // draft.submissionStatus = SUBMISSION_STATUS.READY_TO_CERTIFY + // draft.action = isPrintInAdvance + // ? SubmissionAction.CERTIFY_DECLARATION + // : SubmissionAction.CERTIFY_AND_ISSUE_DECLARATION const registeredDate = getRegisteredDate(draft.data) const certificate = draft.data.registration.certificates[0] const eventDate = getEventDate(draft.data, draft.event) - if (!isPrintInAdvance) { - const paymentAmount = calculatePrice( - draft.event, - eventDate, - registeredDate, - offlineData - ) - certificate.payments = { - type: 'MANUAL' as const, - amount: Number(paymentAmount), - outcome: 'COMPLETED' as const, - date: new Date().toISOString() - } - } + // if (!isPrintInAdvance) { + // const paymentAmount = calculatePrice( + // draft.event, + // eventDate, + // registeredDate, + // offlineData + // ) + // certificate.payments = { + // type: 'MANUAL' as const, + // amount: Number(paymentAmount), + // outcome: 'COMPLETED' as const, + // date: new Date().toISOString() + // } + // } const svg = await compileSvg( offlineData.templates.certificates[draft.event].definition, @@ -186,12 +186,12 @@ export const usePrintableCertificate = (declarationId: string) => { } const pdfTemplate = svgToPdfTemplate(svg, offlineData) - + console.log(pdfTemplate) printPDF(pdfTemplate, draft.id) - dispatch(modifyDeclaration(draft)) - dispatch(writeDeclaration(draft)) - dispatch(goToHomeTab(WORKQUEUE_TABS.readyToPrint)) + // dispatch(modifyDeclaration(draft)) + // dispatch(writeDeclaration(draft)) + // dispatch(goToHomeTab(WORKQUEUE_TABS.readyToPrint)) } const handleEdit = () => { From 7adae80d0a5679a97d3d15fd445dcd70ff18978f Mon Sep 17 00:00:00 2001 From: tahmidrahman-dsi Date: Tue, 5 Nov 2024 15:31:18 +0600 Subject: [PATCH 2/4] fix: minor amends and apply width of the foreignObject to the column --- .../src/views/PrintCertificate/PDFUtils.ts | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/client/src/views/PrintCertificate/PDFUtils.ts b/packages/client/src/views/PrintCertificate/PDFUtils.ts index b1acdce44ed..d8d72568334 100644 --- a/packages/client/src/views/PrintCertificate/PDFUtils.ts +++ b/packages/client/src/views/PrintCertificate/PDFUtils.ts @@ -268,19 +268,23 @@ export function svgToPdfTemplate(svg: string, offlineResource: IOfflineData) { const foreignObjects = svgElement.getElementsByTagName('foreignObject') const absolutelyPositionedHTMLs: Content[] = [] - if (foreignObjects.length) { - for (const foreignObject of foreignObjects) { - const x = Number.parseInt(foreignObject.getAttribute('x')!) - const y = Number.parseInt(foreignObject.getAttribute('y')!) - const htmlContent = foreignObject.innerHTML - const pdfmakeContent = htmlToPdfmake(htmlContent, { - ignoreStyles: ['font-family'] - }) - absolutelyPositionedHTMLs.push({ - stack: pdfmakeContent, - absolutePosition: { x, y } - } as Content) - } + for (const foreignObject of foreignObjects) { + const width = Number.parseInt(foreignObject.getAttribute('width')!) + const x = Number.parseInt(foreignObject.getAttribute('x')!) + const y = Number.parseInt(foreignObject.getAttribute('y')!) + const htmlContent = foreignObject.innerHTML + const pdfmakeContent = htmlToPdfmake(htmlContent, { + ignoreStyles: ['font-family'] + }) + absolutelyPositionedHTMLs.push({ + columns: [ + { + width, + stack: pdfmakeContent + } + ], + absolutePosition: { x, y } + } as Content) } pdfTemplate.definition.content = [ From feaccae628e57e1335f7285fa9b95bb2d7f3dd61 Mon Sep 17 00:00:00 2001 From: tahmidrahman-dsi Date: Tue, 5 Nov 2024 17:21:41 +0600 Subject: [PATCH 3/4] chore: remove temporary changes --- .../usePrintableCertificate.ts | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts index dbc1ce7d398..e69966f3389 100644 --- a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts +++ b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts @@ -146,28 +146,28 @@ export const usePrintableCertificate = (declarationId: string) => { } const draft = cloneDeep(declaration) - // draft.submissionStatus = SUBMISSION_STATUS.READY_TO_CERTIFY - // draft.action = isPrintInAdvance - // ? SubmissionAction.CERTIFY_DECLARATION - // : SubmissionAction.CERTIFY_AND_ISSUE_DECLARATION + draft.submissionStatus = SUBMISSION_STATUS.READY_TO_CERTIFY + draft.action = isPrintInAdvance + ? SubmissionAction.CERTIFY_DECLARATION + : SubmissionAction.CERTIFY_AND_ISSUE_DECLARATION const registeredDate = getRegisteredDate(draft.data) const certificate = draft.data.registration.certificates[0] const eventDate = getEventDate(draft.data, draft.event) - // if (!isPrintInAdvance) { - // const paymentAmount = calculatePrice( - // draft.event, - // eventDate, - // registeredDate, - // offlineData - // ) - // certificate.payments = { - // type: 'MANUAL' as const, - // amount: Number(paymentAmount), - // outcome: 'COMPLETED' as const, - // date: new Date().toISOString() - // } - // } + if (!isPrintInAdvance) { + const paymentAmount = calculatePrice( + draft.event, + eventDate, + registeredDate, + offlineData + ) + certificate.payments = { + type: 'MANUAL' as const, + amount: Number(paymentAmount), + outcome: 'COMPLETED' as const, + date: new Date().toISOString() + } + } const svg = await compileSvg( offlineData.templates.certificates[draft.event].definition, @@ -186,12 +186,12 @@ export const usePrintableCertificate = (declarationId: string) => { } const pdfTemplate = svgToPdfTemplate(svg, offlineData) - console.log(pdfTemplate) + printPDF(pdfTemplate, draft.id) - // dispatch(modifyDeclaration(draft)) - // dispatch(writeDeclaration(draft)) - // dispatch(goToHomeTab(WORKQUEUE_TABS.readyToPrint)) + dispatch(modifyDeclaration(draft)) + dispatch(writeDeclaration(draft)) + dispatch(goToHomeTab(WORKQUEUE_TABS.readyToPrint)) } const handleEdit = () => { From 55cc4db38f700dc8e9c007d080709aa0dc9070c8 Mon Sep 17 00:00:00 2001 From: tahmidrahman-dsi Date: Tue, 5 Nov 2024 17:36:37 +0600 Subject: [PATCH 4/4] chore: update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index da853ccc22e..578b6b0f562 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ - Internally we were storing the `family` name field as a required property which was limiting what how you could capture the name of a person in the forms. Now we are storing it as an optional property which would make more flexible. - Remove the leftover features from the application config pages, such as certificates and informant notification. [#7156](https://github.com/opencrvs/opencrvs-core/issues/7156) - **PDF page size** The generated PDF used to be defaulted to A4 size. Now it respects the SVG dimensions if specified +- Support html content wrapped in `foreignObject` used in svg template in certificate PDF output ## Bug fixes