diff --git a/docs/content/doc/advanced/external-renderers.en-us.md b/docs/content/doc/advanced/external-renderers.en-us.md
index 4e5e72554d9d3..8f60387466661 100644
--- a/docs/content/doc/advanced/external-renderers.en-us.md
+++ b/docs/content/doc/advanced/external-renderers.en-us.md
@@ -158,6 +158,35 @@ RENDER_COMMAND = "jupyter-nbconvert --stdin --stdout --to html --template basic"
ALLOW_DATA_URI_IMAGES = true
```
+### Example: Office PPTX
+
+Convert Office PPTX files to PDF using
+[LibreOffice CLI](https://help.libreoffice.org/latest/en-US/text/shared/guide/start_parameters.html):
+
+```ini
+[markup.pptx]
+ENABLED = true
+FILE_EXTENSIONS = .pptx
+IS_INPUT_FILE = true
+RENDER_COMMAND = ./convert-pptx.sh
+RENDER_CONTENT_MODE = pdf
+```
+
+The script `convert-pptx.sh`:
+
+```sh
+#!/usr/bin/env sh
+set -eu
+file="$1"
+dir=`mktemp -d`
+libreoffice --convert-to pdf "$file" --outdir "$dir"
+cat "$dir/$(basename $file .pptx).pdf"
+rm -rf "$dir"
+```
+
+Using `RENDER_CONTENT_MODE = pdf` makes Gitea to embed files into a PDF viewer.
+It is mutually exclusive with post-processing and sanitization.
+
## Customizing CSS
The external renderer is specified in the .ini in the format `[markup.XXXXX]` and the HTML supplied by your external renderer will be wrapped in a `
` with classes `markup` and `XXXXX`. The `markup` class provides out of the box styling (as does `markdown` if `XXXXX` is `markdown`). Otherwise you can use these classes to specifically target the contents of your rendered HTML.
diff --git a/modules/markup/external/external.go b/modules/markup/external/external.go
index 23dd45ba0a1f2..9b6373a999ae6 100644
--- a/modules/markup/external/external.go
+++ b/modules/markup/external/external.go
@@ -61,7 +61,9 @@ func (p *Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
// SanitizerDisabled disabled sanitize if return true
func (p *Renderer) SanitizerDisabled() bool {
- return p.RenderContentMode == setting.RenderContentModeNoSanitizer || p.RenderContentMode == setting.RenderContentModeIframe
+ return p.RenderContentMode == setting.RenderContentModeNoSanitizer ||
+ p.RenderContentMode == setting.RenderContentModeIframe ||
+ p.RenderContentMode == setting.RenderContentModePDF
}
// DisplayInIFrame represents whether render the content with an iframe
@@ -69,6 +71,10 @@ func (p *Renderer) DisplayInIFrame() bool {
return p.RenderContentMode == setting.RenderContentModeIframe
}
+func (p *Renderer) DisplayAsPDF() bool {
+ return p.RenderContentMode == setting.RenderContentModePDF
+}
+
func envMark(envName string) string {
if runtime.GOOS == "windows" {
return "%" + envName + "%"
diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go
index 5f69dc72354f0..4815e12c5ddf1 100644
--- a/modules/markup/renderer.go
+++ b/modules/markup/renderer.go
@@ -106,6 +106,9 @@ type ExternalRenderer interface {
// DisplayInIFrame represents whether render the content with an iframe
DisplayInIFrame() bool
+
+ // DisplayAsPDF represents whether to the renderer output should be viewed as PDF.
+ DisplayAsPDF() bool
}
// RendererContentDetector detects if the content can be rendered
@@ -177,23 +180,38 @@ type nopCloser struct {
func (nopCloser) Close() error { return nil }
+func getRenderURL(ctx *RenderContext) string {
+ return fmt.Sprintf("%s/%s/%s/render/%s/%s",
+ setting.AppSubURL,
+ url.PathEscape(ctx.Metas["user"]),
+ url.PathEscape(ctx.Metas["repo"]),
+ ctx.Metas["BranchNameSubURL"],
+ url.PathEscape(ctx.RelativePath),
+ )
+}
+
func renderIFrame(ctx *RenderContext, output io.Writer) error {
// set height="0" ahead, otherwise the scrollHeight would be max(150, realHeight)
// at the moment, only "allow-scripts" is allowed for sandbox mode.
// "allow-same-origin" should never be used, it leads to XSS attack, and it makes the JS in iframe can access parent window's config and CSRF token
// TODO: when using dark theme, if the rendered content doesn't have proper style, the default text color is black, which is not easy to read
_, err := io.WriteString(output, fmt.Sprintf(`
-`,
- setting.AppSubURL,
- url.PathEscape(ctx.Metas["user"]),
- url.PathEscape(ctx.Metas["repo"]),
- ctx.Metas["BranchNameSubURL"],
- url.PathEscape(ctx.RelativePath),
+ getRenderURL(ctx),
+ ))
+ return err
+}
+
+func renderPDFViewer(ctx *RenderContext, output io.Writer) error {
+ _, err := io.WriteString(output, fmt.Sprintf(`
+`,
+ setting.StaticURLPrefix+"/assets",
+ getRenderURL(ctx),
))
return err
}
@@ -281,11 +299,13 @@ func (err ErrUnsupportedRenderExtension) Error() string {
func renderFile(ctx *RenderContext, input io.Reader, output io.Writer) error {
extension := strings.ToLower(filepath.Ext(ctx.RelativePath))
if renderer, ok := extRenderers[extension]; ok {
- if r, ok := renderer.(ExternalRenderer); ok && r.DisplayInIFrame() {
- if !ctx.InStandalonePage {
- // for an external render, it could only output its content in a standalone page
- // otherwise, a