SVGHMI: Implemented "Add Font" and "Remove Font", add font embedding in CSS at build time, tested ok with some OTF for now. svghmi
authorEdouard Tisserant
Tue, 30 Mar 2021 14:54:43 +0200
branchsvghmi
changeset 3211 938b55abe946
parent 3210 0ddefd20ca2b
child 3212 2b5b3f4f26f0
SVGHMI: Implemented "Add Font" and "Remove Font", add font embedding in CSS at build time, tested ok with some OTF for now.
svghmi/fonts.py
svghmi/gen_index_xhtml.xslt
svghmi/gen_index_xhtml.ysl2
svghmi/svghmi.py
--- a/svghmi/fonts.py	Tue Mar 30 10:05:55 2021 +0200
+++ b/svghmi/fonts.py	Tue Mar 30 14:54:43 2021 +0200
@@ -17,14 +17,17 @@
     """
 
     familyname = None
+    uniquename = None
     formatname = None
     mimetype = None
 
     font = ttLib.TTFont(filename)
     # https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html
     for name in font["name"].names:
-        if name.nameID==1 and name.platformID in [0,3]:
+        if name.nameID in [1,16] and name.platformID==3 and name.langID==1033:
             familyname = name.toUnicode()
+        if name.nameID==4 and name.platformID==3 and name.langID==1033:
+            uniquename = name.toUnicode()
 
     if font.flavor :
         # woff and woff2
@@ -32,13 +35,13 @@
         mimetype = "font/" + formatname
     # conditions on sfntVersion was deduced from fontTools.ttLib.sfnt
     elif font.sfntVersion in ("\x00\x01\x00\x00", "true"):
-        formatname = "truetype" 
+        formatname = "truetype"
         mimetype = "font/ttf"
     elif font.sfntVersion == "OTTO":
         formatname = "opentype"
         mimetype = "font/otf"
 
-    return familyname,formatname,mimetype
+    return familyname,uniquename,formatname,mimetype
 
 def DataURIFromFile(filename, mimetype):
     with open(filename, "rb") as fp:
@@ -50,7 +53,7 @@
         b64encode(data).strip()])
 
 def GetCSSFontFaceFromFontFile(filename):
-    familyname, formatname, mimetype = GetFontTypeAndFamilyName(filename)
+    familyname, uniquename, formatname, mimetype = GetFontTypeAndFamilyName(filename)
     data_uri = DataURIFromFile(filename, mimetype)
     css_font_face = \
     """
--- a/svghmi/gen_index_xhtml.xslt	Tue Mar 30 10:05:55 2021 +0200
+++ b/svghmi/gen_index_xhtml.xslt	Tue Mar 30 14:54:43 2021 +0200
@@ -1,6 +1,6 @@
 <?xml version="1.0"?>
-<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:str="http://exslt.org/strings" xmlns:func="http://exslt.org/functions" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:debug="debug" xmlns:preamble="preamble" xmlns:declarations="declarations" xmlns:definitions="definitions" xmlns:epilogue="epilogue" xmlns:ns="beremiz" version="1.0" extension-element-prefixes="ns func exsl regexp str dyn" exclude-result-prefixes="ns func exsl regexp str dyn debug preamble epilogue declarations definitions">
-  <xsl:output cdata-section-elements="xhtml:script" method="xml"/>
+<xsl:stylesheet xmlns:ns="beremiz" xmlns:definitions="definitions" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:func="http://exslt.org/functions" xmlns:epilogue="epilogue" xmlns:preamble="preamble" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:svg="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:str="http://exslt.org/strings" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:exsl="http://exslt.org/common" xmlns:declarations="declarations" xmlns:debug="debug" exclude-result-prefixes="ns func exsl regexp str dyn debug preamble epilogue declarations definitions" extension-element-prefixes="ns func exsl regexp str dyn" version="1.0">
+  <xsl:output method="xml" cdata-section-elements="xhtml:script"/>
   <xsl:variable name="svg" select="/svg:svg"/>
   <xsl:variable name="hmi_elements" select="//svg:*[starts-with(@inkscape:label, 'HMI:')]"/>
   <xsl:variable name="hmitree" select="ns:GetHMITree()"/>
@@ -1713,7 +1713,7 @@
 </xsl:text>
   </xsl:template>
   <xsl:variable name="excluded_types" select="str:split('Page VarInit VarInitPersistent')"/>
-  <xsl:key name="TypesKey" match="widget" use="@type"/>
+  <xsl:key use="@type" name="TypesKey" match="widget"/>
   <declarations:hmi-classes/>
   <xsl:template match="declarations:hmi-classes">
     <xsl:text>
