You export a logo as a transparent PNG, drop it into a "PNG to PDF" converter, and the result has an ugly black box around it — or the transparency silently becomes white and ruins a design meant to overlay something. This is the single most common bug in PNG→PDF conversion, and it comes from one fact people forget:
A PDF page is not transparent. When you print or flatten it, "transparent" has to become some colour.
So the real question isn't "does it keep transparency?" — it's "what should the transparent pixels become, and who decides?" Good converters make that a choice. Let's build one client-side.
The library: pdf-lib
pdf-lib creates and edits PDFs in pure JavaScript — in the browser, no server. It can embed PNGs with their alpha channel:
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf-lib/1.17.1/pdf-lib.min.js"></script>
const doc = await PDFLib.PDFDocument.create();
const png = await doc.embedPng(pngBytes); // alpha preserved
const page = doc.addPage([png.width, png.height]);
page.drawImage(png, { x: 0, y: 0, width: png.width, height: png.height });
const pdfBytes = await doc.save();
That "alpha preserved" is the trap. If you embed a transparent PNG directly, the transparent areas show whatever ends up behind them — which, on a white-printed page, is fine; on a coloured workflow, is not; and in some viewers, renders as black. You want to decide on purpose.
The fix: offer three modes
Give the user (or yourself) three options:
- Flatten onto white — the safe default for anything you'll view or print.
- Flatten onto black — for light artwork meant for dark backgrounds.
- Keep transparency — for PDFs that will be layered over another design.
"Flatten" just means: paint a background colour, draw the PNG on top, re-encode. A <canvas> does this in a few lines:
// Returns PNG bytes, flattened onto `bg` ('white' | 'black' | 'keep')
async function preparePng(file, bg) {
if (bg === "keep") {
return new Uint8Array(await file.arrayBuffer()); // embed alpha as-is
}
const img = await loadImage(file); // see helper below
const canvas = document.createElement("canvas");
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
const ctx = canvas.getContext("2d");
ctx.fillStyle = bg === "black" ? "#000" : "#fff";
ctx.fillRect(0, 0, canvas.width, canvas.height); // background first
ctx.drawImage(img, 0, 0); // PNG on top
const blob = await new Promise((r) => canvas.toBlob(r, "image/png"));
return new Uint8Array(await blob.arrayBuffer());
}
function loadImage(file) {
return new Promise((resolve, reject) => {
const url = URL.createObjectURL(file);
const img = new Image();
img.onload = () => { URL.revokeObjectURL(url); resolve(img); };
img.onerror = () => { URL.revokeObjectURL(url); reject(new Error("bad png")); };
img.src = url;
});
}
Order matters: fill the background, then draw the image. Do it the other way and you paint over your own picture.
Putting it together (multiple PNGs → one PDF)
async function pngsToPdf(files, { bg = "white" } = {}) {
const doc = await PDFLib.PDFDocument.create();
for (const file of files) {
const bytes = await preparePng(file, bg);
const png = await doc.embedPng(bytes);
const page = doc.addPage([png.width, png.height]); // fit page to image
page.drawImage(png, { x: 0, y: 0, width: png.width, height: png.height });
}
return doc.save(); // Uint8Array — download or zip it, still no server
}
Why not just convert PNG→JPG first?
Because PNG is lossless and often holds screenshots, diagrams, and text — exactly the content JPG compression smears. Embed the PNG and you keep crisp edges at full quality. Flattening onto a solid colour doesn't recompress the visible pixels; it only resolves the alpha.
Quick reference
| Mode | Transparent pixels become | Use when |
|---|---|---|
| White | white | normal viewing/printing (default) |
| Black | black | light logos on dark layouts |
| Keep | whatever's behind in the viewer | PDF will overlay another design |
The whole thing runs in the browser, so nothing uploads and there's no backend to pay for.
I shipped this as a free tool — PDFNest's PNG to PDF converter has exactly this white/black/keep transparency control, runs client-side, and adds no watermark. There's a sibling Image to PDF tool for mixed JPG/PNG/WebP. Questions about the pdf-lib/canvas details — drop them below.
Top comments (0)