Hopding / pdf-lib

Create and modify PDF documents in any JavaScript environment
https://pdf-lib.js.org
MIT License
6.73k stars 643 forks source link

How to add image to signature field? #418

Closed ZChenzy closed 3 years ago

ZChenzy commented 4 years ago

Is there anyways to set an image into a field with the Field Type /Sig?

I have a jpg/png image of a clients signature and I would like to embed it into the signature field. Is there a way of doing that? How do you guys do it without drawing it at a X/Y coordinate?

Much Thanks.

arturikoX commented 4 years ago

I'm using https://github.com/szimek/signature_pad and

        // Draw the PNG image in the signature field
        const pngImage = await pdfDoc.embedPng(signaturePad.toDataURL())
        const pngDims = pngImage.scale(0.22)
        firstPage.drawImage(pngImage, {
            x: 390, // firstPage.getWidth() / 2 - 150,
            y: firstPage.getHeight() - 580, //  - pngDims.height,
            width: pngDims.width,
            height: pngDims.height,
        })
ZChenzy commented 4 years ago

Hi, thanks for replying. But I would like to insert an image into the signature field instead of drawing it at an X/Y coordinate. Thanks.

msvargas commented 4 years ago

I have the same question, but i dont know it its posible, at moment i write custom function to pass field name and from this get x,y position then draw signature image.

Please @Hopding do you know?

ghost commented 4 years ago

This is open for quite some time but might be usefull for googlers. I managed to add an image and text to a signature field, the function I'll put here is pretty basic, I use a more complex version but you can change it to fit you needs.

For a complete signature (image + text):

function signatureAppearanceStream(image,text,rotation,width,height,font,size) {
    const dict = image.doc.context.obj({
        Type: 'XObject',
        Subtype: 'Form',
        FormType: 1,
        BBox: [0, 0, width, height],
        Resources: { XObject: { Image: image.ref }, Font: { F0: font.ref }},
    });
    var operators = [
        rotateDegrees(rotation),
        translate(0, rotation % 90 === 0 ? -width : 0),
        ...drawImage('Image', {
            x: 0,
            y: width, //y = 0 is width for me
            width: width,
            height: height,
            rotate: degrees(0),
            xSkew: degrees(0),
            ySkew: degrees(0),
        }),
        PDFOperator.of(PDFOperatorNames.BeginMarkedContent, [asPDFName("Tx")]),
        pushGraphicsState(),
        ...drawText(font.encodeText(text), {
            color: rgb(0, 0, 0),
            font: 'F0',
            size: size,
            rotate: degrees(0),
            xSkew: degrees(0),
            ySkew: degrees(0),
            x: 0,
            y: width, //y = 0 is width for me
        }),
        popGraphicsState(),
        PDFOperator.of(PDFOperatorNames.EndMarkedContent)
    ];
    const stream = PDFContentStream.of(dict, operators, false);
    return image.doc.context.register(stream);
};

For only images:

function imageAppearanceStream(image,rotation,width,height,font,size) {
    const dict = image.doc.context.obj({
        Type: 'XObject',
        Subtype: 'Form',
        FormType: 1,
        BBox: [0, 0, width, height],
        Resources: { XObject: { Image: image.ref }},
    });
    var operators = [
        rotateDegrees(rotation),
        translate(0, rotation % 90 === 0 ? -width : 0),
        ...drawImage('Image', {
            x: 0,
            y: width, //y = 0 is width for me
            width: width,
            height: height,
            rotate: degrees(0),
            xSkew: degrees(0),
            ySkew: degrees(0),
        })
    ];
    const stream = PDFContentStream.of(dict, operators, false);
    return image.doc.context.register(stream);
};

Then add it to your widgetDict like this:

const widgetDict = pdfDoc.context.obj({
    Type: 'Annot',
    Subtype: 'Widget',
    FT: 'Sig',
    Rect: [image.width,image.height, 0, 0],
    V: signatureDictRef,
    T: PDFString.of('Signature1'),
    F: 4,
    P: pages[pages.length - 1].ref, //lastPage
    AP: pdfDoc.context.obj({N: signatureAppearanceStreamRef})
});

Your y value might vary and you might have to fiddle around with some stuff.

Hope it helps.

Note: the functions are not in the conventional way you see around here (using this in react-native) but you should be able to figure them out. Comment not spellchecked.

Hopding commented 3 years ago

Hello @ZChenzy @arturikoX @punisher97 @Alph4Four! Here's an example showing how to do this using the new forms API:

import { PDFDocument, drawRectangle, rgb, degrees, drawImage } from 'pdf-lib';