--- a/svghmi/gen_index_xhtml.ysl2	Tue Mar 30 10:05:55 2021 +0200
+++ b/svghmi/gen_index_xhtml.ysl2	Tue Mar 30 14:54:43 2021 +0200
@@ -69,7 +69,11 @@
         html xmlns="http://www.w3.org/1999/xhtml"
              xmlns:svg="http://www.w3.org/2000/svg"
              xmlns:xlink="http://www.w3.org/1999/xlink" {
-            head;
+            head {
+                style type="text/css" media="screen" {
+                    value "ns:GetFonts()";
+                }
+            }
             // prevents user selection by mouse click / touch and drag
             // prevents pinch zoom and other accidental panning panning with touch devices
             body style="margin:0;overflow:hidden;user-select:none;touch-action:none;" {
--- a/svghmi/svghmi.py	Tue Mar 30 10:05:55 2021 +0200
+++ b/svghmi/svghmi.py	Tue Mar 30 14:54:43 2021 +0200
@@ -313,7 +313,7 @@
         {
             "bitmap":    "AddFont",
             "name":    _("Add Font"),
-            "tooltip": _("Add TTF, OTH or WOFF font to be embedded in HMI"),
+            "tooltip": _("Add TTF, OTF or WOFF font to be embedded in HMI"),
             "method":   "_AddFont"
         },
         {
@@ -400,6 +400,18 @@
 
         return ret
 
+    def GetFonts(self, _context):
+        project_path = self.CTNPath()
+        fontdir = os.path.join(project_path, "fonts") 
+        css_parts = []
+
+        for f in sorted(os.listdir(fontdir)):
+            fontfile = os.path.join(fontdir,f)
+            if os.path.isfile(fontfile):
+                css_parts.append(GetCSSFontFaceFromFontFile(fontfile))
+
+        return "".join(css_parts)
+
     times_msgs = {}
     indent = 1
     def ProgressStart(self, k, m):
@@ -455,6 +467,7 @@
                               [("GetSVGGeometry", lambda *_ignored:self.GetSVGGeometry()),
                                ("GetHMITree", lambda *_ignored:self.GetHMITree()),
                                ("GetTranslations", self.GetTranslations),
+                               ("GetFonts", self.GetFonts),
                                ("ProgressStart", lambda _ign,k,m:self.ProgressStart(str(k),str(m))),
                                ("ProgressEnd", lambda _ign,k:self.ProgressEnd(str(k)))])
 
@@ -618,10 +631,62 @@
             self.GetCTRoot().logger.write_error(_("POT file does not exist, add translatable text (label starting with '_') in Inkscape first\n"))
 
     def _AddFont(self):
-        pass
+        dialog = wx.FileDialog(
+            self.GetCTRoot().AppFrame,
+            _("Choose a font"),
+            os.path.expanduser("~"),
+            "",
+            _("Font files (*.ttf;*.otf;*.woff;*.woff2)|*.ttf;*.otf;*.woff;*.woff2"), wx.OPEN)
+
+        if dialog.ShowModal() == wx.ID_OK:
+            fontfile = dialog.GetPath()
+            if os.path.isfile(fontfile):
+                familyname, uniquename, formatname, mimetype = GetFontTypeAndFamilyName(fontfile)
+            else:
+                self.GetCTRoot().logger.write_error(
+                    _('Selected font %s is not a readable file\n')%fontfile)
+                return
+            if familyname is None or uniquename is None or formatname is None or mimetype is None:
+                self.GetCTRoot().logger.write_error(
+                    _('Selected font file %s is invalid or incompatible\n')%fontfile)
+                return
+
+            project_path = self.CTNPath()
+
+            fontfname = uniquename + "." + mimetype.split('/')[1]
+            fontdir = os.path.join(project_path, "fonts") 
+            newfontfile = os.path.join(fontdir, fontfname) 
+
+            if not os.path.exists(fontdir):
+                os.mkdir(fontdir)
+
+            shutil.copyfile(fontfile, newfontfile)
+
+            self.GetCTRoot().logger.write(
+                _('Added font %s as %s\n')%(fontfile,newfontfile))
 
     def _DelFont(self):
-        pass
+        project_path = self.CTNPath()
+        fontdir = os.path.join(project_path, "fonts") 
+        dialog = wx.FileDialog(
+            self.GetCTRoot().AppFrame,
+            _("Choose a font to remove"),
+            fontdir,
+            "",
+            _("Font files (*.ttf;*.otf;*.woff;*.woff2)|*.ttf;*.otf;*.woff;*.woff2"), wx.OPEN)
+        if dialog.ShowModal() == wx.ID_OK:
+            fontfile = dialog.GetPath()
+            if os.path.isfile(fontfile):
+                if os.path.relpath(fontfile, fontdir) == os.path.basename(fontfile):
+                    os.remove(fontfile) 
+                    self.GetCTRoot().logger.write(
+                        _('Removed font %s\n')%fontfile)
+                else:
+                    self.GetCTRoot().logger.write_error(
+                        _("Font to remove %s is not in %s\n") % (fontfile,fontdir))
+            else:
+                self.GetCTRoot().logger.write_error(
+                    _("Font file does not exist: %s\n") % fontfile)
         
     def CTNGlobalInstances(self):
         # view_name = self.BaseParams.getName()