-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement preview functionality for
pdf
files (#4157)
- Loading branch information
Showing
12 changed files
with
740 additions
and
16 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
<template> | ||
<div> | ||
<canvas id="pdf" /> | ||
|
||
<nav v-if="rendered"> | ||
<el-tooltip :content="$t('page.previous')" effect="light" :show-after="1500"> | ||
<el-button @click="onPrevPage"> | ||
<chevron-left /> | ||
</el-button> | ||
</el-tooltip> | ||
<span> | ||
{{ pageNum }} | ||
{{ $t("of") }} | ||
{{ pdfDoc?.numPages }} | ||
</span> | ||
<el-tooltip :content="$t('page.next')" effect="light" :show-after="1500"> | ||
<el-button @click="onNextPage"> | ||
<chevron-right /> | ||
</el-button> | ||
</el-tooltip> | ||
</nav> | ||
</div> | ||
</template> | ||
|
||
<script> | ||
import * as pdfjs from "pdfjs-dist"; | ||
import ChevronLeft from "vue-material-design-icons/ChevronLeft.vue"; | ||
import ChevronRight from "vue-material-design-icons/ChevronRight.vue"; | ||
|
||
export default { | ||
components: { | ||
ChevronLeft, | ||
ChevronRight | ||
}, | ||
props: { | ||
source: { | ||
type: String, | ||
required: true | ||
} | ||
}, | ||
data() { | ||
// Can't be a reactive prop | ||
this.pdfDoc = undefined; | ||
|
||
return { | ||
pageNum: 1, | ||
rendered: false, | ||
pageRendering: false, | ||
pageNumPending: undefined, | ||
scale: 1.5 | ||
} | ||
}, | ||
computed: { | ||
canvas() { | ||
return document.getElementById("pdf"); | ||
}, | ||
context() { | ||
return this.canvas.getContext("2d"); | ||
} | ||
}, | ||
mounted() { | ||
// Provide worker location | ||
pdfjs.GlobalWorkerOptions.workerSrc = this.getWorkerUrl(); | ||
|
||
// Initial/first page rendering | ||
this.initRender(); | ||
}, | ||
methods: { | ||
getWorkerUrl() { | ||
return new URL( | ||
"pdfjs-dist/build/pdf.worker.min.mjs", | ||
import.meta.url | ||
).toString(); | ||
}, | ||
renderPage(pageNum) { | ||
this.pageRendering = true; | ||
|
||
// Using promise to fetch the page | ||
this.pdfDoc.getPage(pageNum).then((page) => { | ||
const viewport = page.getViewport({scale: this.scale}); | ||
this.canvas.height = viewport.height; | ||
this.canvas.width = viewport.width; | ||
|
||
// Render PDF page into canvas context | ||
const renderContext = { | ||
canvasContext: this.context, | ||
viewport: viewport | ||
}; | ||
const renderTask = page.render(renderContext); | ||
|
||
// Wait for rendering to finish | ||
renderTask.promise.then(() => { | ||
this.rendered = true; | ||
this.pageRendering = false; | ||
|
||
if (this.pageNumPending) { | ||
// New page rendering is pending | ||
this.renderPage(this.pageNumPending); | ||
this.pageNumPending = undefined; | ||
} | ||
}); | ||
}); | ||
}, | ||
initRender() { | ||
// Decode PDF document | ||
pdfjs.getDocument({data: atob(this.source)}).promise.then((pdf) => { | ||
this.pdfDoc = pdf; | ||
|
||
this.renderPage(this.pageNum); | ||
}, () => { | ||
// PDF loading error | ||
this.$toast().error(this.$t("failed to render pdf")); | ||
}); | ||
}, | ||
queueRenderPage(pageNum) { | ||
if (this.pageRendering) { | ||
this.pageNumPending = pageNum; | ||
} else { | ||
this.renderPage(pageNum); | ||
} | ||
}, | ||
onPrevPage() { | ||
if (this.pageNum <= 1) { | ||
return; | ||
} | ||
this.pageNum--; | ||
this.queueRenderPage(this.pageNum); | ||
}, | ||
onNextPage() { | ||
if (this.pageNum >= this.pdfDoc.numPages) { | ||
return; | ||
} | ||
this.pageNum++; | ||
this.queueRenderPage(this.pageNum); | ||
} | ||
} | ||
} | ||
</script> | ||
|
||
<style lang="scss" scoped> | ||
nav { | ||
display: flex; | ||
gap: 1rem; | ||
align-items: center; | ||
justify-content: center; | ||
margin-top: 0.5em; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 15 additions & 0 deletions
15
webserver/src/main/java/io/kestra/webserver/utils/filepreview/Base64Render.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package io.kestra.webserver.utils.filepreview; | ||
|
||
import org.apache.commons.io.IOUtils; | ||
|
||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.util.Base64; | ||
|
||
public class Base64Render extends FileRender { | ||
|
||
Base64Render(String extension, InputStream inputStream, Integer maxLine) throws IOException { | ||
super(extension, maxLine); | ||
this.content = Base64.getEncoder().encodeToString(IOUtils.toByteArray(inputStream)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,7 @@ public enum Type { | |
TEXT, | ||
LIST, | ||
IMAGE, | ||
MARKDOWN | ||
MARKDOWN, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 2 additions & 5 deletions
7
webserver/src/main/java/io/kestra/webserver/utils/filepreview/ImageFileRender.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
14 changes: 14 additions & 0 deletions
14
webserver/src/main/java/io/kestra/webserver/utils/filepreview/PdfFileRender.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package io.kestra.webserver.utils.filepreview; | ||
|
||
import lombok.Getter; | ||
|
||
import java.io.IOException; | ||
import java.io.InputStream; | ||
|
||
@Getter | ||
public class PdfFileRender extends Base64Render { | ||
PdfFileRender(String extension, InputStream inputStream, Integer maxLine) throws IOException { | ||
super(extension, inputStream, maxLine); | ||
this.type = Type.PDF; | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
webserver/src/test/java/io/kestra/webserver/utils/filepreview/FileRenderBuilderTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package io.kestra.webserver.utils.filepreview; | ||
|
||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.Arguments; | ||
import org.junit.jupiter.params.provider.MethodSource; | ||
|
||
import java.io.ByteArrayInputStream; | ||
import java.io.IOException; | ||
import java.nio.charset.StandardCharsets; | ||
import java.util.Optional; | ||
import java.util.stream.Stream; | ||
|
||
import static org.hamcrest.MatcherAssert.assertThat; | ||
import static org.hamcrest.Matchers.is; | ||
|
||
public class FileRenderBuilderTest { | ||
@ParameterizedTest | ||
@MethodSource("provideExtensions") | ||
void of(String extension, Class returnedClass) throws IOException { | ||
var emptyInput = new ByteArrayInputStream("".getBytes()); | ||
var charset = StandardCharsets.UTF_8; | ||
|
||
assertThat( | ||
FileRenderBuilder.of(extension, emptyInput, Optional.of(charset), 1000).getClass(), | ||
is(returnedClass) | ||
); | ||
} | ||
|
||
private static Stream<Arguments> provideExtensions() { | ||
return Stream.of( | ||
Arguments.of("ion", IonFileRender.class), | ||
Arguments.of("md", DefaultFileRender.class), | ||
Arguments.of("pdf", PdfFileRender.class), | ||
Arguments.of("PDF", PdfFileRender.class), | ||
Arguments.of("foobar", DefaultFileRender.class) | ||
); | ||
} | ||
} |