const PDF_LIB_SIGNATURE =
  'iVBORw0KGgoAAAANSUhEUgAAApgAAAF5CAYAAAA7wVpzAAAgAElEQVR4nO3dT48jxf3H8Y5+sA' +
  'ssux4EaCGzy0y0URbESh4gCUi7kp1DtFH+yJZAWjYXG3KYlXKwb8MlsgkSl0h4LkRCHOwDEnvz' +
  'SBvOPeIJtPIImmfQD+H7O5Ay5bZnxn++/aeq3y+ppUCScY+nu+vTVd+qCgQAAABQFBR9AgAAAP' +
  'ALARMAAACqCJgAAABQRcAEAACAKgImAAAAVBEwAQAAoIqACQAAAFUETAAAAKgiYAIAAEAVARMA' +
  'AACqCJgAAABQRcAEAACAKgImAAAAVBEwAQAAoIqACQAAAFUETAAAAKgiYAIAAEAVARMAAACqCJ' +
  'gAAABQRcAEAACAKgImAAAAVBEwAQAAoIqACQAAAFUETAAAAKgiYAIAAEAVARMAAACqCJgAAABQ' +
  'RcAEAACAKgImAAAAVBEwAQAAoIqACQAAAFUETAAAAKgiYAIAAEAVARMAAACqCJgAAABQRcAEAA' +
  'CAKgImAAAAVBEwAQAAoIqACQAAAFUETAAAAKgiYAIAAEAVARMAAACqCJgAAABQRcAEAACAKgIm' +
  'AAAAVBEwAQAAoIqACQAAAFUETAAAAKgiYAIAAEAVARMAAACqCJgAAABQRcAEAACAKgImAAAAVB' +
  'EwAQAAoIqACQAAAFUETAAAAKgiYAIAAEAVARMAAACqCJgAAABQRcAEAACAKgImAAAAVBEwAQAA' +
  'oIqACQAAAFUETAAAAKgiYAIAAEAVARMAAACqCJgAAABQRcAEAACAKgImAAAAVBEwAQAAoIqACQ' +
  'AAAFUETAAAAKgiYAIAAEAVARMAAACqCJgAAABQRcAEAACAKgImAAAAVBEwAQAAoIqACQAAAFUE' +
  'TAAAAKgiYAIAAEAVARMAAACqCJgAAABQRcAEAACAKgImAAAAVBEwkYnBYCBBEMhgMCj6VAAAQM' +
  '4ImCUWhqFMJhMZDofS7Xal2WxKs9mU4XAow+FQTk9PJYqiok9zqSAIZgcAAKgWWv+ChWEow+FQ' +
  '2u22NJtN2d/fnwtnqx6XLl2Sfr8vcRwX/SuJiMje3l6lAyY9uACAKqtm61+gJElkMplIu93eKE' +
  'iucrTbbZlMJpIkSWG/Z6PRmJ1PWXtZs0QPLgCgymj9cjIej+W1115bKyjevHlTWq2WDAYDGY1G' +
  'Mp1OZTwey2AwkMFgIK1WSxqNhty+fVsuXbq08P/f2dmR6XRayO9rB8wwDAs5hyLVajUCJgCgsm' +
  'j9MhTHsXS7XdnZ2TkzRNbrden1ejIejyUMQ4miaKOexziOpdfrzQ1Nm6Pb7Wbw252v6gHT/v3L' +
  'UrYAAEBeCJgZiOP43CHw3//+9zIejzMLHtPpVOr1+txn7u7uyvHxcW7D5umg+9prr8krr7wizz' +
  '33nHzwwQeFDt/noeoBGwBQbQRMRUmSyHA4PLOncjQa5dablSSJdDqdpcPm4/E488+/aPi/2Wx6' +
  'HTIJmACAKiNgKgnDcOkM8E6nU+gklzt37iwNeFkPm9++fXulyUi+hkw73E+n09ms8n6/X/SpAQ' +
  'CQOQLmlpIkkWazuTRYliU8xXEsg8FgbuJJEATywQcfZPaZdg/eF198IU8//fTSkHlwcFCa70mT' +
  'CZRBEMz95yr03gIAQMDcwmAwWJjAs7e3V9oh0TiOF2ojd3d3M/ms9BDxDz/8ID//+c+Xhsxer5' +
  'fJORQpHTB7vd5CqUK325Xj4+NKLuMEAPAbAXMDZw2Hf/TRR6XvmYqiSK5evTp33lksZbSsBjGK' +
  'oqWz3H2sU0wHTBGR0Wh0ZrnAU089JW+//fZshyYAAFxGwFxTuifKTOBxKSAlSSLPPvvs7Pz39/' +
  'fVP8Mejre/mziOF2a4m5n1PrEDZqfTmf37MAzPDNnLygf6/b6cnJyU/sUFAAAbAXNFSZIsXXrI' +
  '1eHd8Xg893tozyw/r3fyrBnuPg0V2wGz0WjM/XdJkiytib3o2N/fn+1FP5lM5PT0lDU2AQClRM' +
  'BcQZIkcnBwMNfY12o1p3otl7F70jR7MeM4Xmn4+9atWwt1ib6EzPMCphFFkbzwwgtrhcxNj4OD' +
  'A2k2m2ceBwcH8stf/lKazab0+30ZDoezw4TZ9EG4LQ573QMoOwLmBZaFy3q97kXjmu7F1KrFDM' +
  'NwpYD53//+d2F2uS8hc5WAKfLj9fW73/1OXn311VyCZlbHzs6OHBwczHpZzzra7ba02+2Ffz8a' +
  'jfL743jA/u4BoIx4Op3jrHDpUz2c3YupNdy/asAU+bEXLz1UvLOz43yATy9NtIo4jmdrZtqTpL' +
  'I+arWa3Lp1S3Z3d6Ver689dK91YHXsdQ9kL4qipaM3PnSC5IGn0xmSJJGXX37Z63ApMj9p6bye' +
  'tnWkA+ZF9Z3LQqbr62NuEjCXieNYwjCU8Xgsg8FABoOBTKdTCcNQwjBc6ztKkmTt4G4+f9lhwr' +
  'A5/vKXv8ibb74pjUZj7cPVWuai2C8gNHbA+ZIkmYVDU/qzbCSl2WzK66+/vtD2X3Q888wzs/Ki' +
  '4+NjVgL5HwLmElEUyY0bN7wPlyI/7luu3Yv0j3/8Y+5nrhIeloXMrHcbylI6YLreI4tyYStS4E' +
  'dxHMvp6alMJhMZDofS7/c3Doqah1nruMr3JwEzJYqihcXTX3rpJS/Dpchib6OG9Axxe5me80RR' +
  'tHCTZrFGZx7SAbPKDxno49pCVUVRJMfHx9Jutxfa6m2OWq0m9Xpdbt68Kbu7u9JoNGajM+PxWM' +
  'IwnP1zr9ebjb7cvHlTLl26dO7PPjg4qOR9SsC0JEmysID67u6ut+HSsH9fjd81vVboOksgpSce' +
  'uTrpJx0wXQ3KKJ/0i1gVGy5URxzHMplMpNvtrh0oa7WaNBoNabVas3C4SXnRKpIkmYXQVqu1tJ' +
  'a93W5XajSLgGlJ97zVajUnw826tBur9ASVdX9mOqC6WI/54MGDud/hwYMHRZ8SPPH555/PXVtH' +
  'R0dFnxKgxg6Uzz333EpBsl6vS6fTmQXIsrTb4/F46cYaH3/8sXNt2iYImP+TXrtx3Z43l9mBUG' +
  'NdvfROPZu8saXD/m9/+9utzytP9+/fnzv/VqtV9CnBE2+88cbctfXGG28UfUrAVqbTqXS73aVb' +
  'MJ8VKHu9njO996PRaOlqKb6PbBEw/yc9pFmv14s+pdzYYW7VesnzpB8GGufl2jBz+npiQWxouX' +
  '79+kIZD8rBTDhJHycnJ3J8fDy3gcFwOJRPPvlEHj16tPDv+/3+mbOcfVk7No5j6ff7Kw17X79+' +
  'XTqdjkynU2d7/pIkWbr8XLvddvZ3uggB83/S3dhV6b0UWX1R8FXZ3+Pe3t5WP+vOnTtzb3yu3I' +
  'gETGQl3UDdvXu36FNy1rJAuCwMmiBoZiibY9Uet6wOF8VxLN1ud6Xfr9FoONNLuaooihaCpotl' +
  'YKtw8wpVll6qJwh0Jru4wp5JvrOzo/azNAJrkiRzQwvtdnurn5cXAiaysGylBa6tRSY4npycLK' +
  'x7mN48I6ujXq8vXfO11WpJq9WSXq8nR0dH8uDBg7n1ZM87er2etFotaTQape/BXLadaXpUqkrB' +
  'Mm00Gi30ZPqm0gHT3ADpmsH79+8XfWq50lyqKP2zNIbc0y8ALgyVEzCRhfT9FQRB6YPGNqIomg' +
  'uJ3W73zMWx02sXawbER48ezQVBs2xNVjOSfWB/l1EUySuvvHLhd97pdCo1yzqr7ZrLotIB86yL' +
  'vGqzMpMkUQuY6RtGK1i1Wq25XtayP4QImMhCutcjCPxZpigMQzk+PpZut5tZL+Pe3t4sNC4Li2' +
  'V/rrjE/t7Pq7Os1WoyGAwqG9LttsKlMrBVVDZg2rPGb968ufC2VTVaATMdrLR6V9JD5c1mU+Xn' +
  'ZoWAiSykr6sgcLec57PPPpOPP/5Yms2mSnh88cUXpdFozJarGY1G9DAW6KK/V9WDpc2eA3JwcF' +
  'D06aipbMC0h5pu3bqlFrBcpfX7p9ew1OxdSQ+Vl3lokICJLKSvq20n0eUtSRKZTCbyi1/8Yqte' +
  'SLNw9nQ6za1DYFlNIRbFcXzuS8Ply5fl8PCQYGlJl7740pNezTQlizOnzX+u6nqFWj0i6dlx2g' +
  '9/O8CWeaicgIkspO8vjRrnPERRtPFOLCZMFt0TWfVOiFUsW4YnCAK5c+eOjEaj0j6vy+Cdd95x' +
  '7r6+SGXvFHtij/ZC4y6ylwPaptcx/YDRliTJ3HBCWYfKCZjIQvr+KvNyakmSyHA4XGspn0ajIb' +
  '1eT8bjcanCiN3DpLGUm6/O+rv6UiecJR97MSsZMNO79hAw55eP2OY7sL/XrBarTy/VUsah8vRk' +
  'jKpeV9CV3g2kjMOMq65z+PLLL0u9Xpcvv/yy9HXv9v3c6/WKPp3SMm3pq6++Ove3/vzzz4s+NS' +
  'f4lkUqGTD/9re/zV38v/rVryr/8LB73Lbpnre/1yzLDco+8y79NurDwwLFK/vw+LJJSGUb6t6E' +
  '/QJexhfasmE7082k16R27T5Jq2TAvHv37tzFf/v27coPf2gMAaV7FrMOVXaZQ9leDAiY0Ja+v7' +
  '799tuiT2lBOli2Wi0v1vaznzUM914sPXH21q1bRZ+SM+wSMNdfZioZMNMLq6ffuqtII2CmQ1XW' +
  'D+I4jueGDMs0zEbAhLZ02UUZmWepbwtml70soWzSnThsZ7o6ey1p15csKudTKmPpt+zpdFr5JS' +
  'g0AmZ6kfU8HsR2o1umCT8u1InCLfYwbVVHWoqQXh4NF0tPRuN6XV164xOXX2gqd7cs28uXIY/5' +
  'cLjpw8DuCc5qgs8yZRy+Sj8kynJecFeZS0J8Zi+NVqvVij4dJxAwt2N/fy63HZULmMv28sV8ON' +
  'y0F9fuYcmzAbRfGvb393P73IsQMKEpPeqCfKSXtMPFCJjbsdtjl0e/KpeuXN8JIyv2Xt+brq1n' +
  'P1TybgDtXobPPvss188+i32dlak+FO7xcY08Fyxb0g4XI2Buxy7LcLlsr/IBkwv/Rxpd8vaEm7' +
  'zrRpIkkStXrkgQBHLt2rVSNMD0kkOL/dximDY/Dx8+nLuPb9++XfQpOYGAuR17VM7l765yLZ89' +
  'jEst00/sB8Km4bCI+kub3ctThtl39tAasA17hKGq29kWIT0buqhnm2sImNvz4aWmci1f+sJ3ub' +
  '5B0+XLl7cKQ3a4K7JLv0y1KyZgsgYctmWvjefykJlr7O89CAI5Ojoq+pScQMDc3rPPPitBEMjl' +
  'y5eLPpWNVT5gMvliccbzJuyakaK/UzNUX/ROCLu7uxIEgezu7hZ2DnAfKxIUJz0hlMlVqyFgbs' +
  '8etXC1hr/yARPz4fCPf/zjRj/j8PBQgiCQ559/Xvns1mdP+Hn99dcLOw9zrfFwxTbSE3yQj2Ur' +
  'jrja0OeNgLk9e43nTSfeFq1yTyv7wmcG+Y/sQLbpsLKpVSpDjVJ6rdOiGgUCJjTYDQ3XUn7SG0' +
  'cQ7ldHwNye/YLj6lyRyt0x9sSLTqdT9OmUgl1ntOnsa/Mziq57NN54443Cb04zxMHDFduwJyZS' +
  'f5mf9IojZXh5dgUBU4fr31/lAqa9lE5ZwlCR7N6+TXt07bXiyjKEZPc+FFWLaYIBDRO2Yb8Ul+' +
  'X+qoL0iiN0SKyO704HAdMxFMvPs9/SN+3pM2GuTOvzJUky9zJRRA2L+W53dnZy/2z4w1zDZbq/' +
  'qiDdC0fv8erssiu+u80RMB1DPc08e3h8094R8zAp201Q9NqBdngHNmHXYdELlC9WHNlcuryAgL' +
  'k+H+79yrV89Ab8xB5G3mbCkxnCK9tDxJ4dHwT57y5EwMS2fJhJ6iq7NKGI54fLCJjbs9tnV7+/' +
  'SrV8dq1g2XrbimD3Xm7TeJmfUcY14oocJidgYlt2LRsBJ192QGLFkfWkAybzHdZnlxm42nteqZ' +
  'bP7nKuesC0HwDbPDzt77SMDaDdQOc9zEDAxLbefPNNCYJAXnzxxaJPpXLsgMT2nOtJryHqakAq' +
  'kt0BVMa2dRWVavnsi77KD4z0OpHb3PxmCK+sb/j2MHnek20ImNiW60NkLmOId3MEzO1o7K5XBu' +
  '6e+QZ8qGnYVpIksr+/r/Y9mB7CMgd2+0bddJ3PTRAwsQ27kS5j+YnvCEibI2Bux669dnWCj0jF' +
  'Aqbd4Fc1YNozqzXKBMo6wcdmzwbNs6EmYGIbdiOT54sRfsQEn+340ANXFHuCmcvhvFJ/ebvBr2' +
  'LR8dHR0dwseo2Hpgs3gcZan9t+LrAu8zJY1vITn9k9cFevXi36dJxEwNyMPRnZ9Xu/Un95u/eu' +
  'zIEoC1nsz132CT6GfZ553rAETGzDFPmXufzEV0wI3Z5ZwYMlAddjzx53dQ9yo1Itnz1UWqWAmS' +
  'SJ7OzszH73f/3rXyo/t+wTfGxFvE2bgOnC94NysXsxqjjaUjT75ZCAuZlbt25JEARy69atok/F' +
  'KRqbn5RFZQNmmXvcNCVJIgcHB5m8EZk3LRd6WO7evZv73940UuxFjnXZExKr9DJcFtTrb293d1' +
  'eCIJDd3d2iT8UZ9qonPrQblQqY9qLbVaE9qcdmArsLD+AiyiNMI0UPCNZlD5Mhf3ZnBD3ImzHf' +
  'Ic+/1WltflIWlXp6+VI4uyq7kdrb21PvuXOph6WICV4ETGzKzCLl2ilGVcupNBEw12OvGuFLRq' +
  'lkwKzCBW8PsdVqNfVaDrtGzIVyA3voIa91xQiY2IS9yLILowM+0p4QWUUEzNVFUTQ3T8KXl5rK' +
  'BEx7FrXvF7wdLrO6WE1gc+VNq4hZoQRMbMK+Vn1paFzDEjvbI2CuJst5EkWrzN1jP7R97hVIh8' +
  'us6jhMeHJllwG7VyivLSMJmNgEy1sVyx6d4W+wOQLmasxueL5M7LFV5u6pQsC0L9Ssi4TNw8Ol' +
  'Avi8Gw3XQjjKgYa5WHZb4VuDnyeu44t9+umnc6VsLpSbraOSAdO3Yac4jue62Gu1WuZbIpoZ+S' +
  '7VJxUVMH0a8kD2fH8RLjsWWddBwDxfFEVy7do1r2t9KxMwfV1XbjwezxUH1+v1zC9Uu57VJXbA' +
  'zONN0QRMggJW5fOLsCsImDoImGdL111qbX5SNm4lhC3YdU1xHBd9OltLkkTa7fZcaOr1erkEJx' +
  'PWXXtw3Lx5M9fGm4CJdVF/WTx7xQnu3c0RMM9ml7P5XEJVmaeYTw/uMAzl8uXLc2tm5dnbYdbX' +
  'dO3hay+2nkftKAET6zLXqAu7Y/mKXXx0EDCXs0dTs1ifukzcT1sr8iVg2oun59lraTMPDteG8O' +
  'xrII+6SAIm1mVqm12aPOebIjZl8BEBc1F6vUsf6y5tbqetNbi+ZEwURXM1G1evXpXHjx8Xci5m' +
  'IpFr7DfHPK6Do6MjCYJAjo6OMv8suM+ubfa94SkzO2C69hJdJgTMeUmSyP7+fi6rvJRFZQLm4e' +
  'GhBEEg77zzTtGnsjZ7CykzfFZUt/qTJ08kCAK5e/duIZ+/jbyL9801d3h4mPlnwX3mBciVzQt8' +
  'RcDUQcD8SXpSj891l7bKBMw///nPztU2xXEszWZzbvmhot96TK/cgwcPCj2PTeQdME1DxTAbVm' +
  'EK/6vS+JSVbxNCi0LA/FE6XFbp+6hMwDQXuwv1cEmSyHA4nOu1bDQapXjYmUbQxTf7ogKmi98V' +
  '8re3t1eZobMy86Vev2gEzMVwWa/XvZ7Uk1aZO8g8vMvemzQej+fqNGq1WqnO2dUJPiIETJSXvZ' +
  'VplRqgMiJg6qh6wIyiaK4tr1q4FKlQwCxzTY3psbQvRjOcX4ZeS5uZ5eoiexJFHg89M+O/jNcc' +
  'ysXUNt+5c6foU6k81yeElkWVA2Z6tnin06lcuBSpYMAs0+zMMAwXFks3N2QZQ4npZXF5f948A6' +
  'bLvb3I10cffeRcjbivXJ4QWiZVDZhfffXVXHte5ZrqSgRMe2i0aEmSyPHx8UJvpbkQyxxGzPfo' +
  '8gPDfNfPPPNM5p9FwMSq6vU610pJPHjwgLCvoIoB0y6vcKEkL2vFJ64cFB0w4ziWyWQyu+HsY2' +
  '9vT0ajkRPd52YZFRcmSp3F/u6zRsDEKqi/LBcTEgiY26lSwEySRLrd7uw+vnr1qnz99ddFn1bh' +
  'KhEwzTqSeQ7tRlEkx8fHc8sM2aGy1+uVarh+Faam0NW3sjiOCxki31Qcx3J6ejo7hsOhDIdD+f' +
  'DDD+UPf/iDNJvN2WHX+5x37OzsyP7+vhwcHEiz2ZTXX39dbty4MfezDg4OZH9//9yfubOzM/f/' +
  '6ff7it9ctZi9r6vQELuAGkwd5vnne1BfNlPctbY9K5UImHk9MKbTqfT7/aXD37VaTd5//32n32' +
  'pc75Gze7LzqIsxw57pYZMgCGR/f392rBoOy35gM+bFzeWRAZ8QMHW0Wi3vaxDTk3mqOFP8PJVo' +
  'FcwDXPNCD8NQTk5OZDgcLu2ltHsqXQ1kaS7PIBeZ3xEpj8bcfF9lP2q1mjQaja0OV3u1y4D6y3' +
  'IhYOow36OvL07j8XhhpjjmuZsWVjSdTmdrYLZarbkhR3NMJpPZ8KN9fPjhh/LrX/96Ngy4rGcy' +
  'fZjGtmzLC23L1Im5vI2d3ZOYx2LWdpgtOkSaINjr9WQwGEgYhpUfxkmXIJyensp//vMfefz48d' +
  'LnhDk07227bAPlQMDU4XPANJ1W5uAFeznvn2p59PwMBgOZTqded42b4WWX62nsh0IevUXrBIco' +
  'iiSKIgnDcOkxHo9lMBjIYDCQBw8eyP3792f/PBqNFv73Pl+Lm4jjWP7617/KvXv35uqlNjl2dn' +
  'bUwrmZOEeYKQ8Cpg4fA2YURXPPj1qtJtPptOjTKi3vA+ZoNJL/+7//O7Ox2Nvbmxvqa7Vas4b7' +
  '8PBQDg8PZ/9sGu8q9vz48LCwZ/HnFTBd7vF1WZIkcnJyIv1+f+tAuezQug/M1qsu31e+IWDq8K' +
  'HNsI1Go4V6S99GKrV5HzBFyr2LjytMwbbLb2t2wMyjh49GKj9hGMrx8bF0u91MAmV65EKrYTHl' +
  'O1V8aS0rlinS4UvAjKJoYZ6F679TXgiYWImZiODyG5sdMLPmw6L0LtCobzWjGL1eT46OjuTrr7' +
  '9eKDmIokjiOFYNgmbr0lqtpvYzsb0sJoVWkQ8BM/18YQmi9XgfMCmi1+FDQ2g/KLJGwMzHKr2N' +
  'pvzl9u3bUq/X5ejoSKbTaeEvnGZVA3rKysWM1rgcjMrA5YAZhqG88MILc88RF3+Ponmfuorexc' +
  'cHPoQle7eUPH4P853RC5Itu16u1+vJeDwuPDiuygSZPFY0wOrMSAeBYjsuBswkSaTf78+9pHY6' +
  'HadH7orkfeoyDT2TLTZnelpcelCk5b3Iuvk8l78zZMtcjzRe5ULA1HF0dCRBEMjR0VHRp7KSMA' +
  'znliJ844035PHjx0WfltO8D5gsA7I9M9PV5Qk+ea+B6UMoR3bM9pB5bl+L1RAwdRweHkoQBHJ4' +
  'eFj0qZwrvY94EATS6/VY6k2B9wGTJSe2Zx64Lve0vPXWW7OHRx5F2ua6Y/gTy5iJJL1er+hTQQ' +
  'oBU4cLL9nppYf29vacKbFxQWUCJg/yzfkwwcds2/j000/n8nnmuuNhhWXM8kRcH+VDwNRR5oA5' +
  'nU4XduYbDAb0WiqrTMAs40XuAjML3/UeYHtf8DyYRooAgTRzT7n+0uYrAqYOU55Wpu8xDMOFNS' +
  '0bjQZLD2XE+4BpHhbsFboZM1nF9R5gs45nXo26ue54I0aa6dlhhYFyos3QUaaAOZ1OF4Ilw+HZ' +
  'q0zA5ELajC+1hKYHM6/VBMx1B6SZa8P1e8pX5u/j8qTGMig6YMZxLJPJRK5cubIQLLn38uF9C0' +
  'jA3I6ZQe7695fnGpjm81gaC2n2eqz0bpcTbYaOIpZqi6JI+v3+0u1iCZb58z5g8jDfji89cUUE' +
  'TNfrVqGPZdPKj4CpI6+AmSSJDIfDhUk75nj33Xfliy++yPQcsJz7yeEC7OKznVqt5nxPnNnzOc' +
  '9t+QgRWMbs3kN9X3lRwqAjj4A5GAzmlhkydfadTocXhBLwPnkxVLk5M5zn+l7J9i4+eQzXmEBb' +
  'huJ2lIupBXZ5TVnf8RKgI6uAGUXR0h7LRqNB3WzJeB0wTUNPT9Jmnjx54tRWX2exd/HJ4wHky/' +
  'cGXd9//70EQSA3b94s+lRwDrMIPgFzO9oBczweL0zYCYJAfvGLX8gnn3wik8lETk9P5fT0lBe4' +
  'kvA6YJoL3PUeuKJ88803EgSB8/UrpsHIq67KBMyvv/4688+COx49esTzyAGsnaxDO2Ca0oV1jo' +
  'ODA2m323J8fMxalwXwOmDSk7QdE8xcvzHtB1Meb7bs4oNlzO49rt9Pvjs6OqLdUJBFD+bLL7+8' +
  'dsi0j52dHQJnjrwOmCYgffrpp0WfipN8mUFuB8w8EDCRZsp12L2n/A4PDyUIAjk8PCz6VJyWVQ' +
  '3mYDCYvaxpHH/6059kMpkwrJ4B99PDOWjoN2cm+PhQv3r79u1cA6ZZOxQw2L3HHQyR68hjFnkU' +
  'RfLdd9/JaDSSwWAwOxqNxkZD6vv7+9Lv9+Xk5CSzc64Sr1tB04NJwFxfEYvkZuXSpUsSBIE8++' +
  'yzuXyeLz2/0GO2KmWWa/n5sntZ0crShsRxLGEYymAwkF6vNwufly9fvnA4vdvtkh+24HUraBp6' +
  'ai3WZ3pcXG8Qp9Pp7IGRV+9Ro9GQnZ2dXD4L5RfHMcPjDvFl97KilSVgnidJEplOp9LpdGZLiJ' +
  '3VszmZTNiwZU2VCJhYn1kLzvW6FHsGeV49Er6UFkCHeVlj9rgb2MlHhwsBMy2KIhkMBrMRh2W9' +
  'msPhkKC5Iq/Tl7lIsL5areZFj4v9oMjroUDAhM28rDHk6gYCpg4XA6YtjmMZjUZLw+ZTTz0l/X' +
  '6f0dELeJ2+GJbajBnSc73HxUxUCoJA6vV6rp/JZA6IzF+D9Hq4gYCpw/WAaQvDcFY6cdbEINdH' +
  '+7LgfcCkJ2l94/HYiweD+T2CIJBer5fLZ/r0UMX2zDXo+stalRAwdfj4LIzjWB4+fHjmepzNZt' +
  'P5eQuaCJhY4Mvse3uZirxueh8fqtgc+1q7xzw36HHeju/PQtOruWxy0P7+vvPtpwZvA6a5uAmY' +
  '6/OhdtUM8+ddJmEmdPBwgT08zlC9ZFUAAA9dSURBVPCZO0xgwHZ8D5i28Xi8tFaz2WxWuk7T27' +
  'vIXNx5DY36wjSKedUsZuXWrVu5D4+LsLg/fmKGx12/l6omz00ZfFalgGmEYbh0gfdut1vJHnFv' +
  '76IqXtwazLqRrgdz++bOs/fIFIJX+a0VP2J43E0ETB1VboOXBU2zxFGVeHsXmaHKKl7c2zD1ly' +
  '4XKpsHWxAE8u677+b62ay9ChGGx11GwNRR5YBpTKfThX3Tn3/+eafb13V4exeZoUp6D9ZjbgaX' +
  'G0V7cfW8e2Lr9TpLY4HF1R1GwNRBwPzJeDxeCJrtdtv7YXNv7yJq4dZnJsbs7e0VfSobS5JEdn' +
  'Z2cp89bjCxDCI/TZRjcXX3EDB1EDAXHR0dyZUrV+aGzX3OKN7eRQTM9ZleF5cXCTe/QxFB2QyL' +
  'EjCrzV7BwPceCh8RMHWYgMko4rwkSWb12ebw9UXU27vI/AEJmKsz35nL9SFPP/10YcPjvLFDhO' +
  'Fx1xEwdZjnocvtSZbszpCdnR0vJ4Z6exexWO76XO91sXfuCYJAvvvuu1w/38zAJ2BWm6m1omF1' +
  'k2nwsR0C5sXskHlwcOBs23sW7wMmVvP9999LEARy586dok9lY3YR9fXr13P/fMoyYO6ja9euFX' +
  '0q2BBrl+owAZPn4fnsBdrv379f9Omo8jaBETDX8+jRI6eH9ew3waJqWswamDxQq+v999+nDtdh' +
  'pn6Wv9/2CJirsZfV8+378jaBETDXY74vFy/uKIrmbtCiZsFzzVWbPbnHx3qqKmCLYT0EzNWZF9' +
  'Mg+HF7SV942xqyn+zq7EWhXasBSZJEnn/++VK8AbIGZrWZ9VcJJ+4iYOohYK7Ofjn16QXV2wTm' +
  '+nqOeTKTU1yrO4qiaPYiUdTMcZuL3yH0mGvR1yVHqoCAqYeAuZ6bN28W3kmizeuAyUNiNabnxa' +
  'X9x6MomltQvehwZ4bpXa1hxXbMCgb0YLvNhCKX1wIuCwLm6uxRxCAI5MmTJ0WfkgoCJmaz2FxZ' +
  'TiIMw4Vw+dJLLxU6vP/kyRMJgkCOjo4KOwcUx6whSzBxm3lRYKmx7Zlnoi9hKUvpiT7ffvtt0a' +
  'ekgoBZca7VX5qZ2vbRaDQKP/fPP/9cgiCQTz/9tNDzQP7s+ilXXtKwnLmPeVHcHj2Yq0uvglJ0' +
  'e6bF64DJcOXFzBt72WsH4ziW3d3dhXBZlh4jliiqLrP+KcPj7js8PJQgCOTw8LDoU3Geqe33Zc' +
  'JKlkyZmm/PEa8Dpks1hUUxQ3tlHhJaNiRetr8vO0dVl5ncU5aXHWzOvCyU+XnoCtMrR8C8mL3Y' +
  'uk8jr14HTB4S57OHx8v6EEhv/xgEgVy5cqV0PYWsWlBNDx8+9G7mZ5WZUETbsT0T1nG+dP3laD' +
  'Qq+pTUePnXNzN6eUiczzxMy9olbw8blKneMs1cbz69eeJiSZLIK6+8MnvpgfuY5KOHgLkaM/pl' +
  'jjiOiz4lNV7+9U3RPQ+J85V15muSJPLee++VekjcZt5Ay3p+yIY94Yy1L/1g7mXaju0RMC+Wnt' +
  'xTtrZ4W17+9c1DwqeuZm32zNcyNY5nTeYp0zmmmQcp11t12PcPPdf+IGDqIWCeL4oiuXbt2tzk' +
  'nrKNzm3Ly7++eUiwZMjZ/v73v5duSYRlk3muXr1a2vpQwwzlU4NXHXbd1HvvvVf06UAJAVOP6e' +
  'HHci+++OJcW+dj++HlX5/1ty52/fp1CYJArl+/XvSpiMjiUEEQBPLCCy+UJvyex9TQlD0IQ49Z' +
  'RNocN27ckH6/zzXgOAKmHgLm2dLzC3wtr/Lyr88OAuczk1LKMKz7ww8/yL1795aub+lCuBQR2d' +
  'vb40FaMemAaR8HBwcyHA5lMpnIycmJnJycSLfblX6/L5PJRE5PT525tquGgKnHvHhjXnpllFu3' +
  'bhV9Spnx8q9PD+b57MkJRTZ0cRzLyy+/vNBAFx1611XmmfjIRpIk8vzzz58ZMlc5dnZ2pNlsSr' +
  '/fl+PjYzk9PS3616o8linS02g0ZGdnp+jTKBW7c8fXuksbAbNikiSZ1TkWOWMtiqKFessrV644' +
  'VzdrJnsw0aN6kiSR6XQqnU5n1outcRwcHEi32yV0FoCF1vU0Gg3WBrbEcbzQ5vleUkPArBi7e7' +
  '6oMDcejxdutGvXrsn3339fyPlsw1xrBEyMx+OFNe20jhs3bkiz2ZRutzsbfmeoXR8BU0+j0Sj9' +
  'FsR5SZJEDg4O5u7pMq+MosXrgOlab1geTANYxJBukiTSbrcXGs96ve5sQ8nCzEhLkkTCMJTBYD' +
  'A7Op2OdDodabVamYRQO3yenJx43zOSFQKmnkajIfv7+0WfRuGWhUtfJ/WkeR0wq/CGsA577b68' +
  'h8e//vpreeaZZxYaxjLuzLMO1sDEpuI4ljAMZTQazUJoo9GY9fyYPc63rfNst9syHA7l9PTUq1' +
  '1CskDA1GOu5SpbFi59W0z9PATMCrEn9+RZPrBsCSJfbjSzGxLlGMiSCaOmR9Q03tvUeZoJRgy5' +
  '/4SAqafqATOO44Vw2Wq1ij6tXBEwK8Luvcyr8DoMQ2k2m0sbOF/+NvV6vRLF2ii3MAznAmir1Z' +
  'Lbt2/L1atXNwqgL730kjSbzZWOfr9f9K+vhoCpp8oBc9mmIa6P1m2CgFkRDx8+nF3oWQ7nJkki' +
  '//znP5cOh5vj6Ogos8/Pm/mdgLKya0LNMLzG8Lt9+NKDT8DUs7e3V8mAaa4h30brNuFly0jAXP' +
  'Tcc89JEATys5/9LJO3qOl0Kt1ud+GtzT5qtZo3DZHIT2uaMVMSrjJD73bv5+3bt2fhwCw1c164' +
  '9ClEEDD11Go1r66Ni0RRtDAkXqvVKj3Z2OuAWeU/rM1emmiT3sPRaCTD4XDuuHfvnrz22mvy0k' +
  'svrdTD4ePwgPleq/QQBXxGwNRTpWfjaDRa6FxptVretXnrImBWgD0ZYN1awfSeqeseL774ord/' +
  'hziOpdFoePv7AVXjQ8CMokhOT09lMpnIcDgsbNSoCpNali29t7e359VI3Ta8Dpj8ked7LzeZ3L' +
  'Nprdarr77qVa0lAP9Np1NpNBqlnLSXDo7D4VDa7fZsstVZz+KiSnh8rz2cTqdy5cqVueFwl19M' +
  'skDA9JwdEDdZ3NUsw7PKUavVpNPp8L0DwBqSJJHT01M5PT2Vf//739Lv96Xb7Uqz2ZT9/f2NXv' +
  'JNDW0RcxGSJHG+J3iZJEnk+Ph44W/S6XRYY3YJAqbn7AL9TR40SZLMLQBtH3fu3JFWqyXT6bSU' +
  'b/wAUBQ7NNq9jiY4NpvNcydFpg8z8arVas0mZI3H49kkrTAMS1PzZ5bF8yVgJkkiw+Fw4e/17r' +
  'vvyuPHj4s+vdLyMmCaYWEC5o83unkQAUAV2OHOHCcnJwuTFTc9PvnkE3n06NHCMPWmvY1mxnWj' +
  '0ZBHjx7J4eHhLDyWJTSuw6yw4XrAPCtYMlK3GgImAKDU4jiWk5MTabfb8vbbb88t9J5eGqYshx' +
  '0a7V7H0Wg063H0dVjVjCK6GjDPC5a+/s2y4GXANDMBCZgAUH5mAovdy9hut0sXHuv1ujQaDXn4' +
  '8KE8ePBABoNBKYeoi/bkyRMJAvc21YjjWPr9PsFSCQETAJC7ZTueFH2YOsdOpzNX40i4WI8JmF' +
  '999VXRp7ISs1FI+nogWG6HgAkAyF0Zhqun0ykBMgPT6VSCoNxrUU+n06W9lbVaTXq9HteEAq8D' +
  'JjObAaCczHPa3pbSDn/Ljrfeekt+85vfyNHR0dy/t+safa9vdEEZA2YYhnJ8fHzmmqH1el3G4z' +
  'FlDoq8Dpg8YAAAyFfRAdOsItBut+XevXtnLgfVarVkNBqRFTLidcBkiBwAgHyZgJlnGxyGofT7' +
  '/TN7KE2ZhJmYhewRMAEAgBqzVGCWPZhRFF045P3RRx/JN998Qw9lQbwOmNRgAgCQryw6eaIoks' +
  'lkIu12e+mQ997ennQ6HZlOp9RRloTXAZMeTAAA8rVtG2zC5HA4PHNLzVqtJq1WS8bjMT2UJeVl' +
  'wByNRgRMAAAKsGrANJNxPvzwQ7l37965W22aQDkajRiddAQBEwAAqDEBczwez/17s+XneZNx0h' +
  'NyptMpPZSO8jJgFjGDDQCAqouiSDqdjgRBII1GQ7rd7pnD3PZknC+//JI22zMETAAAsJHRaHRu' +
  'gEwfds8kk3H85mXAjONYBoMBFy8AABmq1WoLIfLSpUuz3kkTJqmbrB4vAyYAAMheGIZz+7qLiL' +
  'RaLQmCQAaDQaHnhmIRMAEAgJrpdCp7e3ul2osc+SNgAgAAQBUBEwAAAKoImAAAAFBFwAQAAIAq' +
  'AiYAAABUETABAACgioAJAAAAVQRMAAAAqCJgAgAAQBUBEwAAAKoImAAAAFBFwAQAAIAqAiYAAA' +
  'BUETABAACgioAJAAAAVQRMAAAAqCJgAgAAQBUBEwAAAKoImAAAAFBFwAQAAIAqAiYAAABUETAB' +
  'AACgioAJAAAAVQRMAAAAqCJgAgAAQBUBEwAAAKoImAAAAFBFwAQAAIAqAiYAAABUETABAACgio' +
  'AJAAAAVQRMAAAAqCJgAgAAQBUBEwAAAKoImAAAAFBFwAQAAIAqAiYAAABUETABAACgioAJAAAA' +
  'VQRMAAAAqCJgAgAAQBUBEwAAAKoImAAAAFBFwAQAAIAqAiYAAABUETABAACgioAJAAAAVQRMAA' +
  'AAqCJgAgAAQBUBEwAAAKoImAAAAFBFwAQAAIAqAiYAAABUETABAACgioAJAAAAVQRMAAAAqCJg' +
  'AgAAQBUBEwAAAKoImAAAAFBFwAQAAIAqAiYAAABUETABAACgioAJAAAAVQRMAAAAqCJgAgAAQB' +
  'UBEwAAAKoImAAAAFBFwAQAAIAqAiYAAABUETABAACgioAJAAAAVQRMAAAAqCJgAgAAQBUBEwAA' +
  'AKoImAAAAFBFwAQAAIAqAiYAAABUETABAACgioAJAAAAVQRMAAAAqCJgAgAAQBUBEwAAAKoImA' +
  'AAAFBFwAQAAIAqAiYAAABUETABAACgioAJAAAAVQRMAAAAqCJgAgAAQBUBEwAAAKoImAAAAFBF' +
  'wAQAAIAqAiYAAABUETABAACgioAJAAAAVQRMAAAAqCJgAgAAQBUBEwAAAKoImAAAAFBFwAQAAI' +
  'AqAiYAAABUETABAACgioAJAAAAVQRMAAAAqCJgAgAAQBUBEwAAAKoImAAAAFBFwAQAAIAqAiYA' +
  'AABUETABAACgioAJAAAAVQRMAAAAqCJgAgAAQBUBEwAAAKoImAAAAFBFwAQAAIAqAiYAAABUET' +
  'ABAACgioAJAAAAVQRMAAAAqPp/bVlFhjBDLggAAAAASUVORK5CYII=';

