diff --git a/src/main/java/org/olat/core/gui/render/StringOutput.java b/src/main/java/org/olat/core/gui/render/StringOutput.java
index 3eeaa4a2f77d3d31840aecc00bcba41fab0dc738..b2f2ec1fbc17de63801a8721238126dbd7303f1b 100644
--- a/src/main/java/org/olat/core/gui/render/StringOutput.java
+++ b/src/main/java/org/olat/core/gui/render/StringOutput.java
@@ -190,6 +190,11 @@ public class StringOutput extends Writer {
 		return this;
 	}
 	
+	public StringOutput insert(int offset, String str) {
+		sb.insert(offset, str);
+		return this;
+	}
+	
 	/**
 	 * Generate the following html code: onclick="call" onkeyup="if(event.which == 13 || event.keyCode){ call }"
 	 * @param call The JavaScript method to envelop
@@ -223,12 +228,20 @@ public class StringOutput extends Writer {
 		return sb.indexOf(str) >= 0;
 	}
 	
+	public int indexOf(String str) {
+		return sb.indexOf(str);
+	}
+	
+	public StringBuilder getBuffer() {
+		return sb;
+	}
+	
 	public Reader getReader() {
 		return new StringOutputReader();
 	}
 	
 	@Override
-	public void flush() throws IOException {
+	public void flush() {
 		//
 	}
 
@@ -237,9 +250,6 @@ public class StringOutput extends Writer {
 		//
 	}
 
-	/**
-	 * @see java.lang.Object#toString()
-	 */
 	@Override
 	public String toString() {
 		return sb.toString();
@@ -247,109 +257,118 @@ public class StringOutput extends Writer {
 	
 	private class StringOutputReader extends Reader {
 		
-    private int length;
-    private int next = 0;
-    private int mark = 0;
-    /**
-     * Creates a new string reader.
-     *
-     * @param s  String providing the character stream.
-     */
-    public StringOutputReader() {
-    	this.length = sb.length();
-    }
+		private int length;
+		private int next = 0;
+		private int mark = 0;
+		
+		/**
+		 * Creates a new string reader.
+		 *
+		 * @param s  String providing the character stream.
+		 */
+		public StringOutputReader() {
+			this.length = sb.length();
+		}
 
-    /**
-     * Reads a single character.
-     *
-     * @return     The character read, or -1 if the end of the stream has been
-     *             reached
-     *
-     * @exception  IOException  If an I/O error occurs
-     */
-    public int read() throws IOException {
-    	synchronized (lock) {
-		    if (next >= length)
-		    	return -1;
-		    
-		    char[] dst = new char[1];
-		    sb.getChars(next++, next, dst, 0);
-		    return dst[0];
-    	}
-    }
+		/**
+		 * Reads a single character.
+		 *
+		 * @return     The character read, or -1 if the end of the stream has been
+		 *             reached
+		 *
+		 * @exception  IOException  If an I/O error occurs
+		 */
+		@Override
+		public int read() throws IOException {
+			synchronized (lock) {
+			    if (next >= length)
+			    	return -1;
+			    
+			    char[] dst = new char[1];
+			    sb.getChars(next++, next, dst, 0);
+			    return dst[0];
+			}
+		}
 
-    public int read(char cbuf[], int off, int len) throws IOException {
-    	synchronized (lock) {
-        if ((off < 0) || (off > cbuf.length) || (len < 0) ||
-            ((off + len) > cbuf.length) || ((off + len) < 0)) {
-        	throw new IndexOutOfBoundsException();
-        } else if (len == 0) {
-        	return 0;
-        }
-		    if (next >= length) return -1;
-		    
-		    int n = Math.min(length - next, len);
-		    sb.getChars(next, next + n, cbuf, off);
-		    next += n;
-		    return n;
-    	}
-    }
+		@Override
+		public int read(char[] cbuf, int off, int len) throws IOException {
+			synchronized (lock) {
+		    if ((off < 0) || (off > cbuf.length) || (len < 0) ||
+		        ((off + len) > cbuf.length) || ((off + len) < 0)) {
+		    	throw new IndexOutOfBoundsException();
+		    } else if (len == 0) {
+		    	return 0;
+		    }
+			    if (next >= length) return -1;
+			    
+			    int n = Math.min(length - next, len);
+			    sb.getChars(next, next + n, cbuf, off);
+			    next += n;
+			    return n;
+			}
+		}
 
-    /**
-     * Skips the specified number of characters in the stream. Returns
-     * the number of characters that were skipped.
-     *
-     * <p>The <code>ns</code> parameter may be negative, even though the
-     * <code>skip</code> method of the {@link Reader} superclass throws
-     * an exception in this case. Negative values of <code>ns</code> cause the
-     * stream to skip backwards. Negative return values indicate a skip
-     * backwards. It is not possible to skip backwards past the beginning of
-     * the string.
-     *
-     * <p>If the entire string has been read or skipped, then this method has
-     * no effect and always returns 0.
-     *
-     * @exception  IOException  If an I/O error occurs
-     */
-    public long skip(long ns) throws IOException {
-    	synchronized (lock) {
-        if (next >= length)
-            return 0;
-        // Bound skip by beginning and end of the source
-        long n = Math.min(length - next, ns);
-        n = Math.max(-next, n);
-        next += n;
-        return n;
-       }
-    }
+		/**
+		 * Skips the specified number of characters in the stream. Returns
+		 * the number of characters that were skipped.
+		 *
+		 * <p>The <code>ns</code> parameter may be negative, even though the
+		 * <code>skip</code> method of the {@link Reader} superclass throws
+		 * an exception in this case. Negative values of <code>ns</code> cause the
+		 * stream to skip backwards. Negative return values indicate a skip
+		 * backwards. It is not possible to skip backwards past the beginning of
+		 * the string.
+		 *
+		 * <p>If the entire string has been read or skipped, then this method has
+		 * no effect and always returns 0.
+		 *
+		 * @exception  IOException  If an I/O error occurs
+		 */
+		@Override
+		public long skip(long ns) throws IOException {
+			synchronized (lock) {
+				if (next >= length)
+					return 0;
+				// Bound skip by beginning and end of the source
+				long n = Math.min(length - next, ns);
+				n = Math.max(-next, n);
+				next += n;
+				return n;
+			}
+		}
 
-    public boolean ready() throws IOException {
-    	synchronized (lock) {
-        return true;
-      }
-    }
+		@Override
+		public boolean ready() throws IOException {
+			synchronized (lock) {
+				return true;
+			}
+		}
 
-    public boolean markSupported() {
-    	return true;
-    }
+		@Override
+		public boolean markSupported() {
+			return true;
+		}
 
-    public void mark(int readAheadLimit) throws IOException {
-    	if (readAheadLimit < 0) {
-    		throw new IllegalArgumentException("Read-ahead limit < 0");
-    	}
-    	synchronized (lock) {
-    		mark = next;
-    	}
-    }
+		@Override
+		public void mark(int readAheadLimit) throws IOException {
+			if (readAheadLimit < 0) {
+				throw new IllegalArgumentException("Read-ahead limit < 0");
+			}
+			synchronized (lock) {
+				mark = next;
+			}
+		}
 
-    public void reset() throws IOException {
-    	synchronized (lock) {
-    		next = mark;
-    	}
-    }
+		@Override
+		public void reset() throws IOException {
+			synchronized (lock) {
+				next = mark;
+			}
+		}
 
-    public void close() {
-    	//
-    }
+    	@Override
+	    public void close() throws IOException {
+	    	//
+	    }
 	}
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/certificate/manager/CertificatePdfServiceWorker.java b/src/main/java/org/olat/course/certificate/manager/CertificatePdfServiceWorker.java
index 031d6644583a04932d115fa9d8f270ae84cc041f..a7a46e7e05049f8fdb5fa1843cf5f13332122d39 100644
--- a/src/main/java/org/olat/course/certificate/manager/CertificatePdfServiceWorker.java
+++ b/src/main/java/org/olat/course/certificate/manager/CertificatePdfServiceWorker.java
@@ -23,11 +23,13 @@ import java.io.File;
 import java.io.FileOutputStream;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.Reader;
 import java.io.Writer;
 import java.nio.charset.Charset;
 import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
 import java.util.Date;
 import java.util.List;
 import java.util.Locale;
@@ -35,6 +37,7 @@ import java.util.UUID;
 
 import org.apache.velocity.VelocityContext;
 import org.olat.core.commons.services.pdf.PdfService;
+import org.olat.core.gui.render.StringOutput;
 import org.olat.core.id.Identity;
 import org.olat.core.id.User;
 import org.olat.core.id.UserConstants;
@@ -45,6 +48,7 @@ import org.olat.core.util.StringHelper;
 import org.olat.core.util.i18n.I18nManager;
 import org.olat.course.assessment.AssessmentHelper;
 import org.olat.course.certificate.CertificateTemplate;
+import org.olat.course.certificate.CertificatesManager;
 import org.olat.repository.RepositoryEntry;
 import org.olat.user.UserManager;
 import org.olat.user.propertyhandlers.UserPropertyHandler;
@@ -105,6 +109,14 @@ public class CertificatePdfServiceWorker {
 		File certificateFile = new File(destinationDir, filename);
 		File templateFile = certificatesManager.getTemplateFile(template);
 		File htmlCertificateFile = copyAndEnrichTemplate(templateFile);
+		File qrCodeScriptFile = new File(htmlCertificateFile.getParent(), "qrcode.min.js");
+		if(!qrCodeScriptFile.exists()) {
+			try(InputStream inQRCodeLib = CertificatesManager.class.getResourceAsStream("qrcode.min.js")) {
+				Files.copy(inQRCodeLib, qrCodeScriptFile.toPath(), StandardCopyOption.REPLACE_EXISTING);	
+			} catch(Exception e) {
+				log.error("Can not read qrcode.min.js for QR Code PDF generation", e);
+			}
+		}
 
 		try(OutputStream out = new FileOutputStream(certificateFile)) {
 			pdfService.convert(htmlCertificateFile.getParentFile(), htmlCertificateFile.getName(), out);
@@ -122,19 +134,67 @@ public class CertificatePdfServiceWorker {
 	}
 	
 	private File copyAndEnrichTemplate(File templateFile) {
-		VelocityContext context = getContext();
 		boolean result = false;
 		File htmlCertificate = new File(templateFile.getParent(), "c" + UUID.randomUUID() + ".html");
+		
 		try(Reader in = Files.newBufferedReader(templateFile.toPath(), Charset.forName("UTF-8"));
-			Writer output = new FileWriter(htmlCertificate)) {
-			result = certificatesManager.getVelocityEngine().evaluate(context, output, "mailTemplate", in);
+				StringOutput content = new StringOutput(32000);
+				Writer output = new FileWriter(htmlCertificate)) {
+			VelocityContext context = getContext();
+			result = certificatesManager.getVelocityEngine().evaluate(context, content, "mailTemplate", in);
+			content.flush();
+			
+			if(hasQRCode(content)) {
+				injectQRCodeScript(content);
+			}
+			
+			output.write(content.toString());
 			output.flush();
+			result = true;
 		} catch(Exception e) {
 			log.error("", e);
 		}
 		return result ? htmlCertificate : null;
 	}
 	
+	private boolean hasQRCode(StringOutput content) {
+		return content.contains("o_qrcode");
+	}
+	
+	private void injectQRCodeScript(StringOutput content) {
+		int injectionIndex = injectionPoint(content);
+		
+		StringBuilder qr = new StringBuilder(512);
+		qr.append("<script src='qrcode.min.js'></script>\n")
+		  .append("<script>\n")
+		  .append("/* <![CDATA[ */ \n")
+		  .append("document.addEventListener('load', new function() {\n")
+		  .append("  var qrcodes = document.querySelectorAll('.o_qrcode');\n")
+		  .append("  for (var i=0; i<qrcodes.length; i++) {\n")
+		  .append("    var qrcode = qrcodes[i];\n")
+		  .append("    var val = qrcode.textContent;\n")
+		  .append("    while (qrcode.firstChild) {\n")
+		  .append("      qrcode.removeChild(qrcode.firstChild);\n")
+		  .append("    }\n")
+		  .append("    new QRCode(qrcode, val);\n")
+		  .append("  }\n")
+		  .append("});\n")
+		  .append("/* ]]> */\n")
+		  .append("</script>");
+		content.insert(injectionIndex, qr.toString());	
+	}
+	
+	private int injectionPoint(StringOutput content) {
+		String[] anchors = new String[] { "</body", "</ body", "</BODY", "</ BODY", "</html", "</HTML" };
+		for(String anchor:anchors) {
+			int bodyIndex = content.indexOf(anchor);
+			if(bodyIndex > 0) {
+				return bodyIndex;
+			}
+		}
+		return content.length();// last hope
+	}
+	
 	private VelocityContext getContext() {
 		VelocityContext context = new VelocityContext();
 		fillUserProperties(context);
diff --git a/src/main/java/org/olat/course/config/ui/CourseOptionsController.java b/src/main/java/org/olat/course/config/ui/CourseOptionsController.java
index 088808af7330092925ec15699bd68a1f1ecfa72c..49ad41aab18e35789a4b6aa5c91bd1494e12e474 100644
--- a/src/main/java/org/olat/course/config/ui/CourseOptionsController.java
+++ b/src/main/java/org/olat/course/config/ui/CourseOptionsController.java
@@ -210,7 +210,8 @@ public class CourseOptionsController extends FormBasicController {
 
 		glossaryNameEl = uifactory.addStaticTextElement("glossaryName", "glossary.isconfigured",
 				translate("glossary.no.glossary"), glossaryCont);
-
+		glossaryNameEl.setExampleKey("chkbx.glossary.inverse.explain", null);
+		
 		boolean managedGlossary = RepositoryEntryManagedFlag.isManaged(entry, RepositoryEntryManagedFlag.glossary);
 		FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
 		glossaryCont.add(buttonsCont);
diff --git a/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_de.properties
index 5d030748a22033c592d91e0130a50a52f9370a41..7dd27afe7ea0d2af0216305be13ad61c6a539a56 100644
--- a/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_de.properties
@@ -2,7 +2,8 @@
 chkbx.calendar.onoff=Kurskalender
 chkbx.chat.onoff=Kurs-Chat
 chkbx.efficency.onoff=Leistungsnachweis verwenden
-chkbx.glossary.explain=Das Glossary muss unter "Optionen" konfiguriert werden.
+chkbx.glossary.explain=Das Glossar muss unter "Optionen" konfiguriert werden.
+chkbx.glossary.inverse.explain=Glossar Menu in Toolbar muss unter "Toolbar" konfiguriert werden.
 chkbx.glossary.onoff=Glossar
 chkbx.search.onoff=Kurssuche
 chkbx.menu.onoff=Menu sichtbar f\u00FCr Teilnehmer und Betreuer
diff --git a/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_en.properties
index 4c11406b6a14aa9c799e049bed2e164e129f288e..f8f93a81c3576a204c245789ad5468abd66777fc 100644
--- a/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_en.properties
@@ -4,6 +4,7 @@ chkbx.chat.onoff=Use course chat
 chkbx.search.onoff=Enable course search
 chkbx.efficency.onoff=Use evidence of achievement
 chkbx.glossary.explain=The glossary need to be configured under "Options".
+chkbx.glossary.inverse.explain=Glossary menu in toolbar is configured under "Toolbar".
 chkbx.glossary.onoff=Glossary
 chkbx.menu.onoff=Menu visible for participants and coaches
 chkbx.toolbar.explain=Activate tools in toolbar:
diff --git a/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_fr.properties
index 4ab45c70a80b3602a8f9c78cbb31df0f69660a80..a813f3d55800f766cbfb10581d0db2abb8be7332 100644
--- a/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_fr.properties
+++ b/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_fr.properties
@@ -4,6 +4,7 @@ chkbx.calendar.onoff=Calendrier du cours
 chkbx.chat.onoff=Chat du cours
 chkbx.efficency.onoff=Utiliser l'attestation de performance
 chkbx.glossary.explain=Le glossaire doit \u00EAtre configurer sous "Options".
+chkbx.glossary.inverse.explain=Le menu du glossaire dans la barre d'outils est à configurer sous "Barre d'outils".
 chkbx.glossary.onoff=Glossaire
 chkbx.menu.onoff=Menu visible pour les participants et les coaches
 chkbx.search.onoff=Recherche au sein du cours
diff --git a/src/main/java/org/olat/course/run/CourseRuntimeController.java b/src/main/java/org/olat/course/run/CourseRuntimeController.java
index 42900b3b909ab7a7e26e91551c6363fde959359e..5c2e0d613820293594159584daab6adbf625516c 100644
--- a/src/main/java/org/olat/course/run/CourseRuntimeController.java
+++ b/src/main/java/org/olat/course/run/CourseRuntimeController.java
@@ -1771,7 +1771,7 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 				if(glossary != null) {
 					ICourse course = CourseFactory.loadCourse(getRepositoryEntry());
 					CourseConfig cc = course.getCourseEnvironment().getCourseConfig();
-					glossary.setVisible(cc.hasGlossary());
+					glossary.setVisible(cc.hasGlossary() && cc.isGlossaryEnabled());
 					toolbarPanel.setDirty(true);
 				}
 				break;