Repository: lfoppiano/streamlit-pdf-viewer (language: python)

Issue:
Invalid canvas size
Using [this PDF](https://github.com/user-attachments/files/16319000/solvency2-factsheet.pdf) in [here](https://pdfworkdesk.streamlit.app/) lead to "invalid canvas size" popup. 

PDF Viewer isn't loading when called in tabs or expanders
When trying to render PDFs in a streamlit tab, it only works for the tab that is selected when the page loads. 
I've also seen the same behavior when rendering inside streamlit extenders.

The code is as follows:
```
paths = glob("../../*/*.pdf")
for i, (path, tab) in enumerate(zip(paths, streamlit.tabs(paths))):
  with tab:
      with streamlit.container(border=True):
          pdf_viewer(path, height = 490, render_text = True, key=f"key_{i}")
          streamlit.info(f"read from {path}")
```

What I've tried so far:
- Wrote an extra function with pdf_viewer as return value.
- Stored multiple instances of pdf_viewer in a dict and variables to make sure it wasn't an overwrite issue. (Even tried to save them in the session parameters).
- Tried all available rendering methods for the function.

I would really appreciate it if you have a workaround.

Thanks in advance!

**EDIT**: I've seen the other Issue with streamlit tabs so I was wondering if it is in your plans to fix it later and if it's the same issues with expenders.


Discussion / hints:
@SiddhantSadangi I did some tests and the issue seems to be related to the fact that you're calling the `pdf_viewer` multiple time while loading the PDF. 

I did not find why exactly why this is happening yet, I'm still investigating and is quite time consuming. 
~~Anyway I think the issue is most likely that we never designed this component to have multiple instance of the PDF opened, so we need to generate some unique id to be used for the name of certain internal HTML elements.~~
Looking better, each component should be isolated by an iframe so I don't really know what happens and why we have this invalid frame size
Thanks for looking into this @lfoppiano .

If it was an underlying issue with the component itself, the popup would show for any PDF.
Could there be something specifically in this PDF that is breaking the component?
@SiddhantSadangi indeed, that's a good point. if I remove the alert after catching an exception, it will works as if nothing have happened, showing all the PDF correctly, but I want to do a few more tests before that. 
I have been having this same issue. I thought it was the PDF size, but it still appears to be happening when I reduce the width to something very small.

I appreciate the support on this!
@ehenry09 could you share the PDF and the code that you used (or a similar example)? 
> @ehenry09 could you share the PDF and the code that you used (or a similar example)?

I played around with this a little bit this morning, and I ** think ** I am only getting this error when I try to render a PDF in the context of a tab.

This is the PDF file: https://www.dropbox.com/sh/wty3w9kl7v8u40t/AADbg8ITc8ZUpcatDAaLB-8sa/Current%20Rule%20Book/1.2%20Final?dl=0&preview=Rulebook+1p2+Final+Version+02-27-23+Optimized.pdf&subfolder_nav_tracking=1

Here is an example of code that I used to reproduce it.

```
import streamlit as st
from streamlit_pdf_viewer import pdf_viewer


def init_session_state() -> None:

    if "curr_page" not in st.session_state:
        st.session_state["curr_page"] = 1


def set_curr_page():
    try:
        if st.session_state["user_page_input"].isdigit() and int(st.session_state["user_page_input"]) > 0:
            st.session_state["curr_page"] = st.session_state["user_page_input"]
    except:
        pass


def render_rulebook_tab() -> None:

    min_page = 1
    max_page = 1000

    col1, col2, col3 = st.columns([4, 2, 2])

    with col1:

        user_input = st.text_input(
            label="Rulebook Navigation",
            value=None,
            on_change=set_curr_page,
            placeholder="Go to page ...",
        )

    with col2:

        previous = st.button(
            label="Previous",
            key="previous",
            use_container_width=True,
        )

    with col3:

        next = st.button(
            label="Next",
            key="next",
            use_container_width=True,
        )

    if previous:
        st.session_state["curr_page"] = max(min_page, int(st.session_state["curr_page"]) - 1)

    if next:
        st.session_state["curr_page"] = min(max_page, int(st.session_state["curr_page"]) + 1)

    if st.session_state["curr_page"]:
        pdf_viewer("data/rulebooks/middara/Rulebook 1p2 Final Version 02-27-23 Optimized.pdf", pages_to_render=[int(st.session_state["curr_page"])])



def render_main_page() -> None:
        tab1, tab2 = st.tabs(["tab1", "tab2"])

        with tab1:
            st.markdown("Test Tab")

        with tab2:
            render_rulebook_tab()


def main() -> None:

    init_session_state()
    render_main_page()


if __name__ == "__main__":
    main()
```
Hi @hamdi3, thanks for reporting the issue. 

There is an issue that looks similar (#53), I tried to look for the cause and for see if I could find a workaround but i did not manage. What I understood is that if the tab is not selected, the underlying iframe does not get the correct dimension and the pdf is not shown. 

I wish I could spend more time to try to fix it rapidly because is indeed an annoying problem. 

Fix patch:
diff --git a/.bumpversion.toml b/.bumpversion.toml
index a360a3f..05b164e 100644
--- a/.bumpversion.toml
+++ b/.bumpversion.toml
@@ -1,5 +1,25 @@
 [tool.bumpversion]
-current_version = "0.0.14"
+current_version = "0.0.16-dev1"
 commit = "true"
 tag = "true"
-tag_name = "v{new_version}"
\ No newline at end of file
+tag_name = "v{new_version}"
+
+parse = """(?x)
+    (?P<major>0|[1-9]\\d*)\\.
+    (?P<minor>0|[1-9]\\d*)\\.
+    (?P<patch>0|[1-9]\\d*)
+    (?:
+        -                             # dash separator for pre-release section
+        (?P<pre_l>[a-zA-Z-]+)         # pre-release label
+        (?P<pre_n>0|[1-9]\\d*)        # pre-release version number
+    )?                                # pre-release section is optional
+"""
+
+serialize = [
+    "{major}.{minor}.{patch}-{pre_l}{pre_n}",
+    "{major}.{minor}.{patch}",
+]
+
+[tool.bumpversion.parts.pre_l]
+values = ["dev", "rc", "final"]
+optional_value = "final"
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index d5b0006..6dfc215 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,4 +6,5 @@ dist
 /frontend/dist
 streamlit_pdf_viewer/frontend/package-lock.json
 streamlit_pdf_viewer.egg-info
-streamlit_pdf_viewer/__pycache__/
\ No newline at end of file
+streamlit_pdf_viewer/__pycache__/
+tests/__pycache__
\ No newline at end of file
diff --git a/streamlit_pdf_viewer/__init__.py b/streamlit_pdf_viewer/__init__.py
index b28b065..09110f5 100644
--- a/streamlit_pdf_viewer/__init__.py
+++ b/streamlit_pdf_viewer/__init__.py
@@ -97,15 +97,34 @@ def pdf_viewer(input: Union[str, Path, bytes],
 
 
 if not _RELEASE:
+    import streamlit as st
+
     with open("resources/test.pdf", 'rb') as fo:
         binary = fo.read()
 
+    with open("resources/test.pdf", 'rb') as fo:
+        binary2 = fo.read()
+
     with open("resources/annotations.sample.json", 'rb') as fo:
         annotations = json.loads(fo.read())
 
-    viewer = pdf_viewer(
-        binary,
-        width=800,
-        annotations=annotations,
-        render_text=True
-    )
+    tab1, tab2 = st.tabs(["tab1", "tab2"])
+
+    with tab1:
+        st.markdown("tab 1")
+        with st.container(height=300):
+            viewer = pdf_viewer(
+                binary,
+                annotations=annotations,
+                render_text=True,
+                key="bao"
+            )
+    with tab2:
+        st.markdown("tab 2")
+        viewer2 = pdf_viewer(
+            binary2,
+            height=500,
+            annotations=annotations,
+            render_text=True,
+            key="miao"
+        )
\ No newline at end of file
diff --git a/streamlit_pdf_viewer/frontend/package.json b/streamlit_pdf_viewer/frontend/package.json
index 8acd22b..72d5b43 100644
--- a/streamlit_pdf_viewer/frontend/package.json
+++ b/streamlit_pdf_viewer/frontend/package.json
@@ -8,7 +8,7 @@
   },
   "dependencies": {
     "core-js": "^3.6.5",
-    "pdfjs-dist": "^4.2.67",
+    "pdfjs-dist": "^4.5.136",
     "streamlit-component-lib": "^2.0.0",
     "vue": "^3.0.0-0"
   },
diff --git a/streamlit_pdf_viewer/frontend/src/PdfViewer.vue b/streamlit_pdf_viewer/frontend/src/PdfViewer.vue
index 5e92901..d62619f 100644
--- a/streamlit_pdf_viewer/frontend/src/PdfViewer.vue
+++ b/streamlit_pdf_viewer/frontend/src/PdfViewer.vue
@@ -135,12 +135,14 @@ export default {
         // textLayerDiv.style.position = "absolute";
         // textLayerDiv.style.height = `${viewport.height}px`;
         // textLayerDiv.style.width = `${viewport.width}px`;
-        pdfjsLib.renderTextLayer({
+        const textLayer = new pdfjsLib.TextLayer({
           textContentSource: textContent,
           container: textLayerDiv,
           viewport: viewport,
           textDivs: []
         })
+        await textLayer.render()
+        
         const pageDiv = document.createElement('div');
         pageDiv.className = 'page';
 
@@ -186,12 +188,35 @@ export default {
           rotation: rotation,
         })
 
+        // console.log(`unscaledViewport ${pageNumber}`)
+        // console.log(unscaledViewport)
+
+        // console.log("Max width")
+        // console.log(maxWidth.value)
+
+        // console.log("Height")
+        // console.log(props.args.height)
+
+        if (props.args.height > 0) {
+          let widthScale =  unscaledViewport.width / unscaledViewport.height
+          const possibleScaledWidth = widthScale * props.args.height
+          if (maxWidth.value === 0) {
+            maxWidth.value = possibleScaledWidth
+          } else if (possibleScaledWidth < maxWidth.value) {
+            maxWidth.value = possibleScaledWidth
+          }
+        }
+
         const scale = maxWidth.value / unscaledViewport.width
 
         pageScales.value.push(scale)
         pageHeights.value.push(unscaledViewport.height)
         if (pagesToRender.includes(pageNumber)) {
           const canvas = createCanvasForPage(page, scale, rotation, pageNumber)
+
+          // console.log(`canvas`)
+          // console.log(canvas)
+
           // pdfViewer?.append(canvas)
           pdfViewer.style.setProperty('--scale-factor', scale);
 
@@ -201,6 +226,9 @@ export default {
             intent: "print",
           });
 
+          // console.log(`Scaled viewport`)
+          // console.log(viewport)
+
           const ratio = window.devicePixelRatio || 1
           totalHeight.value += canvas.height / ratio
           await renderPage(page, canvas, viewport)
@@ -248,8 +276,10 @@ export default {
 
         // If the desired width is larger than the available inner width,
         // we should not exceed it. To be revised
-        if (window.innerWidth < maxWidth.value) {
-          maxWidth.value = window.innerWidth
+        if (window.innerWidth > 0) {
+          if (window.innerWidth < maxWidth.value) {
+            maxWidth.value = window.innerWidth
+          }
         }
       }
     }


Test patch:
diff --git a/tests/streamlit_apps/example_tab.py b/tests/streamlit_apps/example_tab.py
new file mode 100644
index 0000000..92ac9d9
--- /dev/null
+++ b/tests/streamlit_apps/example_tab.py
@@ -0,0 +1,16 @@
+import os
+
+import streamlit as st
+
+from streamlit_pdf_viewer import pdf_viewer
+from tests import ROOT_DIRECTORY
+
+st.subheader("Test PDF Viewer with the PDF in a tab")
+
+tab1, tab2 = st.tabs(["tab1", "tab2"])
+
+with tab1:
+    pdf_viewer(os.path.join(ROOT_DIRECTORY, "resources/test.pdf"))
+
+with tab2:
+    pdf_viewer(os.path.join(ROOT_DIRECTORY, "resources/test.pdf"), width=300)
diff --git a/tests/streamlit_apps/example_tab_2.py b/tests/streamlit_apps/example_tab_2.py
new file mode 100644
index 0000000..6c27a82
--- /dev/null
+++ b/tests/streamlit_apps/example_tab_2.py
@@ -0,0 +1,16 @@
+import os
+
+import streamlit as st
+
+from streamlit_pdf_viewer import pdf_viewer
+from tests import ROOT_DIRECTORY
+
+st.subheader("Test PDF Viewer with the PDF in a tab 2")
+
+tab1, tab2 = st.tabs(["tab1", "tab2"])
+
+with tab1:
+    pdf_viewer(os.path.join(ROOT_DIRECTORY, "resources/test.pdf"))
+
+with tab2:
+    pdf_viewer(os.path.join(ROOT_DIRECTORY, "resources/test.pdf"), height=300)
diff --git a/tests/test_tabs.py b/tests/test_tabs.py
new file mode 100644
index 0000000..3119f1e
--- /dev/null
+++ b/tests/test_tabs.py
@@ -0,0 +1,90 @@
+import os
+from pathlib import Path
+
+import pytest
+from playwright.sync_api import Page, expect
+
+from tests import ROOT_DIRECTORY
+from tests.e2e_utils import StreamlitRunner
+
+BASIC_EXAMPLE_FILE = os.path.join(ROOT_DIRECTORY, "tests", "streamlit_apps", "example_tab.py")
+
+
+@pytest.fixture(scope="session")
+def browser_type_launch_args(browser_type_launch_args):
+    return {
+        **browser_type_launch_args,
+        "firefox_user_prefs": {
+            "pdfjs.disabled": False,
+        }
+    }
+
+
+@pytest.fixture(autouse=True, scope="module")
+def streamlit_app():
+    with StreamlitRunner(Path(BASIC_EXAMPLE_FILE)) as runner:
+        yield runner
+
+
+@pytest.fixture(autouse=True, scope="function")
+def go_to_app(page: Page, streamlit_app: StreamlitRunner):
+    page.goto(streamlit_app.server_url)
+    # Wait for app to load
+    page.get_by_role("img", name="Running...").is_hidden()
+
+
+def test_should_render_template_check_container_size(page: Page):
+    expect(page.get_by_text("Test PDF Viewer with the PDF in a tab")).to_be_visible()
+
+    iframe_component = page.locator('iframe[title="streamlit_pdf_viewer.streamlit_pdf_viewer"]').nth(0)
+    expect(iframe_component).to_be_visible()
+
+    iframe_box = iframe_component.bounding_box()
+    assert iframe_box['width'] > 0
+    assert iframe_box['height'] > 0
+
+    # Tab 1
+    iframe_frame_0 = page.frame_locator('iframe[title="streamlit_pdf_viewer.streamlit_pdf_viewer"]').nth(0)
+    pdf_container_0 = iframe_frame_0.locator('div[id="pdfContainer"]')
+    expect(pdf_container_0).to_be_visible()
+
+    b_box_0 = pdf_container_0.bounding_box()
+    assert round(b_box_0['height']) > iframe_box['height']
+    assert b_box_0['width'] == iframe_box['width']
+
+    pdf_viewer_0 = iframe_frame_0.locator('div[id="pdfViewer"]')
+    expect(pdf_viewer_0).to_be_visible()
+
+    annotations_locator = page.locator('div[id="pdfAnnotations"]').nth(0)
+    expect(annotations_locator).to_be_hidden()
+
+    # Tab 2
+    iframe_frame_1 = page.frame_locator('iframe[title="streamlit_pdf_viewer.streamlit_pdf_viewer"]').nth(1)
+    pdf_container_1 = iframe_frame_1.locator('div[id="pdfContainer"]')
+    expect(pdf_container_1).not_to_be_visible()
+
+    pdf_viewer_1 = iframe_frame_1.locator('div[id="pdfViewer"]')
+    expect(pdf_viewer_1).not_to_be_visible()
+
+    annotations_locator = page.locator('div[id="pdfAnnotations"]').nth(1)
+    expect(annotations_locator).to_be_hidden()
+
+    tab0 = page.get_by_text('tab1')
+    expect(tab0).to_be_visible()
+
+    tab1 = page.get_by_text('tab2')
+    expect(tab1).to_be_visible()
+
+    # click on the second tab and verify that the PDF is visible
+    tab1.click()
+
+    iframe_frame_1 = page.frame_locator('iframe[title="streamlit_pdf_viewer.streamlit_pdf_viewer"]').nth(1)
+    pdf_container_1 = iframe_frame_1.locator('div[id="pdfContainer"]')
+    expect(pdf_container_1).to_be_visible()
+
+    b_box_1 = pdf_container_1.bounding_box()
+    assert round(b_box_1['height']) < iframe_box['height'] and round(b_box_1['height']) > 0
+    assert b_box_1['width'] == 300
+
+    pdf_viewer_1 = iframe_frame_1.locator('div[id="pdfViewer"]')
+    expect(pdf_viewer_1).to_be_visible()
diff --git a/tests/test_tabs_2.py b/tests/test_tabs_2.py
new file mode 100644
index 0000000..f9c1ce9
--- /dev/null
+++ b/tests/test_tabs_2.py
@@ -0,0 +1,92 @@
+import os
+from pathlib import Path
+
+import pytest
+from playwright.sync_api import Page, expect
+
+from tests import ROOT_DIRECTORY
+from tests.e2e_utils import StreamlitRunner
+
+BASIC_EXAMPLE_FILE = os.path.join(ROOT_DIRECTORY, "tests", "streamlit_apps", "example_tab_2.py")
+
+
+@pytest.fixture(scope="session")
+def browser_type_launch_args(browser_type_launch_args):
+    return {
+        **browser_type_launch_args,
+        "firefox_user_prefs": {
+            "pdfjs.disabled": False,
+        }
+    }
+
+
+@pytest.fixture(autouse=True, scope="module")
+def streamlit_app():
+    with StreamlitRunner(Path(BASIC_EXAMPLE_FILE)) as runner:
+        yield runner
+
+
+@pytest.fixture(autouse=True, scope="function")
+def go_to_app(page: Page, streamlit_app: StreamlitRunner):
+    page.goto(streamlit_app.server_url)
+    # Wait for app to load
+    page.get_by_role("img", name="Running...").is_hidden()
+
+
+def test_should_render_template_check_container_size(page: Page):
+    expect(page.get_by_text("Test PDF Viewer with the PDF in a tab")).to_be_visible()
+
+    iframe_component = page.locator('iframe[title="streamlit_pdf_viewer.streamlit_pdf_viewer"]').nth(0)
+    expect(iframe_component).to_be_visible()
+
+    iframe_box = iframe_component.bounding_box()
+    assert iframe_box['width'] > 0
+    assert iframe_box['height'] > 0
+
+    # Tab 1
+    iframe_frame_0 = page.frame_locator('iframe[title="streamlit_pdf_viewer.streamlit_pdf_viewer"]').nth(0)
+    pdf_container_0 = iframe_frame_0.locator('div[id="pdfContainer"]')
+    expect(pdf_container_0).to_be_visible()
+
+    b_box_0 = pdf_container_0.bounding_box()
+    assert round(b_box_0['height']) > iframe_box['height']
+    assert b_box_0['width'] == iframe_box['width']
+
+    pdf_viewer_0 = iframe_frame_0.locator('div[id="pdfViewer"]')
+    expect(pdf_viewer_0).to_be_visible()
+
+    annotations_locator = page.locator('div[id="pdfAnnotations"]').nth(0)
+    expect(annotations_locator).to_be_hidden()
+
+    # Tab 2
+    iframe_frame_1 = page.frame_locator('iframe[title="streamlit_pdf_viewer.streamlit_pdf_viewer"]').nth(1)
+    pdf_container_1 = iframe_frame_1.locator('div[id="pdfContainer"]')
+    expect(pdf_container_1).not_to_be_visible()
+
+    pdf_viewer_1 = iframe_frame_1.locator('div[id="pdfViewer"]')
+    expect(pdf_viewer_1).not_to_be_visible()
+
+    annotations_locator = page.locator('div[id="pdfAnnotations"]').nth(1)
+    expect(annotations_locator).to_be_hidden()
+
+    tab0 = page.get_by_text('tab1')
+    expect(tab0).to_be_visible()
+
+    tab1 = page.get_by_text('tab2')
+    expect(tab1).to_be_visible()
+
+    # click on the second tab and verify that the PDF is visible
+    tab1.click()
+
+    iframe_frame_1 = page.frame_locator('iframe[title="streamlit_pdf_viewer.streamlit_pdf_viewer"]').nth(1)
+    pdf_container_1 = iframe_frame_1.locator('div[id="pdfContainer"]')
+    expect(pdf_container_1).to_be_visible()
+
+    b_box_1 = pdf_container_1.bounding_box()
+    assert b_box_1['height'] == 300
+    # The second part of the If tests that the width < height, which indicate that we have resized
+    # the PDF to keep the proportions
+    assert round(b_box_1['width']) < iframe_box['width'] and round(b_box_1['width']) < round(b_box_1['height'])
+
+    pdf_viewer_1 = iframe_frame_1.locator('div[id="pdfViewer"]')
+    expect(pdf_viewer_1).to_be_visible()
diff --git a/tests/test_unwrap_height.py b/tests/test_unwrap_height.py
index cedeb34..624c5d8 100644
--- a/tests/test_unwrap_height.py
+++ b/tests/test_unwrap_height.py
@@ -40,7 +40,8 @@ def test_should_render_template_check_container_size(page: Page):
     b_box = pdf_container.bounding_box()
     # Since we do not specify the width, we occupy all the available space, which should correspond to the
     # parent element's width of the pdfContainer.
-    assert b_box['width'] == iframe_box['width']
+    # LF: This was changed with #58, where the proportions are maintained, or at least we try to
+    assert b_box['width'] < iframe_box['width']
     assert b_box['height'] == 300
 
     pdf_viewer = iframe_frame.locator('div[id="pdfViewer"]')