(async () => {
  const signaturePdf = await fetch(
    'https://github.com/Hopding/pdf-lib/files/5264763/with_signature.pdf',
  ).then((res) => res.arrayBuffer());

  const pdfDoc = await PDFDocument.load(signaturePdf);

  const pdfLibSigImg = await pdfDoc.embedPng(PDF_LIB_SIGNATURE);
  const pdfLibSigImgName = 'PDF_LIB_SIG_IMG';

  const form = pdfDoc.getForm();

  const sig = form.getSignature('Signature1');

  sig.acroField.getWidgets().forEach((widget) => {
    const { context } = widget.dict;
    const { width, height } = widget.getRectangle();

    const appearance = [
      ...drawRectangle({
        x: 0,
        y: 0,
        width,
        height,
        borderWidth: 1,
        color: rgb(1, 0, 0),
        borderColor: rgb(1, 0.5, 0.75),
        rotate: degrees(0),
        xSkew: degrees(0),
        ySkew: degrees(0),
      }),

      ...drawImage(pdfLibSigImgName, {
        x: 5,
        y: 5,
        width: width - 10,
        height: height - 10,
        rotate: degrees(0),
        xSkew: degrees(0),
        ySkew: degrees(0),
      }),
    ];

    const stream = context.formXObject(appearance, {
      Resources: { XObject: { [pdfLibSigImgName]: pdfLibSigImg.ref } },
      BBox: context.obj([0, 0, width, height]),
      Matrix: context.obj([1, 0, 0, 1, 0, 0]),
    });
    const streamRef = context.register(stream);

    widget.setNormalAppearance(streamRef);
  });

  const pdfBytes = await pdfDoc.save({ updateFieldAppearances: false });
})();

