Skip to content

MacOS preview doesn't show the correct font size #1750

@IonelLupu

Description

@IonelLupu

What were you trying to do?

When previewing the PDF using the MacOS Preview app (select the PDF in Finder and press Space bar), it shows some weird font sizes.

How did you attempt to do it?

Not sure if this is a known issue. I am setting the font size using this piece of code for my text fields:

myTextField.acroField.setDefaultAppearance(`/Helv ${fontSize} Tf 0 g`);
myTextField.updateAppearances(font);

I tried also using myTextField.setFontSize(fontSize) but it doesn't work. That's why I am using the .setDefaultAppearance() method.

Any idea or insights what's wrong here?

What actually happened?

Weird text is being shown:
Image

What did you expect to happen?

Any other app that can open PDFs show the contents correctly:

Image

How can we reproduce the issue?

const { join } = require("path");
const fs = require("fs/promises");
const {
AcroTextFlags,
PDFDocument,
StandardFonts,
PDFTextField,
PDFCheckBox,
} = require("pdf-lib");

/**

  • A helper function that can be used to align fields in a PDF form.
    */
    async function alignFields(basePath, inputPDF, outputPDF) {
    const pdfPath = join(basePath, inputPDF);
    const pdfPathOut = join(basePath, outputPDF);

const pdfBytes = await fs.readFile(pdfPath);
const pdfDoc = await PDFDocument.load(pdfBytes);

const form = pdfDoc.getForm();
const fields = form.getFields();
const page1 = pdfDoc.getPages()[0];
const page2 = pdfDoc.getPages()[1];

const font = await pdfDoc.embedFont(StandardFonts.Helvetica);

const alignmentGroups = {
regular: {},
overtime: {},
other: {},
fringeBenefit: {},
};

for (const field of fields) {
const name = field.getName();
const lowerName = name.toLowerCase();

if (!lowerName.includes("employee1")) continue;

const widgets = field.acroField?.getWidgets() || [];

for (const widget of widgets) {
  const rect = widget.getRectangle();
  if (!rect) continue;

  let group;

  if (lowerName.includes("regular") || lowerName.includes("baserate")) {
    group = "regular";
  } else if (lowerName.includes("overtime") || lowerName.includes("overtimerate")) {
    group = "overtime";
  } else if (lowerName.includes("fringebenefit")) {
    group = "fringeBenefit";
  } else {
    group = "other";
  }

  const groupData = alignmentGroups[group];
  groupData.y ??= rect.y;
  groupData.height ??= rect.height;

  widget.setRectangle({
    x: rect.x,
    y: groupData.y,
    width: rect.width,
    height: groupData.height,
  });
}

}

let rowOffset;
const cloneCount = 8;

for (let i = 1; i < cloneCount; i++) {
for (const field of fields) {
const name = field.getName();
const lowerName = name.toLowerCase();

  if (!lowerName.includes("employee1")) continue;

  rowOffset = lowerName.includes("fringebenefit") ? 11.5 : 28.5;

  const newFieldName = name.replace("employee1", `employee${i + 1}`).replace("Employee1", `Employee${i + 1}`);
  const widgets = field.acroField?.getWidgets() || [];

  for (const widget of widgets) {
    const rect = widget.getRectangle();
    if (!rect) continue;

    const clonedField = form.createTextField(newFieldName);

    const page = newFieldName.includes("fringeBenefit") ? page2 : page1;
    clonedField.setText("");
    clonedField.addToPage(page, {
      x: rect.x,
      y: rect.y - i * rowOffset,
      width: rect.width,
      height: rect.height,
      borderWidth: 0,
      backgroundColor: undefined,
      borderColor: undefined,
    });

    let fontSize = 10;
    if (lowerName.startsWith("employee")) fontSize = 6;
    if (lowerName.includes("regularhours") || lowerName.includes("overtimehours")) fontSize = 4;
    if (lowerName.includes("fringebenefit")) fontSize = 8;

    clonedField.acroField.setDefaultAppearance(`/Helv ${fontSize} Tf 0 g`);
    clonedField.updateAppearances(font);

    if (newFieldName.includes("JobClassification")) {
      const acroField = clonedField.acroField;
      acroField.setFlags(acroField.getFlags() | AcroTextFlags.Multiline);
    }
  }
}

}

const newPdfBytes = await pdfDoc.save();
await fs.writeFile(pdfPathOut, newPdfBytes);

const pdfBuffer = await fs.readFile(pdfPathOut);
const templatePdfDoc = await PDFDocument.load(pdfBuffer);
const templateForm = templatePdfDoc.getForm();
const fieldsToFill = templateForm.getFields();

fieldsToFill.forEach((field) => {
const fieldName = field.getName().toLowerCase();

Object.keys(data).forEach((key) => {
  if (fieldName === key.toLowerCase()) {
    const value = data[key] ?? "";
    try {
      if (field instanceof PDFTextField) {
        field.setText(String(value));
      } else if (field instanceof PDFCheckBox) {
        if (value) field.check();
      }
    } catch (error) {
      console.warn(`Could not fill field ${fieldName}:`, error.message);
    }
  }
});

});

// templateForm.flatten()

const outputPdfDoc = await PDFDocument.create();

const [filledPage1] = await outputPdfDoc.copyPages(templatePdfDoc, [0]);

outputPdfDoc.addPage(filledPage1);

const pdfBytes2 = await outputPdfDoc.save();
await fs.writeFile("output.pdf", pdfBytes2);
}

