From b6e0a571d5e34ca194f54f3bfa6d6fbd1e857dc4 Mon Sep 17 00:00:00 2001
From: Gustavo Trott <gustavo@trott.com.br>
Date: Thu, 22 Apr 2021 21:57:55 -0300
Subject: [PATCH] Implements forceRasterizeSlides (force convert presentation
 to png before svg) and pngWidthRasterizedSlides (force png width (px))

---
 .../presentation/imp/SvgImageCreatorImp.java  | 101 +++++++++++-------
 .../grails-app/conf/bigbluebutton.properties  |  17 +++
 .../grails-app/conf/spring/doc-conversion.xml |   2 +
 3 files changed, 83 insertions(+), 37 deletions(-)

diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/SvgImageCreatorImp.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/SvgImageCreatorImp.java
index 01659895df..50e654e902 100755
--- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/SvgImageCreatorImp.java
+++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/SvgImageCreatorImp.java
@@ -31,6 +31,8 @@ public class SvgImageCreatorImp implements SvgImageCreator {
     private long pathsThreshold;
     private int convPdfToSvgTimeout = 60;
     private int svgResolutionPpi = 300;
+    private boolean forceRasterizeSlides = false;
+    private int pngWidthRasterizedSlides = 2048;
 	private String BLANK_SVG;
 
     @Override
@@ -93,30 +95,37 @@ public class SvgImageCreatorImp implements SvgImageCreator {
 
         File destsvg = new File(imagePresentationDir.getAbsolutePath() + File.separatorChar + "slide" + page + ".svg");
 
-        NuProcessBuilder convertPdfToSvg = createConversionProcess("-svg", page, source, destsvg.getAbsolutePath(),
+        SvgConversionHandler pHandler = new SvgConversionHandler();
+
+        if(this.forceRasterizeSlides == false) {
+            NuProcessBuilder convertPdfToSvg = createConversionProcess("-svg", page, source, destsvg.getAbsolutePath(),
                     true);
 
-        SvgConversionHandler pHandler = new SvgConversionHandler();
-        convertPdfToSvg.setProcessListener(pHandler);
+            convertPdfToSvg.setProcessListener(pHandler);
 
-        NuProcess process = convertPdfToSvg.start();
-        try {
-            process.waitFor(convPdfToSvgTimeout + 1, TimeUnit.SECONDS);
-            done = true;
-        } catch (InterruptedException e) {
-            log.error("Interrupted Exception while generating SVG slides {}", pres.getName(), e);
-        }
+            NuProcess process = convertPdfToSvg.start();
+            try {
+                process.waitFor(convPdfToSvgTimeout + 1, TimeUnit.SECONDS);
+                done = true;
+            } catch (InterruptedException e) {
+                log.error("Interrupted Exception while generating SVG slides {}", pres.getName(), e);
+            }
 
-        if(pHandler.isCommandTimeout()) {
-            log.error("Command execution (convertPdfToSvg) exceeded the {} secs timeout for {} page {}.", convPdfToSvgTimeout, pres.getName(), page);
-        }
+            if(pHandler.isCommandTimeout()) {
+                log.error("Command execution (convertPdfToSvg) exceeded the {} secs timeout for {} page {}.", convPdfToSvgTimeout, pres.getName(), page);
+            }
 
-        if (!done) {
-            return done;
+            if (!done) {
+                return done;
+            }
         }
 
-        if (destsvg.length() == 0 || pHandler.numberOfImageTags() > imageTagThreshold
-                || pHandler.numberOfPaths() > pathsThreshold) {
+
+        if (destsvg.length() == 0 ||
+                pHandler.numberOfImageTags() > imageTagThreshold ||
+                pHandler.numberOfPaths() > pathsThreshold ||
+                this.forceRasterizeSlides) {
+
             // We need t delete the destination file as we are starting a
             // new conversion process
             if (destsvg.exists()) {
@@ -125,21 +134,24 @@ public class SvgImageCreatorImp implements SvgImageCreator {
 
             done = false;
 
-            Map<String, Object> logData = new HashMap<String, Object>();
-            logData.put("meetingId", pres.getMeetingId());
-            logData.put("presId", pres.getId());
-            logData.put("filename", pres.getName());
-            logData.put("page", page);
-            logData.put("convertSuccess", pHandler.isCommandSuccessful());
-            logData.put("fileExists", destsvg.exists());
-            logData.put("numberOfImages", pHandler.numberOfImageTags());
-            logData.put("numberOfPaths", pHandler.numberOfPaths());
-            logData.put("logCode", "potential_problem_with_svg");
-            logData.put("message", "Potential problem with generated SVG");
-            Gson gson = new Gson();
-            String logStr = gson.toJson(logData);
-
-            log.warn(" --analytics-- data={}", logStr);
+            if(!this.forceRasterizeSlides) {
+                Map<String, Object> logData = new HashMap<String, Object>();
+                logData.put("meetingId", pres.getMeetingId());
+                logData.put("presId", pres.getId());
+                logData.put("filename", pres.getName());
+                logData.put("page", page);
+                logData.put("convertSuccess", pHandler.isCommandSuccessful());
+                logData.put("fileExists", destsvg.exists());
+                logData.put("numberOfImages", pHandler.numberOfImageTags());
+                logData.put("numberOfPaths", pHandler.numberOfPaths());
+                logData.put("logCode", "potential_problem_with_svg");
+                logData.put("message", "Potential problem with generated SVG");
+                Gson gson = new Gson();
+                String logStr = gson.toJson(logData);
+
+                log.warn(" --analytics-- data={}", logStr);
+            }
+
 
             File tempPng = null;
             String basePresentationame = UUID.randomUUID().toString();
@@ -148,14 +160,15 @@ public class SvgImageCreatorImp implements SvgImageCreator {
             } catch (IOException ioException) {
                 // We should never fall into this if the server is correctly
                 // configured
+                Map<String, Object> logData = new HashMap<String, Object>();
                 logData = new HashMap<String, Object>();
                 logData.put("meetingId", pres.getMeetingId());
                 logData.put("presId", pres.getId());
                 logData.put("filename", pres.getName());
                 logData.put("logCode", "problem_with_creating_svg");
                 logData.put("message", "Unable to create temporary files");
-                gson = new Gson();
-                logStr = gson.toJson(logData);
+                Gson gson = new Gson();
+                String logStr = gson.toJson(logData);
                 log.error(" --analytics-- data={}", logStr, ioException);
             }
 
@@ -178,7 +191,6 @@ public class SvgImageCreatorImp implements SvgImageCreator {
 
             if(tempPng.length() > 0) {
                 // Step 2: Convert a PNG image to SVG
-
                 NuProcessBuilder convertPngToSvg = new NuProcessBuilder(Arrays.asList("timeout", convPdfToSvgTimeout + "s", "convert",
                             tempPng.getAbsolutePath(), destsvg.getAbsolutePath()));
 
@@ -254,12 +266,19 @@ public class SvgImageCreatorImp implements SvgImageCreator {
 
     private NuProcessBuilder createConversionProcess(String format, int page, String source, String destFile,
             boolean analyze) {
-        String rawCommand = "pdftocairo -r " + this.svgResolutionPpi + " " + format + (analyze ? "" : " -singlefile")
-                + " -q -f " + String.valueOf(page) + " -l " + String.valueOf(page) + " " + source + " " + destFile;
+        String rawCommand = "pdftocairo -r " + this.svgResolutionPpi + " " + format + (analyze ? "" : " -singlefile");
+
+        //Resize png resolution to avoid too large files
+        if(format.equals("-png") && this.pngWidthRasterizedSlides != 0) {
+            rawCommand += " -scale-to-x " + this.pngWidthRasterizedSlides + " -scale-to-y -1 ";
+        }
+
+        rawCommand  += " -q -f " + String.valueOf(page) + " -l " + String.valueOf(page) + " " + source + " " + destFile;
         if (analyze) {
             rawCommand += " && cat " + destFile;
             rawCommand += " | egrep 'data:image/png;base64|<path' | sed 's/  / /g' | cut -d' ' -f 1 | sort | uniq -cw 2";
         }
+
         return new NuProcessBuilder(Arrays.asList("timeout", convPdfToSvgTimeout + "s", "/bin/sh", "-c", rawCommand));
     }
 
@@ -314,4 +333,12 @@ public class SvgImageCreatorImp implements SvgImageCreator {
     public void setSvgResolutionPpi(int svgResolutionPpi) {
         this.svgResolutionPpi = svgResolutionPpi;
     }
+
+    public void setForceRasterizeSlides(boolean forceRasterizeSlides) {
+        this.forceRasterizeSlides = forceRasterizeSlides;
+    }
+
+    public void setPngWidthRasterizedSlides(int pngWidthRasterizedSlides) {
+        this.pngWidthRasterizedSlides = pngWidthRasterizedSlides;
+    }
 }
diff --git a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties
index d0a309d8ae..2a12b773a6 100755
--- a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties
+++ b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties
@@ -108,6 +108,23 @@ svgConversionTimeout=60
 #------------------------------------
 svgPresentationResolutionPpi=300
 
+#------------------------------------
+# Force conversion of slides to PNG before converting to SVG
+## This will solve problems like reported in issue #8835
+## Disabled by default as it can affect the quality in zoom
+#------------------------------------
+forceRasterizeSlides=false
+
+#------------------------------------
+# Presentation will be resized to this width (in pixels) when rasterizing (converting to PNG)
+## Applied in these situations:
+##  a) the source can't be converted directly to SVG ;
+##  b) option "forceRasterizeSlides" is defined as true ;
+## To disable this constraint (and keep source resolution) define this property as 0.
+#------------------------------------
+pngWidthRasterizedSlides=2048
+
+
 #------------------------------------
 # Timeout(secs) to wait for conversion script execution
 #------------------------------------
diff --git a/bigbluebutton-web/grails-app/conf/spring/doc-conversion.xml b/bigbluebutton-web/grails-app/conf/spring/doc-conversion.xml
index 1120fe7dbb..7f4f9da4cb 100755
--- a/bigbluebutton-web/grails-app/conf/spring/doc-conversion.xml
+++ b/bigbluebutton-web/grails-app/conf/spring/doc-conversion.xml
@@ -88,6 +88,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
         <property name="blankSvg" value="${BLANK_SVG}"/>
         <property name="convPdfToSvgTimeout" value="${svgConversionTimeout}"/>
         <property name="svgResolutionPpi" value="${svgPresentationResolutionPpi}"/>
+        <property name="forceRasterizeSlides" value="${forceRasterizeSlides}"/>
+        <property name="pngWidthRasterizedSlides" value="${pngWidthRasterizedSlides}"/>
     </bean>
 
     <bean id="generatedSlidesInfoHelper" class="org.bigbluebutton.presentation.GeneratedSlidesInfoHelperImp"/>
-- 
GitLab