diff --git a/src/App.scss b/src/App.scss index f21738d..a1c267a 100644 --- a/src/App.scss +++ b/src/App.scss @@ -121,6 +121,16 @@ input { object-fit: contain; /* Ensure the image fits within the container */ } +// Consistent styling for every file mark +// Reverts some of the design defaults for font +.file-mark { + font-family: Arial; + font-size: 16px; + font-weight: normal; + color: black; + letter-spacing: normal; +} + [data-dev='true'] { .image-wrapper { // outline: 1px solid #ccc; /* Optional: for visual debugging */ diff --git a/src/components/DrawPDFFields/style.module.scss b/src/components/DrawPDFFields/style.module.scss index d0085df..b3150b3 100644 --- a/src/components/DrawPDFFields/style.module.scss +++ b/src/components/DrawPDFFields/style.module.scss @@ -23,10 +23,6 @@ justify-content: center; align-items: center; - font-family: Arial; - font-size: 16px; - font-weight: normal; - &.nonEditable { cursor: default; visibility: hidden; diff --git a/src/components/PDFView/PdfMarkItem.tsx b/src/components/PDFView/PdfMarkItem.tsx index 138c5e1..9642499 100644 --- a/src/components/PDFView/PdfMarkItem.tsx +++ b/src/components/PDFView/PdfMarkItem.tsx @@ -1,6 +1,6 @@ import { CurrentUserMark } from '../../types/mark.ts' import styles from '../DrawPDFFields/style.module.scss' -import { FONT_TYPE, inPx } from '../../utils/pdf.ts' +import { FONT_SIZE, FONT_TYPE, inPx } from '../../utils/pdf.ts' interface PdfMarkItemProps { userMark: CurrentUserMark @@ -26,13 +26,14 @@ const PdfMarkItem = ({ return (
{getMarkValue()} diff --git a/src/pages/verify/index.tsx b/src/pages/verify/index.tsx index 4e21a82..f442cd7 100644 --- a/src/pages/verify/index.tsx +++ b/src/pages/verify/index.tsx @@ -29,6 +29,8 @@ import axios from 'axios' import { addMarks, convertToPdfBlob, + FONT_SIZE, + FONT_TYPE, groupMarksByFileNamePage, inPx } from '../../utils/pdf.ts' @@ -105,13 +107,15 @@ const SlimPdfView = ({ {marks.map((m) => { return (
{m.value} @@ -427,7 +431,7 @@ export const VerifyPage = () => { for (const [fileName, file] of Object.entries(files)) { if (file.isPdf) { // Draw marks into PDF file and generate a brand new blob - const pages = await addMarks(file, marksByPage[fileName]) + const pages = await addMarks(file, file.pages!, marksByPage[fileName]) const blob = await convertToPdfBlob(pages) zip.file(`files/${fileName}`, blob) } else { diff --git a/src/pages/verify/style.module.scss b/src/pages/verify/style.module.scss index af93107..b63ba60 100644 --- a/src/pages/verify/style.module.scss +++ b/src/pages/verify/style.module.scss @@ -61,6 +61,6 @@ [data-dev='true'] { .mark { - border: 1px dotted black; + outline: 1px dotted black; } } diff --git a/src/types/drawing.ts b/src/types/drawing.ts index b8abe73..bb7c970 100644 --- a/src/types/drawing.ts +++ b/src/types/drawing.ts @@ -10,6 +10,7 @@ export interface MouseState { export interface PdfPage { image: string + scale: number drawnFields: DrawnField[] } diff --git a/src/utils/file.ts b/src/utils/file.ts index aad31c7..26f03ef 100644 --- a/src/utils/file.ts +++ b/src/utils/file.ts @@ -22,7 +22,11 @@ export const getZipWithFiles = async ( for (const [fileName, file] of Object.entries(files)) { if (file.isPdf) { // Handle PDF Files - const pages = await addMarks(file, marksByFileNamePage[fileName]) + const pages = await addMarks( + file, + file.pages!, + marksByFileNamePage[fileName] + ) const blob = await convertToPdfBlob(pages) zip.file(`files/${fileName}`, blob) } else { diff --git a/src/utils/pdf.ts b/src/utils/pdf.ts index 8abafae..c7eca36 100644 --- a/src/utils/pdf.ts +++ b/src/utils/pdf.ts @@ -8,11 +8,16 @@ PDFJS.GlobalWorkerOptions.workerSrc = new URL( import.meta.url ).toString() +/** + * Default width of the rendered element on the website + * @constant {number} + */ +export const DEFAULT_VIEWPORT_WIDTH = 550 /** * Scale between the PDF page's natural size and rendered size * @constant {number} */ -export const SCALE: number = 3 +export const DEFAULT_SCALE: number = 1 /** * Defined font size used when generating a PDF. Currently it is difficult to fully * correlate font size used at the time of filling in / drawing on the PDF @@ -20,7 +25,7 @@ export const SCALE: number = 3 * This should be fixed going forward. * Switching to PDF-Lib will most likely make this problem redundant. */ -export const FONT_SIZE: number = 40 +export const FONT_SIZE: number = 16 /** * Current font type used when generating a PDF. */ @@ -72,28 +77,30 @@ export const readPdf = (file: File): Promise => { export const pdfToImages = async ( data: string | ArrayBuffer ): Promise => { - const images: string[] = [] + const pages: PdfPage[] = [] const pdf = await PDFJS.getDocument(data).promise const canvas = document.createElement('canvas') for (let i = 0; i < pdf.numPages; i++) { const page = await pdf.getPage(i + 1) - const viewport = page.getViewport({ scale: SCALE }) + + const originalViewport = page.getViewport({ scale: 1 }) + const scale = originalViewport.width / DEFAULT_VIEWPORT_WIDTH + + const viewport = page.getViewport({ scale }) const context = canvas.getContext('2d') canvas.height = viewport.height canvas.width = viewport.width + await page.render({ canvasContext: context!, viewport: viewport }).promise - images.push(canvas.toDataURL()) + pages.push({ + image: canvas.toDataURL(), + scale, + drawnFields: [] + }) } - return Promise.resolve( - images.map((image) => { - return { - image, - drawnFields: [] - } - }) - ) + return pages } /** @@ -103,6 +110,7 @@ export const pdfToImages = async ( */ export const addMarks = async ( file: File, + pages: PdfPage[], marksPerPage: { [key: string]: Mark[] } ) => { const p = await readPdf(file) @@ -113,34 +121,39 @@ export const addMarks = async ( for (let i = 0; i < pdf.numPages; i++) { const page = await pdf.getPage(i + 1) - const viewport = page.getViewport({ scale: SCALE }) + const viewport = page.getViewport({ scale: 1 }) const context = canvas.getContext('2d') canvas.height = viewport.height canvas.width = viewport.width - await page.render({ canvasContext: context!, viewport: viewport }).promise + if (context) { + await page.render({ canvasContext: context, viewport: viewport }).promise - if (marksPerPage && Object.hasOwn(marksPerPage, i)) - marksPerPage[i]?.forEach((mark) => draw(mark, context!)) + if (marksPerPage && Object.hasOwn(marksPerPage, i)) { + marksPerPage[i]?.forEach((mark) => draw(mark, context, pages[i].scale)) + } - images.push(canvas.toDataURL()) + images.push(canvas.toDataURL()) + } } - return Promise.resolve(images) + canvas.remove() + + return images } /** * Utility to scale mark in line with the PDF-to-PNG scale */ -export const scaleMark = (mark: Mark): Mark => { +export const scaleMark = (mark: Mark, scale: number): Mark => { const { location } = mark return { ...mark, location: { ...location, - width: location.width * SCALE, - height: location.height * SCALE, - left: location.left * SCALE, - top: location.top * SCALE + width: location.width * scale, + height: location.height * scale, + left: location.left * scale, + top: location.top * scale } } } @@ -156,15 +169,21 @@ export const hasValue = (mark: Mark): boolean => !!mark.value * @param mark to be drawn * @param ctx a Canvas representation of a specific PDF Page */ -export const draw = (mark: Mark, ctx: CanvasRenderingContext2D) => { - const { location } = mark +export const draw = ( + mark: Mark, + ctx: CanvasRenderingContext2D, + scale: number +) => { + const { location } = scaleMark(mark, scale) - ctx!.font = FONT_SIZE + 'px ' + FONT_TYPE - ctx!.fillStyle = 'black' - const textMetrics = ctx!.measureText(mark.value!) + ctx.font = scale * FONT_SIZE + 'px ' + FONT_TYPE + ctx.fillStyle = 'black' + const textMetrics = ctx.measureText(mark.value!) + const textHeight = + textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent const textX = location.left + (location.width - textMetrics.width) / 2 - const textY = location.top + (location.height + parseInt(ctx!.font)) / 2 - ctx!.fillText(mark.value!, textX, textY) + const textY = location.top + (location.height + textHeight) / 2 + ctx.fillText(mark.value!, textX, textY) } /** @@ -194,13 +213,11 @@ export const convertToPdfBlob = async ( /** * @param marks - an array of Marks * @function hasValue removes any Mark without a property - * @function scaleMark scales remaining marks in line with SCALE * @function byPage groups remaining Marks by their page marks.location.page */ export const groupMarksByFileNamePage = (marks: Mark[]) => { return marks .filter(hasValue) - .map(scaleMark) .reduce<{ [fileName: string]: { [page: number]: Mark[] } }>(byPage, {}) }