javascripthtmlunicodefontshtml2canvas

HTML to PF via jsPDF, ▶ character gets corrupted


I'm trying to convert an HTML element which contains the character (U+25B6), but in the PDF output it's represented it as .

I read that embedding fonts in to jsPDF will solve it, but maybe it is too complex for me or maybe I didn't understand correctly.

How can I achieve this? No matter what I've tried I could not figure out how to display it correctly.

The code it self is rather straight forwards. Any help will be greatly appreciated.

const {
  jsPDF
} = window.jspdf;

let darkModeFlag = true;
async function getOutputDivClone() {
  return document.getElementById("output").cloneNode(true)
}
async function exportPDF() {
  const outputDiv = await getOutputDivClone();
  const wrapBoth = document.createElement('div');
  wrapBoth.style.backgroundColor = darkModeFlag ? '#1E1E1E' : '#FFFFFF';
  outputDiv.style.backgroundColor = darkModeFlag ? '#1E1E1E' : '#FFFFFF';
  wrapBoth.appendChild(outputDiv);
  document.body.appendChild(wrapBoth);

  const contentWidth = wrapBoth.offsetWidth;
  const contentHeight = wrapBoth.offsetHeight;
  let doc = new jsPDF({
    orientation: contentWidth > contentHeight ? 'l' : 'p',
    unit: 'px',
    format: [contentWidth, contentHeight]
  });
  await new Promise(resolve => {
    doc.html(wrapBoth, {
      callback: resolve,
      x: 0,
      y: 0,
      margin: [0, 0, 0, 0],
      html2canvas: {
        scale: 1,
        logging: false,
        allowTaint: false,
        useCORS: true
      }
    });
  });
  wrapBoth.remove();
  if (doc.internal.getNumberOfPages() > 1) {
    for (let i = doc.internal.getNumberOfPages(); i > 1; i--) {
      doc.deletePage(i);
    }
  }
  doc.save(`test.pdf`);
}


document.addEventListener('DOMContentLoaded', async () => {
  await exportPDF()
  alert("done")
})
objinsp-summary {
            cursor: pointer;
            color: #d4d4d4;
        }

        summary {
            list-style: none;
            -webkit-appearance: none;
            appearance: none;
        }

        summary::-webkit-details-marker {
            display: none;
        }

        summary::marker {
            display: none;
        }

        details>summary::before {
            content: "▶";
            display: inline-block;
            margin-right: 5px;
            transition: transform 0.2s;
        }

        details[open]>summary::before {
            transform: rotate(90deg);
        }