// Top-level await isn't available in CommonJS without extra config
(async () => {
await alignFields(__dirname, "wh347.base.pdf", "wh347.pdf");
})();

const data = {
"name": "Ionel Inc.",
"companyAddress": "2 Embarcadero Center 8th Floor, San Francisco, CA 94111, US",
"isSubcontractor": "",
"isPrimeContractor": "X",
"ccbRegistrationNumber": "",
"payrollNumber": "22 ",
"payday": "2025-08-13",
"timezone": "America/Los_Angeles",
"isLastCertifiedPayrollReport": false,
"projectLocation": null,
"endWeekDate": "07-27-2025",
"projectNumber": "443",
"primeContractorName": null,
"primeContractorAddress": null,
"payFrequency": "WEEKLY",
"reportDate": "07-30-2025",
"projectName": "Embarcadero",
"signerName": "ionel",
"signerTitle": "payroll admin",
"areFringeBenefitsPaidToPlans": true,
"areFringeBenefitsPaidInCash": false,
"remarks": "test test",
"weekDay1Day": "S",
"weekDay1Date": "7/20",
"weekDay2Day": "M",
"weekDay2Date": "7/21",
"weekDay3Day": "T",
"weekDay3Date": "7/22",
"weekDay4Day": "W",
"weekDay4Date": "7/23",
"weekDay5Day": "T",
"weekDay5Date": "7/24",
"weekDay6Day": "F",
"weekDay6Date": "7/25",
"weekDay7Day": "S",
"weekDay7Date": "7/26",
"shouldListFringes": "X",
"isPayrollAccurateAndCompliant": "X",
"arePayrollRecordsCompleteAndAvailable": "X",
"areWorkerClassificationsAccurate": "X",
"areFullWagesPaidWithNoImproperDeductions": "X",
"projectNamePage2": "Embarcadero",
"projectNumberPage2": "443",
"projectLocationPage2": null,
"endWeekDatePage2": "07-27-2025",
"primeContractorNamePage2": null,
"fringeBenefitTotalEmployee1": "10.73",
"fringeBenefitEmployee1Name": "Roy Anderson",
"signerNameAndTitle": "ionel - payroll admin",
"employee1TotalFringeCredit": "450.85",
"employee1jobClassification": "Classif2",
"employee1classificationId": 2,
"employee1userClassificationId": 5,
"employee1checkRegEarningCodeId": "erc_WAI5j6UyHooC0zltcTUY",
"employee1checkOtEarningCodeId": "erc_5DfzlQKvQOdQ8Wp6rdY6",
"employee1checkDotEarningCodeId": null,
"employee1baseRate": "22.00",
"employee1fringeRate": "0",
"employee1cashFringe": "0",
"employee1regRateOfPay": "22",
"employee1otRateOfPay": "33",
"employee1dotRateOfPay": "44",
"employee1grossEarned": "1034.00",
"employee1netWages": "2681.12",
"employee1paymentMethod": "manual",
"employee1paperCheckNumber": null,
"employee1numberOfExceptions": 0,
"employee1grossEarnedAllProjects": "4434.00",
"employee1fringePay": "0.00",
"employee1cashFringes": "0.00",
"employee1regRate": "22.00",
"employee1overtimeRate": "33.00",
"employee1dotRate": "44.00",
"employee1pwHoursWorked": "32.00",
"employee1pwOvertimeHoursWorked": "10.00",
"employee1pwDotHoursWorked": "0.00",
"employee1netWagesPaid": "2681.12",
"employee1No": 1,
"employee1FirstName": "Roy",
"employee1LastName": "Anderson",
"employee1fringePayTotal": 0,
"employee1cashFringesTotal": 0,
"employee1totalDeductions": "1802.88",
"employee1Fica": "315.07",
"employee1OtherDeductions": "366.80",
"employee1TaxWithHoldings": "1121.01",
"employee1TotalHours": "42.00",
"employee1RegularHours1": "0.00",
"employee1RegularHours2": "0.00",
"employee1RegularHours3": "8.00",
"employee1RegularHours4": "8.00",
"employee1RegularHours5": "8.00",
"employee1RegularHours6": "8.00",
"employee1RegularHours7": "0.00",
"employee1OvertimeHours1": "0.00",
"employee1OvertimeHours2": "0.00",
"employee1OvertimeHours3": "1.00",
"employee1OvertimeHours4": "2.00",
"employee1OvertimeHours5": "3.00",
"employee1OvertimeHours6": "4.00",
"employee1OvertimeHours7": "0.00",
"employee1TotalCashFringes": "0.00",
"employee2TotalFringeCredit": "450.85",
"employee2jobClassification": "Classif2",
"employee2classificationId": 2,
"employee2userClassificationId": 5,
"employee2checkRegEarningCodeId": "erc_WAI5j6UyHooC0zltcTUY",
"employee2checkOtEarningCodeId": "erc_5DfzlQKvQOdQ8Wp6rdY6",
"employee2checkDotEarningCodeId": null,
"employee2baseRate": "22.00",
"employee2fringeRate": "0",
"employee2cashFringe": "0",
"employee2regRateOfPay": "22",
"employee2otRateOfPay": "33",
"employee2dotRateOfPay": "44",
"employee2grossEarned": "1034.00",
"employee2netWages": "2681.12",
"employee2paymentMethod": "manual",
"employee2paperCheckNumber": null,
"employee2numberOfExceptions": 0,
"employee2grossEarnedAllProjects": "4434.00",
"employee2fringePay": "0.00",
"employee2cashFringes": "0.00",
"employee2regRate": "22.00",
"employee2overtimeRate": "33.00",
"employee2dotRate": "44.00",
"employee2pwHoursWorked": "32.00",
"employee2pwOvertimeHoursWorked": "10.00",
"employee2pwDotHoursWorked": "0.00",
"employee2netWagesPaid": "2681.12",
"employee2No": 1,
"employee2FirstName": "Roy",
"employee2LastName": "Anderson",
"employee2fringePayTotal": 0,
"employee2cashFringesTotal": 0,
"employee2totalDeductions": "1802.88",
"employee2Fica": "315.07",
"employee2OtherDeductions": "366.80",
"employee2TaxWithHoldings": "1121.01",
"employee2TotalHours": "42.00",
"employee2RegularHours1": "0.00",
"employee2RegularHours2": "0.00",
"employee2RegularHours3": "8.00",
"employee2RegularHours4": "8.00",
"employee2RegularHours5": "8.00",
"employee2RegularHours6": "8.00",
"employee2RegularHours7": "0.00",
"employee2OvertimeHours1": "0.00",
"employee2OvertimeHours2": "0.00",
"employee2OvertimeHours3": "1.00",
"employee2OvertimeHours4": "2.00",
"employee2OvertimeHours5": "3.00",
"employee2OvertimeHours6": "4.00",
"employee2OvertimeHours7": "0.00",
"employee2TotalCashFringes": "0.00"
}

wh347.base.pdf

Version

1.17.1

What environment are you running pdf-lib in?

Node

Checklist

  • My report includes a Short, Self Contained, Correct (Compilable) Example.
  • I have attached all PDFs, images, and other files needed to run my SSCCE.

Additional Notes

Using templateForm.flatten() will display the correct font sizes in the MacOS preview. But we can't use that because we want the PDF fields to be editable

PS: make sure to run npm i pdf-lib before running the script with node myFile.js

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions