htmlcsscss-positionpdf.jspdfium

Make absolute-positioned children dynamically resize with their ascendants


(Please ignore the empty squares.)

  1. without CSS view { height: 45em; }, I get: enter image description here (position overlap)
  2. with CSS view { height: 45em; }, I get: enter image description here (unwanted, position mismatch)

How can I have the blue <span> element positioned correctly in the second case?

<view style="height: 45em;">
  <pdf-page>                                                    <!-- position: relative -->
    <text class="textLayer">                                    <!-- position: absolute -->
      <span style="left: 417.34px; top: 37.8391px; ..."></span> <!-- position: absolute -->
    </text>
    <svg width="595px" height="842px" preserveAspectRatio="none" viewBox="0 0 595 842" xmlns="http://www.w3.org/2000/svg" version="1.1">
      <g ⋯><g ⋯><text><tspan></tspan></text></g></g>
    </svg>
  </pdf-page>
</view>

Here is the complete case in stackoverflow (see /* ← */ in the second pane after clicking on Show code snippet):

@namespace     url(http://www.w3.org/1999/xhtml);
@namespace svg url(http://www.w3.org/2000/svg);

/*pdf.css*/
:root {
  --pdf-page-outline-color: #aaa;
  --pdf-page-background-color: #fcfcfc;
}

pdf-file { display: contents; }
pdf-page {
  display: inline-block;
  outline: 1px solid var(--pdf-page-outline-color);
  background-color:  var(--pdf-page-background-color);
}

pdf-page { position: relative; }

/* text.css */
.textLayer {
  position: absolute;
  left: 0; top: 0; right: 0; bottom: 0;
  width: 100%; height: 100%;
 -overflow: hidden;
  opacity: 1;
 -line-height: 1;
}

.textLayer > span {
  color: transparent;
  position: absolute;
  white-space: pre;
  cursor: text;
  -webkit-transform-origin: 0% 0%;
          transform-origin: 0% 0%;
}

/**/
 view      { background: green; }
.textLayer { background: rgba(0, 255, 0, .1); }
 svg|svg   { background: rgba(255, 0, 0, .1); }
<style>
  view {
    height: 45em; /* ← */
    display: flex;
    overflow: auto;
    flex-direction: column;
    place-items: center;
    scroll-snap-type: y mandatory;
    overflow: auto;
  }

  pdf-page { height: 100%; scroll-snap-align: start; }
  svg { height: 100%; width: auto; }

  text { overflow: visible; background: rgb(0, 0, 0, .1); }
  text > span { background: rgba(0,0,255,.1); }
</style>

<view -onclick="this.requestFullscreen()">
  <pdf-page of="f" no="+1" svg="">
    <text class="textLayer">
      <span style="left: 417.34px; top: 37.8391px; font-size: 12px; font-family: sans-serif; transform: scaleX(1.07482);">Plenarprotokoll 16/3</span>
    </text>
    <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="595px" height="842px" preserveAspectRatio="none" viewBox="0 0 595 842">
      <g transform="matrix(1 0 0 -1 -8 850)">
        <g transform="">
          <text transform="matrix(12 0 0 12 425.34 801.2976) scale(1, -1)" xml:space="preserve">
            <tspan x="0 0.6672 0.9454 1.5016 2.1128 2.669 3.0582 3.6694 4.0586 4.6698 5.003 5.6142 6.1704 6.7816 7.0598 7.6132 8.1694 8.7256 9.0038" y="0" font-family="g_d0_f1" font-size="1px" fill="rgb(0,0,0)"></tspan>
          </text>
        </g>
      </g>
    </svg>
  </pdf-page>
</view>

(also available for review on codepen: https://codepen.io/cetinsert/pen/MWeVxLe?editors=1100)


Solution

  • A much more precise way is to just transform: scale(x, y) the <text> layer once on resize without any <span style> position value recalculations / unit change.


    This answer has triggered the launch of my commercial project.

    WebPDF.prohttps://WebPDF.pro

    Zero-dependency, truly HTML-native PDF web components.


    const t = document.querySelector('text');
    const r = new ResizeObserver(textResize(t));
          r.observe(t);
    
    const textResize  = t => ([ a ]) => {
      const         e = t.parentNode.lastElementChild; // <svg> | <canvas>
      const         i = PDFPageElement.image(e);       // { height, width };
      const     h = e.clientHeight;
      const x = h / i.      height;
      const     w = e.clientWidth;
      const y = w / i.      width;
                        t.style.setProperty('transform', `scale(${x}, ${y})`);
    };
    
    PDFPageElement.image = i => { if (!i) return;
      switch (i.tagName) {
        case 'CANVAS':   return { height: i.height,               width: i.width               };
        default: /*SVG*/ return { height: i.height.baseVal.value, width: i.width.baseVal.value };
      }    
    };
    

    with 1 additional CSS rule

    .textLayer { overflow: visible; }
    

    Before / After

    before after