<div id="output" tabindex="0">
    <div class="outputContent dark-log" style="margin-left: 4px;">
      <div class="flexContainer">
        <div class="dontAllowSelect symbolDiv">[&gt;]</div>
        <div class="outputItem">
          <details data-loaded="true" open="" style="margin-right: 15px;">
            <summary class="objinsp objinsp-summary"><span class="objinsp objinsp-highlight-type">{}</span>
            </summary>
            <div class="objinsp-node">
              <details data-loaded="true" open="">
                <summary class="objinsp objinsp-summary"><span
                                        class="objinsp objinsp-highlight-type">log: ƒ log()</span></summary>
                <div class="objinsp-node">
                  <div>
                    <div class="objinsp"><span class="objinsp-highlight-key"> length: </span><span
                                                class="objinsp objinsp-highlight-number">0</span></div>
                  </div>
                  <div>
                    <div class="objinsp"><span class="objinsp-highlight-key"> name: </span><span
                                                class="objinsp objinsp-highlight-string">"log"</span></div>
                  </div>
                  <details data-loaded="false">
                    <summary class="objinsp objinsp-summary"><span
                                                class="objinsp objinsp-highlight-type">[[Prototype]]: ƒ
                                                anonymous()</span></summary>
                  </details>
                </div>
              </details>
              <details data-loaded="false">
                <summary class="objinsp objinsp-summary"><span
                                        class="objinsp objinsp-highlight-type">error: ƒ error()</span></summary>
              </details>
              <details data-loaded="false">
                <summary class="objinsp objinsp-summary"><span
                                        class="objinsp objinsp-highlight-type">warn: ƒ warn()</span></summary>
              </details>
              <details data-loaded="false">
                <summary class="objinsp objinsp-summary"><span
                                        class="objinsp objinsp-highlight-type">info: ƒ info()</span></summary>
              </details>
              <details data-loaded="false">
                <summary class="objinsp objinsp-summary"><span
                                        class="objinsp objinsp-highlight-type">debug: ƒ debug()</span></summary>
              </details>
              <details data-loaded="false">
                <summary class="objinsp objinsp-summary"><span
                                        class="objinsp objinsp-highlight-type">clear: ƒ clear()</span></summary>
              </details>
              <details data-loaded="false">
                <summary class="objinsp objinsp-summary"><span
                                        class="objinsp objinsp-highlight-type">table: ƒ table()</span></summary>
              </details>
              <details data-loaded="false">
                <summary class="objinsp objinsp-summary"><span
                                        class="objinsp objinsp-highlight-type">count: ƒ count()</span></summary>
              </details>
              <details data-loaded="false">
                <summary class="objinsp objinsp-summary"><span
                                        class="objinsp objinsp-highlight-type">countReset: ƒ countReset()</span>
                </summary>
              </details>
              <details data-loaded="false">
                <summary class="objinsp objinsp-summary"><span
                                        class="objinsp objinsp-highlight-type">assert: ƒ assert()</span></summary>
              </details>
              <details data-loaded="false">
                <summary class="objinsp objinsp-summary"><span
                                        class="objinsp objinsp-highlight-type">dir: ƒ dir()</span></summary>
              </details>
              <details data-loaded="false">
                <summary class="objinsp objinsp-summary"><span
                                        class="objinsp objinsp-highlight-type">dirxml: ƒ dirxml()</span></summary>
              </details>
              <details data-loaded="false">
                <summary class="objinsp objinsp-summary"><span
                                        class="objinsp objinsp-highlight-type">timeStamp: ƒ timeStamp()</span></summary>
              </details>
              <details data-loaded="false">
                <summary class="objinsp objinsp-summary"><span
                                        class="objinsp objinsp-highlight-type">trace: ƒ trace()</span></summary>
              </details>
              <details data-loaded="false">
                <summary class="objinsp objinsp-summary"><span
                                        class="objinsp objinsp-highlight-type">group: ƒ group()</span></summary>
              </details>
              <details data-loaded="false">
                <summary class="objinsp objinsp-summary"><span
                                        class="objinsp objinsp-highlight-type">groupCollapsed: ƒ groupCollapsed()</span>
                </summary>
              </details>
              <details data-loaded="false">
                <summary class="objinsp objinsp-summary"><span
                                        class="objinsp objinsp-highlight-type">groupEnd: ƒ groupEnd()</span></summary>
              </details>
              <details data-loaded="false">
                <summary class="objinsp objinsp-summary"><span
                                        class="objinsp objinsp-highlight-type">profile: ƒ profile()</span></summary>
              </details>
              <details data-loaded="false">
                <summary class="objinsp objinsp-summary"><span
                                        class="objinsp objinsp-highlight-type">profileEnd: ƒ profileEnd()</span>
                </summary>
              </details>
              <details data-loaded="false">
                <summary class="objinsp objinsp-summary"><span
                                        class="objinsp objinsp-highlight-type">time: ƒ time()</span></summary>
              </details>
              <details data-loaded="false">
                <summary class="objinsp objinsp-summary"><span
                                        class="objinsp objinsp-highlight-type">timeEnd: ƒ timeEnd()</span></summary>
              </details>
              <details data-loaded="false">
                <summary class="objinsp objinsp-summary"><span
                                        class="objinsp objinsp-highlight-type">timeLog: ƒ timeLog()</span></summary>
              </details>
              <details data-loaded="false">
                <summary class="objinsp objinsp-summary"><span
                                        class="objinsp objinsp-highlight-type">[[Prototype]]: Object</span></summary>
              </details>
            </div>
          </details>
        </div>
      </div>
    </div>
  </div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>

For some odd reason here html2canvas gives a CORS error


Solution

  • I have shown elsewhere how to add a font on a page as base64 DataURL for small fonts such as Braille. https://stackoverflow.com/a/75190802/10802527

    However in this case to add a large font for one single character would be overkill, as bloating the file with the extra whole file embedment.

    I tried small dataURL icons and rotating some common symbols like Delta D and Ñ (Δ) , so as to avoid adding yet more fonts.

    I tried dingbats but they did not allow rotation with printing! enter image description here

    None worked well. However knowing that one of the PDF standard fonts is "Symbol" (without triangles!). I tried for a one byte alternative to the triangle using "Þ" see the result below. enter image description here

    const {
      jsPDF
    } = window.jspdf;
    
    let darkModeFlag = true;
    async function getOutputDivClone() {
      return document.getElementById("output").cloneNode(true)
    }
    async function exportPDF() {
      const outputDiv = await getOutputDivClone();
      const wrapBoth = document.createElement('div');
      wrapBoth.style.backgroundColor = darkModeFlag ? '#1E1E1E' : '#FFFFFF';
      outputDiv.style.backgroundColor = darkModeFlag ? '#1E1E1E' : '#FFFFFF';
      wrapBoth.appendChild(outputDiv);
      document.body.appendChild(wrapBoth);
    
      const contentWidth = wrapBoth.offsetWidth;
      const contentHeight = wrapBoth.offsetHeight;
      let doc = new jsPDF({
        orientation: contentWidth > contentHeight ? 'l' : 'p',
        unit: 'px',
        format: [contentWidth, contentHeight]
      });
      await new Promise(resolve => {
        doc.html(wrapBoth, {
          callback: resolve,
          x: 0,
          y: 0,
          margin: [0, 0, 0, 0],
          html2canvas: {
            scale: 1,
            logging: false,
            allowTaint: false,
            useCORS: true
          }
        });
      });
      wrapBoth.remove();
      if (doc.internal.getNumberOfPages() > 1) {
        for (let i = doc.internal.getNumberOfPages(); i > 1; i--) {
          doc.deletePage(i);
        }
      }
      doc.save(`test.pdf`);
    }
    
    
    document.addEventListener('DOMContentLoaded', async () => {
      await exportPDF()
      alert("done")
    })
    objinsp-summary {
                cursor: pointer;
                color: #d4d4d4;
            }
    
            summary {
                list-style: none;
                -webkit-appearance: none;
                appearance: none;
            }
    
            summary::-webkit-details-marker {
                display: none;
            }
    
            summary::marker {
                display: none;
            }
    
            details>summary::before {
             font-family: "Symbol", Symbol, symbol;
             content: "Þ";
             display: inline-block;
             margin-right: 5px;
             transition: transform 0.2s;
            }
            details[open]>summary::before {
             content: "ß"
            }
    <div id="output" tabindex="0">
        <div class="outputContent dark-log" style="margin-left: 4px;">
          <div class="flexContainer">
            <div class="dontAllowSelect symbolDiv">[&gt;]</div>
            <div class="outputItem">
              <details data-loaded="true" open="" style="margin-right: 15px;">
                <summary class="objinsp objinsp-summary"><span class="objinsp objinsp-highlight-type">{}</span>
                </summary>
                <div class="objinsp-node">
                  <details data-loaded="true" open="">
                    <summary class="objinsp objinsp-summary"><span
                                            class="objinsp objinsp-highlight-type">log: ƒ log()</span></summary>
                    <div class="objinsp-node">
                      <div>
                        <div class="objinsp"><span class="objinsp-highlight-key"> length: </span><span
                                                    class="objinsp objinsp-highlight-number">0</span></div>
                      </div>
                      <div>
                        <div class="objinsp"><span class="objinsp-highlight-key"> name: </span><span
                                                    class="objinsp objinsp-highlight-string">"log"</span></div>
                      </div>
                      <details data-loaded="false">
                        <summary class="objinsp objinsp-summary"><span
                                                    class="objinsp objinsp-highlight-type">[[Prototype]]: ƒ
                                                    anonymous()</span></summary>
                      </details>
                    </div>
                  </details>
                  <details data-loaded="false">
                    <summary class="objinsp objinsp-summary"><span
                                            class="objinsp objinsp-highlight-type">error: ƒ error()</span></summary>
                  </details>
                  <details data-loaded="false">
                    <summary class="objinsp objinsp-summary"><span
                                            class="objinsp objinsp-highlight-type">warn: ƒ warn()</span></summary>
                  </details>
                  <details data-loaded="false">
                    <summary class="objinsp objinsp-summary"><span
                                            class="objinsp objinsp-highlight-type">info: ƒ info()</span></summary>
                  </details>
                  <details data-loaded="false">
                    <summary class="objinsp objinsp-summary"><span
                                            class="objinsp objinsp-highlight-type">debug: ƒ debug()</span></summary>
                  </details>
                  <details data-loaded="false">
                    <summary class="objinsp objinsp-summary"><span
                                            class="objinsp objinsp-highlight-type">clear: ƒ clear()</span></summary>
                  </details>
                  <details data-loaded="false">
                    <summary class="objinsp objinsp-summary"><span
                                            class="objinsp objinsp-highlight-type">table: ƒ table()</span></summary>
                  </details>
                  <details data-loaded="false">
                    <summary class="objinsp objinsp-summary"><span
                                            class="objinsp objinsp-highlight-type">count: ƒ count()</span></summary>
                  </details>
                  <details data-loaded="false">
                    <summary class="objinsp objinsp-summary"><span
                                            class="objinsp objinsp-highlight-type">countReset: ƒ countReset()</span>
                    </summary>
                  </details>
                  <details data-loaded="false">
                    <summary class="objinsp objinsp-summary"><span
                                            class="objinsp objinsp-highlight-type">assert: ƒ assert()</span></summary>
                  </details>
                  <details data-loaded="false">
                    <summary class="objinsp objinsp-summary"><span
                                            class="objinsp objinsp-highlight-type">dir: ƒ dir()</span></summary>
                  </details>
                  <details data-loaded="false">
                    <summary class="objinsp objinsp-summary"><span
                                            class="objinsp objinsp-highlight-type">dirxml: ƒ dirxml()</span></summary>
                  </details>
                  <details data-loaded="false">
                    <summary class="objinsp objinsp-summary"><span
                                            class="objinsp objinsp-highlight-type">timeStamp: ƒ timeStamp()</span></summary>
                  </details>
                  <details data-loaded="false">
                    <summary class="objinsp objinsp-summary"><span
                                            class="objinsp objinsp-highlight-type">trace: ƒ trace()</span></summary>
                  </details>
                  <details data-loaded="false">
                    <summary class="objinsp objinsp-summary"><span
                                            class="objinsp objinsp-highlight-type">group: ƒ group()</span></summary>
                  </details>
                  <details data-loaded="false">
                    <summary class="objinsp objinsp-summary"><span
                                            class="objinsp objinsp-highlight-type">groupCollapsed: ƒ groupCollapsed()</span>
                    </summary>
                  </details>
                  <details data-loaded="false">
                    <summary class="objinsp objinsp-summary"><span
                                            class="objinsp objinsp-highlight-type">groupEnd: ƒ groupEnd()</span></summary>
                  </details>
                  <details data-loaded="false">
                    <summary class="objinsp objinsp-summary"><span
                                            class="objinsp objinsp-highlight-type">profile: ƒ profile()</span></summary>
                  </details>
                  <details data-loaded="false">
                    <summary class="objinsp objinsp-summary"><span
                                            class="objinsp objinsp-highlight-type">profileEnd: ƒ profileEnd()</span>
                    </summary>
                  </details>
                  <details data-loaded="false">
                    <summary class="objinsp objinsp-summary"><span
                                            class="objinsp objinsp-highlight-type">time: ƒ time()</span></summary>
                  </details>
                  <details data-loaded="false">
                    <summary class="objinsp objinsp-summary"><span
                                            class="objinsp objinsp-highlight-type">timeEnd: ƒ timeEnd()</span></summary>
                  </details>
                  <details data-loaded="false">
                    <summary class="objinsp objinsp-summary"><span
                                            class="objinsp objinsp-highlight-type">timeLog: ƒ timeLog()</span></summary>
                  </details>
                  <details data-loaded="false">
                    <summary class="objinsp objinsp-summary"><span
                                            class="objinsp objinsp-highlight-type">[[Prototype]]: Object</span></summary>
                  </details>
                </div>
              </details>
            </div>
          </div>
        </div>
      </div>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>

    enter image description here