The input PDF (with_signature.pdf) has a blank signature field. This script fills it in with a base64 PNG containing the text PDF-LIB (created using http://szimek.github.io/signature_pad/). The output PDF looks like the following:

out.pdf

ealloradai commented 3 years ago

Hi, I'm new to pdf-lib, and, first of all, great job! I tested this code and it is fine. Just a question, i added to the operators a drawText call to add some text. Is it possible to use a function to let pdf-lib adapt font size and line breaks in order to fit the text into the rectangle? Thank you very much

...drawText(PDFString.of('This is the string - ciao'), {
        x: 0,
        y: height - 20,
        color: rgb(0, 0, 1),
        font: PDFName.of(fontName),
        size: 20,
        rotate: degrees(0),
        xSkew: degrees(0),
        ySkew: degrees(0),
        //graphicsState?: string | PDFName;
}
PaoloDiGallo commented 2 years ago

I did a little program to sign pdfs using pdf-lib and a signature from https://www.zetakey.com/codesample-signature.php

https://github.com/PaoloDiGallo/pdf-lib-signature

the code isn't the cleanest but it's made just to test out stuff...

i posted it in github pages too so you can check the results without needing to download it

tfrancois commented 1 month ago

I have tested the code posted by @Hopding and it is not working in my environment. Has something changed? The signature never gets updated with the image.