]> git.lyx.org Git - lyx.git/blobdiff - lib/lyx2lyx/lyx_2_4.py
lyx2lyx: correct placement of (new) local layout
[lyx.git] / lib / lyx2lyx / lyx_2_4.py
index 64106cc7f8591b71c11c66fc8e8f6f0deb2934b5..8436d9a84c1e57e3d5de5814b4c4a94f68101405 100644 (file)
@@ -72,6 +72,8 @@ class fontinfo:
         self.package = None
         self.options = []
         self.pkgkey = None      # key into pkg2fontmap
+        self.osfopt = None      # None, string
+        self.osfdef = "false"   # "false" or "true"
 
     def addkey(self):
         self.pkgkey = createkey(self.package, self.options)
@@ -82,7 +84,7 @@ class fontmapping:
         self.pkg2fontmap = dict()
         self.pkginmap = dict()  # defines, if a map for package exists
 
-    def expandFontMapping(self, font_list, font_type, scale_type, pkg, scaleopt = None):
+    def expandFontMapping(self, font_list, font_type, scale_type, pkg, scaleopt = None, osfopt = None, osfdef = "false"):
         " Expand fontinfo mapping"
         #
         # fontlist:    list of fontnames, each element
@@ -93,6 +95,8 @@ class fontmapping:
         # pkg:         package defining the font. Defaults to fontname if None
         # scaleopt:    one of None, 'scale', 'scaled', or some other string
         #              to be used in scale option (e.g. scaled=0.7)
+        # osfopt:      None or some other string to be used in osf option
+        # osfdef:      "true" if osf is default
         for fl in font_list:
             fe = fontinfo()
             fe.fonttype = font_type
@@ -102,6 +106,8 @@ class fontmapping:
             fe.fontname = font_name
             fe.options = flt[1:]
             fe.scaleopt = scaleopt
+            fe.osfopt = osfopt
+            fe.osfdef = osfdef
             if pkg == None:
                 fe.package = font_name
             else:
@@ -157,27 +163,48 @@ def createFontMapping(fontlist):
                                   'IBMPlexMonoSemibold,semibold'],
                                  "typewriter", "tt", "plex-mono", "scale")
         elif font == 'Adobe':
-            fm.expandFontMapping(['ADOBESourceSerifPro'], "roman", None, "sourceserifpro")
-            fm.expandFontMapping(['ADOBESourceSansPro'], "sans", "sf", "sourcesanspro", "scaled")
-            fm.expandFontMapping(['ADOBESourceCodePro'], "typewriter", "tt", "sourcecodepro", "scaled")
+            fm.expandFontMapping(['ADOBESourceSerifPro'], "roman", None, "sourceserifpro", None, "osf")
+            fm.expandFontMapping(['ADOBESourceSansPro'], "sans", "sf", "sourcesanspro", "scaled", "osf")
+            fm.expandFontMapping(['ADOBESourceCodePro'], "typewriter", "tt", "sourcecodepro", "scaled", "osf")
         elif font == 'Noto':
             fm.expandFontMapping(['NotoSerifRegular,regular', 'NotoSerifMedium,medium',
                                   'NotoSerifThin,thin', 'NotoSerifLight,light',
                                   'NotoSerifExtralight,extralight'],
-                                  "roman", None, "noto-serif")
+                                  "roman", None, "noto-serif", None, "osf")
             fm.expandFontMapping(['NotoSansRegular,regular', 'NotoSansMedium,medium',
                                   'NotoSansThin,thin', 'NotoSansLight,light',
                                   'NotoSansExtralight,extralight'],
                                   "sans", "sf", "noto-sans", "scaled")
-            fm.expandFontMapping(['NotoMonoRegular'], "typewriter", "tt", "noto-mono", "scaled")
+            fm.expandFontMapping(['NotoMonoRegular,regular'], "typewriter", "tt", "noto-mono", "scaled")
+        elif font == 'Cantarell':
+            fm.expandFontMapping(['cantarell,defaultsans'],
+                                  "sans", "sf", "cantarell", "scaled", "oldstyle")
+        elif font == 'Chivo':
+            fm.expandFontMapping(['ChivoThin,thin', 'ChivoLight,light',
+                                  'Chivo,regular', 'ChivoMedium,medium'],
+                                  "sans", "sf", "Chivo", "scale", "oldstyle")
+        elif font == 'CrimsonPro':
+            fm.expandFontMapping(['CrimsonPro', 'CrimsonProExtraLight,extralight', 'CrimsonProLight,light',
+                                  'CrimsonProMedium,medium'],
+                                  "roman", None, "CrimsonPro", None, "lf", "true")
+        elif font == 'Fira':
+            fm.expandFontMapping(['FiraSans', 'FiraSansBook,book',
+                                  'FiraSansThin,thin', 'FiraSansLight,light',
+                                  'FiraSansExtralight,extralight',
+                                  'FiraSansUltralight,ultralight'],
+                                  "sans", "sf", "FiraSans", "scaled", "lf", "true")
+            fm.expandFontMapping(['FiraMono'], "typewriter", "tt", "FiraMono", "scaled", "lf", "true")
     return fm
 
-def convert_fonts(document, fm):
+def convert_fonts(document, fm, osfoption = "osf"):
     " Handle font definition (LaTeX preamble -> native) "
 
     rpkg = re.compile(r'^\\usepackage(\[([^\]]*)\])?\{([^\}]+)\}')
     rscaleopt = re.compile(r'^scaled?=(.*)')
 
+    # Check whether we go beyond font option feature introduction
+    haveFontOpts = document.end_format > 580
+
     i = 0
     while i < len(document.preamble):
         i = find_re(document.preamble, rpkg, i+1)
@@ -191,19 +218,40 @@ def convert_fonts(document, fm):
         pkg = mo.group(3)
         o = 0
         oscale = 1
+        has_osf = False
         while o < len(options):
+            if options[o] == osfoption:
+                has_osf = True
+                del options[o]
+                continue
             mo = rscaleopt.search(options[o])
             if mo == None:
                 o += 1
                 continue
             oscale = mo.group(1)
             del options[o]
-            break
+            continue
 
         if not pkg in fm.pkginmap:
             continue
         # determine fontname
-        fn = fm.getfontname(pkg, options)
+        fn = None
+        if haveFontOpts:
+            # Try with name-option combination first
+            # (only one default option supported currently)
+            o = 0
+            while o < len(options):
+                opt = options[o]
+                fn = fm.getfontname(pkg, [opt])
+                if fn != None:
+                    del options[o]
+                    break
+                o += 1
+                continue
+            if fn == None:
+                fn = fm.getfontname(pkg, [])
+        else:
+            fn = fm.getfontname(pkg, options)
         if fn == None:
             continue
         del document.preamble[i]
@@ -213,7 +261,18 @@ def convert_fonts(document, fm):
         else:
             fontscale = "\\font_" + fontinfo.scaletype + "_scale"
             fontinfo.scaleval = oscale
-
+        if (has_osf and fontinfo.osfdef == "false") or (not has_osf and fontinfo.osfdef == "true"):
+            if fontinfo.osfopt == None:
+                options.extend(osfoption)
+                continue
+            osf = find_token(document.header, "\\font_osf false")
+            osftag = "\\font_osf"
+            if osf == -1 and fontinfo.fonttype != "math":
+                # Try with newer format
+                osftag = "\\font_" + fontinfo.fonttype + "_osf"
+                osf = find_token(document.header, osftag + " false")
+            if osf != -1:
+                document.header[osf] = osftag + " true"
         if i > 0 and document.preamble[i-1] == "% Added by lyx2lyx":
             del document.preamble[i-1]
             i -= 1
@@ -233,8 +292,22 @@ def convert_fonts(document, fm):
             words = val.split() # ! splits also values like '"DejaVu Sans"'
             words[0] = '"' + fn + '"'
             document.header[j] = ft + ' ' + ' '.join(words)
+        if haveFontOpts and fontinfo.fonttype != "math":
+            fotag = "\\font_" + fontinfo.fonttype + "_opts"
+            fo = find_token(document.header, fotag)
+            if fo != -1:
+                document.header[fo] = fotag + " \"" + ",".join(options) + "\""
+            else:
+                # Sensible place to insert tag
+                fo = find_token(document.header, "\\font_sf_scale")
+                if fo == -1:
+                    document.warning("Malformed LyX document! Missing \\font_sf_scale")
+                else:
+                    document.header.insert(fo, fotag + " \"" + ",".join(options) + "\"")
 
-def revert_fonts(document, fm, fontmap, OnlyWithXOpts = False):
+
+
+def revert_fonts(document, fm, fontmap, OnlyWithXOpts = False, WithXOpts = False):
     " Revert native font definition to LaTeX "
     # fonlist := list of fonts created from the same package
     # Empty package means that the font-name is the same as the package-name
@@ -247,7 +320,7 @@ def revert_fonts(document, fm, fontmap, OnlyWithXOpts = False):
     while i < len(document.header):
         i = find_re(document.header, rfontscale, i+1)
         if (i == -1):
-            break
+            return True
         mo = rfontscale.search(document.header[i])
         if mo == None:
             continue
@@ -262,23 +335,24 @@ def revert_fonts(document, fm, fontmap, OnlyWithXOpts = False):
         if not val in fontmap:
             fontmap[val] = []
         x = -1
-        if OnlyWithXOpts:
+        if OnlyWithXOpts or WithXOpts:
             if ft == "\\font_math":
-                return
+                return False
             regexp = re.compile(r'^\s*(\\font_roman_opts)\s+')
             if ft == "\\font_sans":
                 regexp = re.compile(r'^\s*(\\font_sans_opts)\s+')
             elif ft == "\\font_typewriter":
                 regexp = re.compile(r'^\s*(\\font_typewriter_opts)\s+')
             x = find_re(document.header, regexp, 0)
-            if x == -1:
-                return
+            if x == -1 and OnlyWithXOpts:
+                return False
 
-            # We need to use this regex since split() does not handle quote protection
-            xopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
-            opts = xopts[1].strip('"').split(",")
-            fontmap[val].extend(opts)
-            del document.header[x]
+            if x != -1:
+                # We need to use this regex since split() does not handle quote protection
+                xopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
+                opts = xopts[1].strip('"').split(",")
+                fontmap[val].extend(opts)
+                del document.header[x]
         words[0] = '"default"'
         document.header[i] = ft + ' ' + ' '.join(words)
         if fontinfo.scaleopt != None:
@@ -290,8 +364,24 @@ def revert_fonts(document, fm, fontmap, OnlyWithXOpts = False):
                 if xval1 != "100":
                     # set correct scale option
                     fontmap[val].extend([fontinfo.scaleopt + "=" + format(float(xval1) / 100, '.2f')])
+        if fontinfo.osfopt != None:
+            oldval = "true"
+            if fontinfo.osfdef == "true":
+                oldval = "false"
+            osf = find_token(document.header, "\\font_osf " + oldval)
+            if osf == -1 and ft != "\\font_math":
+                # Try with newer format
+                osftag = "\\font_roman_osf " + oldval
+                if ft == "\\font_sans":
+                    osftag = "\\font_sans_osf " + oldval
+                elif ft == "\\font_typewriter":
+                    osftag = "\\font_typewriter_osf " + oldval
+                osf = find_token(document.header, osftag)
+            if osf != -1:
+                fontmap[val].extend([fontinfo.osfopt])
         if len(fontinfo.options) > 0:
             fontmap[val].extend(fontinfo.options)
+    return True
 
 ###############################################################################
 ###
@@ -328,8 +418,8 @@ def revert_notoFonts(document):
     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
         fontmap = dict()
         fm = createFontMapping(['Noto'])
-        revert_fonts(document, fm, fontmap)
-        add_preamble_fonts(document, fontmap)
+        if revert_fonts(document, fm, fontmap):
+            add_preamble_fonts(document, fontmap)
 
 def convert_latexFonts(document):
     " Handle DejaVu and IBMPlex fonts definition to LaTeX "
@@ -344,24 +434,24 @@ def revert_latexFonts(document):
     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
         fontmap = dict()
         fm = createFontMapping(['DejaVu', 'IBM'])
-        revert_fonts(document, fm, fontmap)
-        add_preamble_fonts(document, fontmap)
+        if revert_fonts(document, fm, fontmap):
+            add_preamble_fonts(document, fontmap)
 
 def convert_AdobeFonts(document):
-    " Handle DejaVu and IBMPlex fonts definition to LaTeX "
+    " Handle Adobe Source fonts definition to LaTeX "
 
     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
         fm = createFontMapping(['Adobe'])
         convert_fonts(document, fm)
 
 def revert_AdobeFonts(document):
-    " Revert native DejaVu font definition to LaTeX "
+    " Revert Adobe Source font definition to LaTeX "
 
     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
         fontmap = dict()
         fm = createFontMapping(['Adobe'])
-        revert_fonts(document, fm, fontmap)
-        add_preamble_fonts(document, fontmap)
+        if revert_fonts(document, fm, fontmap):
+            add_preamble_fonts(document, fontmap)
 
 def removeFrontMatterStyles(document):
     " Remove styles Begin/EndFrontmatter"
@@ -472,12 +562,25 @@ def revert_paratype(document):
         i2 = find_token(document.header, "\\font_sans \"default\"", 0)
         i3 = find_token(document.header, "\\font_typewriter \"default\"", 0)
         j = find_token(document.header, "\\font_sans \"PTSans-TLF\"", 0)
-        sfval = get_value(document.header, "\\font_sf_scale", 0)
-        # cutoff " 100"
-        sfval = sfval[:-4]
+
+        sf_scale = 100.0
+        sfval = find_token(document.header, "\\font_sf_scale", 0)
+        if sfval == -1:
+            document.warning("Malformed LyX document: Missing \\font_sf_scale.")
+        else:
+            sfscale = document.header[sfval].split()
+            val = sfscale[1]
+            sfscale[1] = "100"
+            document.header[sfval] = " ".join(sfscale)
+            try:
+                # float() can throw
+                sf_scale = float(val)
+            except:
+                document.warning("Invalid font_sf_scale value: " + val)
+
         sfoption = ""
-        if sfval != "100":
-            sfoption = "scaled=" + format(float(sfval) / 100, '.2f')
+        if sf_scale != "100.0":
+            sfoption = "scaled=" + str(sf_scale / 100.0)
         k = find_token(document.header, "\\font_typewriter \"PTMono-TLF\"", 0)
         ttval = get_value(document.header, "\\font_tt_scale", 0)
         # cutoff " 100"
@@ -539,7 +642,7 @@ def revert_lscape(document):
     while True:
         i = find_token(document.body, "\\begin_inset Flex Landscape", i+1)
         if i == -1:
-            return
+            break
         j = find_end_of_inset(document.body, i)
         if j == -1:
             document.warning("Malformed LyX document: Can't find end of Landscape inset")
@@ -554,6 +657,7 @@ def revert_lscape(document):
             document.body[i : i + 4] = put_cmd_in_ert("\\begin{landscape}")
 
         add_to_preamble(document, ["\\usepackage{pdflscape}"])
+    document.del_module("landscape")
 
 
 def convert_fontenc(document):
@@ -953,6 +1057,26 @@ def revert_vcsinfo(document):
         document.body[tp] = "type \"buffer\""
         document.body[arg] = "arg \"vcs-" + argv + "\""
 
+def revert_vcsinfo_rev_abbrev(document):
+    " Convert abbreviated revisions to regular revisions. "
+
+    i = 0
+    while True:
+        i = find_token(document.body, "\\begin_inset Info", i+1)
+        if i == -1:
+            return
+        j = find_end_of_inset(document.body, i+1)
+        if j == -1:
+            document.warning("Malformed LyX document: Could not find end of Info inset.")
+            continue
+        tp = find_token(document.body, 'type', i, j)
+        tpv = get_quoted_value(document.body, "type", tp)
+        if tpv != "vcs":
+            continue
+        arg = find_token(document.body, 'arg', i, j)
+        argv = get_quoted_value(document.body, "arg", arg)
+        if( argv == "revision-abbrev" ):
+            document.body[arg] = "arg \"revision\""
 
 def revert_dateinfo(document):
     " Revert date info insets to static text. "
@@ -1898,7 +2022,7 @@ def revert_linggloss(document):
 
             beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
             endInset = find_end_of_inset(document.body, i)
-            endPlain = find_token_backwards(document.body, "\\end_layout", endInset)
+            endPlain = find_end_of_layout(document.body, beginPlain)
             precontent = put_cmd_in_ert(cmd)
             if len(optargcontent) > 0:
                 precontent += put_cmd_in_ert("[") + optargcontent + put_cmd_in_ert("]")
@@ -2476,14 +2600,25 @@ def revert_plainNotoFonts_xopts(document):
     if str2bool(get_value(document.header, "\\use_non_tex_fonts", i)):
         return
 
+    osf = False
+    y = find_token(document.header, "\\font_osf true", 0)
+    if y != -1:
+        osf = True
+
     regexp = re.compile(r'(\\font_roman_opts)')
     x = find_re(document.header, regexp, 0)
-    if x == -1:
+    if x == -1 and not osf:
         return
 
-    # We need to use this regex since split() does not handle quote protection
-    romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
-    opts = romanopts[1].strip('"')
+    opts = ""
+    if x != -1:
+        # We need to use this regex since split() does not handle quote protection
+        romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
+        opts = romanopts[1].strip('"')
+    if osf:
+        if opts != "":
+            opts += ", "
+        opts += "osf"
 
     i = find_token(document.header, "\\font_roman", 0)
     if i == -1:
@@ -2523,7 +2658,10 @@ def revert_plainNotoFonts_xopts(document):
     preamble += opts
     preamble += "]{noto}"
     add_to_preamble(document, [preamble])
-    del document.header[x]
+    if osf:
+        document.header[y] = "\\font_osf false"
+    if x != -1:
+        del document.header[x]
 
 
 def revert_notoFonts_xopts(document):
@@ -2538,14 +2676,13 @@ def revert_notoFonts_xopts(document):
 
     fontmap = dict()
     fm = createFontMapping(['Noto'])
-    revert_fonts(document, fm, fontmap, True)
-    add_preamble_fonts(document, fontmap)
+    if revert_fonts(document, fm, fontmap, True):
+        add_preamble_fonts(document, fontmap)
 
 
 def revert_IBMFonts_xopts(document):
     " Revert native IBM font definition (with extra options) to LaTeX "
 
-
     i = find_token(document.header, '\\use_non_tex_fonts', 0)
     if i == -1:
         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
@@ -2556,8 +2693,8 @@ def revert_IBMFonts_xopts(document):
     fontmap = dict()
     fm = createFontMapping(['IBM'])
     ft = ""
-    revert_fonts(document, fm, fontmap, True)
-    add_preamble_fonts(document, fontmap)
+    if revert_fonts(document, fm, fontmap, True):
+        add_preamble_fonts(document, fontmap)
 
 
 def revert_AdobeFonts_xopts(document):
@@ -2573,9 +2710,876 @@ def revert_AdobeFonts_xopts(document):
     fontmap = dict()
     fm = createFontMapping(['Adobe'])
     ft = ""
-    revert_fonts(document, fm, fontmap, True)
-    add_preamble_fonts(document, fontmap)
+    if revert_fonts(document, fm, fontmap, True):
+        add_preamble_fonts(document, fontmap)
+
+
+def convert_osf(document):
+    " Convert \\font_osf param to new format "
+
+    NonTeXFonts = False
+    i = find_token(document.header, '\\use_non_tex_fonts', 0)
+    if i == -1:
+        document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
+    else:
+        NonTeXFonts = str2bool(get_value(document.header, "\\use_non_tex_fonts", i))
+
+    i = find_token(document.header, '\\font_osf', 0)
+    if i == -1:
+        document.warning("Malformed LyX document: Missing \\font_osf.")
+        return
+
+    osfsf = ["biolinum", "ADOBESourceSansPro", "NotoSansRegular", "NotoSansMedium", "NotoSansThin", "NotoSansLight", "NotoSansExtralight" ]
+    osftt = ["ADOBESourceCodePro", "NotoMonoRegular" ]
+
+    osfval = str2bool(get_value(document.header, "\\font_osf", i))
+    document.header[i] = document.header[i].replace("\\font_osf", "\\font_roman_osf")
+
+    if NonTeXFonts:
+        document.header.insert(i, "\\font_sans_osf false")
+        document.header.insert(i + 1, "\\font_typewriter_osf false")
+        return
+
+    if osfval:
+        x = find_token(document.header, "\\font_sans", 0)
+        if x == -1:
+            document.warning("Malformed LyX document: Missing \\font_sans.")
+        else:
+            # We need to use this regex since split() does not handle quote protection
+            sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
+            sf = sffont[1].strip('"')
+            if sf in osfsf:
+                document.header.insert(i, "\\font_sans_osf true")
+            else:
+                document.header.insert(i, "\\font_sans_osf false")
+
+        x = find_token(document.header, "\\font_typewriter", 0)
+        if x == -1:
+            document.warning("Malformed LyX document: Missing \\font_typewriter.")
+        else:
+            # We need to use this regex since split() does not handle quote protection
+            ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
+            tt = ttfont[1].strip('"')
+            if tt in osftt:
+                document.header.insert(i + 1, "\\font_typewriter_osf true")
+            else:
+                document.header.insert(i + 1, "\\font_typewriter_osf false")
+
+    else:
+        document.header.insert(i, "\\font_sans_osf false")
+        document.header.insert(i + 1, "\\font_typewriter_osf false")
+
+
+def revert_osf(document):
+    " Revert \\font_*_osf params "
+
+    NonTeXFonts = False
+    i = find_token(document.header, '\\use_non_tex_fonts', 0)
+    if i == -1:
+        document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
+    else:
+        NonTeXFonts = str2bool(get_value(document.header, "\\use_non_tex_fonts", i))
+
+    i = find_token(document.header, '\\font_roman_osf', 0)
+    if i == -1:
+        document.warning("Malformed LyX document: Missing \\font_roman_osf.")
+        return
+
+    osfval = str2bool(get_value(document.header, "\\font_roman_osf", i))
+    document.header[i] = document.header[i].replace("\\font_roman_osf", "\\font_osf")
+
+    i = find_token(document.header, '\\font_sans_osf', 0)
+    if i == -1:
+        document.warning("Malformed LyX document: Missing \\font_sans_osf.")
+        return
+
+    osfval = str2bool(get_value(document.header, "\\font_sans_osf", i))
+    del document.header[i]
+
+    i = find_token(document.header, '\\font_typewriter_osf', 0)
+    if i == -1:
+        document.warning("Malformed LyX document: Missing \\font_typewriter_osf.")
+        return
+
+    osfval |= str2bool(get_value(document.header, "\\font_typewriter_osf", i))
+    del document.header[i]
+
+    if osfval:
+        i = find_token(document.header, '\\font_osf', 0)
+        if i == -1:
+            document.warning("Malformed LyX document: Missing \\font_osf.")
+            return
+        document.header[i] = "\\font_osf true"
+
+
+def revert_texfontopts(document):
+    " Revert native TeX font definitions (with extra options) to LaTeX "
+
+    i = find_token(document.header, '\\use_non_tex_fonts', 0)
+    if i == -1:
+        document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
+        return
+    if str2bool(get_value(document.header, "\\use_non_tex_fonts", i)):
+        return
+
+    rmfonts = ["ccfonts", "cochineal", "utopia", "garamondx", "libertine", "lmodern", "palatino", "times", "xcharter" ]
+
+    # First the sf (biolinum only)
+    regexp = re.compile(r'(\\font_sans_opts)')
+    x = find_re(document.header, regexp, 0)
+    if x != -1:
+        # We need to use this regex since split() does not handle quote protection
+        sfopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
+        opts = sfopts[1].strip('"')
+        i = find_token(document.header, "\\font_sans", 0)
+        if i == -1:
+            document.warning("Malformed LyX document: Missing \\font_sans.")
+        else:
+            # We need to use this regex since split() does not handle quote protection
+            sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
+            sans = sffont[1].strip('"')
+            if sans == "biolinum":
+                sf_scale = 100.0
+                sffont[1] = '"default"'
+                document.header[i] = " ".join(sffont)
+                osf = False
+                j = find_token(document.header, "\\font_sans_osf true", 0)
+                if j != -1:
+                    osf = True
+                k = find_token(document.header, "\\font_sf_scale", 0)
+                if k == -1:
+                    document.warning("Malformed LyX document: Missing \\font_sf_scale.")
+                else:
+                    sfscale = document.header[k].split()
+                    val = sfscale[1]
+                    sfscale[1] = "100"
+                    document.header[k] = " ".join(sfscale)
+                    try:
+                        # float() can throw
+                        sf_scale = float(val)
+                    except:
+                        document.warning("Invalid font_sf_scale value: " + val)
+                preamble = "\\usepackage["
+                if osf:
+                    document.header[j] = "\\font_sans_osf false"
+                    preamble += "osf,"
+                if sf_scale != 100.0:
+                    preamble += 'scaled=' + str(sf_scale / 100.0) + ','
+                preamble += opts
+                preamble += "]{biolinum}"
+                add_to_preamble(document, [preamble])
+                del document.header[x]
+
+    regexp = re.compile(r'(\\font_roman_opts)')
+    x = find_re(document.header, regexp, 0)
+    if x == -1:
+        return
+
+    # We need to use this regex since split() does not handle quote protection
+    romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
+    opts = romanopts[1].strip('"')
+
+    i = find_token(document.header, "\\font_roman", 0)
+    if i == -1:
+        document.warning("Malformed LyX document: Missing \\font_roman.")
+        return
+    else:
+        # We need to use this regex since split() does not handle quote protection
+        romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
+        roman = romanfont[1].strip('"')
+        if not roman in rmfonts:
+            return
+        romanfont[1] = '"default"'
+        document.header[i] = " ".join(romanfont)
+        package = roman
+        if roman == "utopia":
+            package = "fourier"
+        elif roman == "palatino":
+            package = "mathpazo"
+        elif roman == "times":
+            package = "mathptmx"
+        elif roman == "xcharter":
+            package = "XCharter"
+        osf = ""
+        j = find_token(document.header, "\\font_roman_osf true", 0)
+        if j != -1:
+            if roman == "cochineal":
+                osf = "proportional,osf,"
+            elif roman == "utopia":
+                osf = "oldstyle,"
+            elif roman == "garamondx":
+                osf = "osfI,"
+            elif roman == "libertine":
+                osf = "osf,"
+            elif roman == "palatino":
+                osf = "osf,"
+            elif roman == "xcharter":
+                osf = "osf,"
+            document.header[j] = "\\font_roman_osf false"
+        k = find_token(document.header, "\\font_sc true", 0)
+        if k != -1:
+            if roman == "utopia":
+                osf += "expert,"
+            if roman == "palatino" and osf == "":
+                osf = "sc,"
+            document.header[k] = "\\font_sc false"
+        preamble = "\\usepackage["
+        preamble += osf
+        preamble += opts
+        preamble += "]{" + package + "}"
+        add_to_preamble(document, [preamble])
+        del document.header[x]
+
+
+def convert_CantarellFont(document):
+    " Handle Cantarell font definition to LaTeX "
+
+    if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
+        fm = createFontMapping(['Cantarell'])
+        convert_fonts(document, fm, "oldstyle")
 
+def revert_CantarellFont(document):
+    " Revert native Cantarell font definition to LaTeX "
+
+    if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
+        fontmap = dict()
+        fm = createFontMapping(['Cantarell'])
+        if revert_fonts(document, fm, fontmap, False, True):
+            add_preamble_fonts(document, fontmap)
+
+def convert_ChivoFont(document):
+    " Handle Chivo font definition to LaTeX "
+
+    if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
+        fm = createFontMapping(['Chivo'])
+        convert_fonts(document, fm, "oldstyle")
+
+def revert_ChivoFont(document):
+    " Revert native Chivo font definition to LaTeX "
+
+    if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
+        fontmap = dict()
+        fm = createFontMapping(['Chivo'])
+        if revert_fonts(document, fm, fontmap, False, True):
+            add_preamble_fonts(document, fontmap)
+
+
+def convert_FiraFont(document):
+    " Handle Fira font definition to LaTeX "
+
+    if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
+        fm = createFontMapping(['Fira'])
+        convert_fonts(document, fm, "lf")
+
+def revert_FiraFont(document):
+    " Revert native Fira font definition to LaTeX "
+
+    if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
+        fontmap = dict()
+        fm = createFontMapping(['Fira'])
+        if revert_fonts(document, fm, fontmap, False, True):
+            add_preamble_fonts(document, fontmap)
+
+
+def convert_Semibolds(document):
+    " Move semibold options to extraopts "
+
+    NonTeXFonts = False
+    i = find_token(document.header, '\\use_non_tex_fonts', 0)
+    if i == -1:
+        document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
+    else:
+        NonTeXFonts = str2bool(get_value(document.header, "\\use_non_tex_fonts", i))
+
+    i = find_token(document.header, "\\font_roman", 0)
+    if i == -1:
+        document.warning("Malformed LyX document: Missing \\font_roman.")
+    else:
+        # We need to use this regex since split() does not handle quote protection
+        romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
+        roman = romanfont[1].strip('"')
+        if roman == "IBMPlexSerifSemibold":
+            romanfont[1] = '"IBMPlexSerif"'
+            document.header[i] = " ".join(romanfont)
+
+            if NonTeXFonts == False:
+                regexp = re.compile(r'(\\font_roman_opts)')
+                x = find_re(document.header, regexp, 0)
+                if x == -1:
+                    # Sensible place to insert tag
+                    fo = find_token(document.header, "\\font_sf_scale")
+                    if fo == -1:
+                        document.warning("Malformed LyX document! Missing \\font_sf_scale")
+                    else:
+                        document.header.insert(fo, "\\font_roman_opts \"semibold\"")
+                else:
+                    # We need to use this regex since split() does not handle quote protection
+                    romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
+                    document.header[x] = "\\font_roman_opts \"semibold, " + romanopts[1].strip('"') + "\""
+
+    i = find_token(document.header, "\\font_sans", 0)
+    if i == -1:
+        document.warning("Malformed LyX document: Missing \\font_sans.")
+    else:
+        # We need to use this regex since split() does not handle quote protection
+        sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
+        sf = sffont[1].strip('"')
+        if sf == "IBMPlexSansSemibold":
+            sffont[1] = '"IBMPlexSans"'
+            document.header[i] = " ".join(sffont)
+
+            if NonTeXFonts == False:
+                regexp = re.compile(r'(\\font_sans_opts)')
+                x = find_re(document.header, regexp, 0)
+                if x == -1:
+                    # Sensible place to insert tag
+                    fo = find_token(document.header, "\\font_sf_scale")
+                    if fo == -1:
+                        document.warning("Malformed LyX document! Missing \\font_sf_scale")
+                    else:
+                        document.header.insert(fo, "\\font_sans_opts \"semibold\"")
+                else:
+                    # We need to use this regex since split() does not handle quote protection
+                    sfopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
+                    document.header[x] = "\\font_sans_opts \"semibold, " + sfopts[1].strip('"') + "\""
+
+    i = find_token(document.header, "\\font_typewriter", 0)
+    if i == -1:
+        document.warning("Malformed LyX document: Missing \\font_typewriter.")
+    else:
+        # We need to use this regex since split() does not handle quote protection
+        ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
+        tt = ttfont[1].strip('"')
+        if tt == "IBMPlexMonoSemibold":
+            ttfont[1] = '"IBMPlexMono"'
+            document.header[i] = " ".join(ttfont)
+
+            if NonTeXFonts == False:
+                regexp = re.compile(r'(\\font_typewriter_opts)')
+                x = find_re(document.header, regexp, 0)
+                if x == -1:
+                    # Sensible place to insert tag
+                    fo = find_token(document.header, "\\font_tt_scale")
+                    if fo == -1:
+                        document.warning("Malformed LyX document! Missing \\font_tt_scale")
+                    else:
+                        document.header.insert(fo, "\\font_typewriter_opts \"semibold\"")
+                else:
+                    # We need to use this regex since split() does not handle quote protection
+                    ttopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
+                    document.header[x] = "\\font_typewriter_opts \"semibold, " + sfopts[1].strip('"') + "\""
+
+
+def convert_NotoRegulars(document):
+    " Merge diverse noto reagular fonts "
+
+    i = find_token(document.header, "\\font_roman", 0)
+    if i == -1:
+        document.warning("Malformed LyX document: Missing \\font_roman.")
+    else:
+        # We need to use this regex since split() does not handle quote protection
+        romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
+        roman = romanfont[1].strip('"')
+        if roman == "NotoSerif-TLF":
+            romanfont[1] = '"NotoSerifRegular"'
+            document.header[i] = " ".join(romanfont)
+
+    i = find_token(document.header, "\\font_sans", 0)
+    if i == -1:
+        document.warning("Malformed LyX document: Missing \\font_sans.")
+    else:
+        # We need to use this regex since split() does not handle quote protection
+        sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
+        sf = sffont[1].strip('"')
+        if sf == "NotoSans-TLF":
+            sffont[1] = '"NotoSansRegular"'
+            document.header[i] = " ".join(sffont)
+
+    i = find_token(document.header, "\\font_typewriter", 0)
+    if i == -1:
+        document.warning("Malformed LyX document: Missing \\font_typewriter.")
+    else:
+        # We need to use this regex since split() does not handle quote protection
+        ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
+        tt = ttfont[1].strip('"')
+        if tt == "NotoMono-TLF":
+            ttfont[1] = '"NotoMonoRegular"'
+            document.header[i] = " ".join(ttfont)
+
+
+def convert_CrimsonProFont(document):
+    " Handle CrimsonPro font definition to LaTeX "
+
+    if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
+        fm = createFontMapping(['CrimsonPro'])
+        convert_fonts(document, fm, "lf")
+
+def revert_CrimsonProFont(document):
+    " Revert native CrimsonPro font definition to LaTeX "
+
+    if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
+        fontmap = dict()
+        fm = createFontMapping(['CrimsonPro'])
+        if revert_fonts(document, fm, fontmap, False, True):
+            add_preamble_fonts(document, fontmap)
+
+
+def revert_pagesizes(document):
+    " Revert new page sizes in memoir and KOMA to options "
+
+    if document.textclass != "memoir" and document.textclass[:2] != "scr":
+        return
+
+    i = find_token(document.header, "\\use_geometry true", 0)
+    if i != -1:
+        return
+
+    defsizes = ["default", "custom", "letterpaper", "legalpaper", "executivepaper", "a4paper", "a5paper", "b5paper"]
+
+    i = find_token(document.header, "\\papersize", 0)
+    if i == -1:
+        document.warning("Malformed LyX document! Missing \\papersize header.")
+        return
+    val = get_value(document.header, "\\papersize", i)
+    if val in defsizes:
+        # nothing to do
+        return
+
+    document.header[i] = "\\papersize default"
+
+    i = find_token(document.header, "\\options", 0)
+    if i == -1:
+        i = find_token(document.header, "\\textclass", 0)
+        if i == -1:
+            document.warning("Malformed LyX document! Missing \\textclass header.")
+            return
+        document.header.insert(i, "\\options " + val)
+        return
+    document.header[i] = document.header[i] + "," + val
+
+
+def convert_pagesizes(document):
+    " Convert to new page sizes in memoir and KOMA to options "
+
+    if document.textclass != "memoir" and document.textclass[:3] != "scr":
+        return
+
+    i = find_token(document.header, "\\use_geometry true", 0)
+    if i != -1:
+        return
+
+    defsizes = ["default", "custom", "letterpaper", "legalpaper", "executivepaper", "a4paper", "a5paper", "b5paper"]
+
+    i = find_token(document.header, "\\papersize", 0)
+    if i == -1:
+        document.warning("Malformed LyX document! Missing \\papersize header.")
+        return
+    val = get_value(document.header, "\\papersize", i)
+    if val in defsizes:
+        # nothing to do
+        return
+
+    i = find_token(document.header, "\\use_geometry false", 0)
+    if i != -1:
+        # Maintain use of geometry
+        document.header[1] = "\\use_geometry true"
+
+def revert_komafontsizes(document):
+    " Revert new font sizes in KOMA to options "
+
+    if document.textclass[:3] != "scr":
+        return
+
+    i = find_token(document.header, "\\paperfontsize", 0)
+    if i == -1:
+        document.warning("Malformed LyX document! Missing \\paperfontsize header.")
+        return
+
+    defsizes = ["default", "10", "11", "12"]
+
+    val = get_value(document.header, "\\paperfontsize", i)
+    if val in defsizes:
+        # nothing to do
+        return
+
+    document.header[i] = "\\paperfontsize default"
+
+    fsize = "fontsize=" + val
+
+    i = find_token(document.header, "\\options", 0)
+    if i == -1:
+        i = find_token(document.header, "\\textclass", 0)
+        if i == -1:
+            document.warning("Malformed LyX document! Missing \\textclass header.")
+            return
+        document.header.insert(i, "\\options " + fsize)
+        return
+    document.header[i] = document.header[i] + "," + fsize
+
+
+def revert_dupqualicites(document):
+    " Revert qualified citation list commands with duplicate keys to ERT "
+
+    # LyX 2.3 only supports qualified citation lists with unique keys. Thus,
+    # we need to revert those with multiple uses of the same key.
+
+    # Get cite engine
+    engine = "basic"
+    i = find_token(document.header, "\\cite_engine", 0)
+    if i == -1:
+        document.warning("Malformed document! Missing \\cite_engine")
+    else:
+        engine = get_value(document.header, "\\cite_engine", i)
+
+    if not engine in ["biblatex", "biblatex-natbib"]:
+        return
+
+    # Citation insets that support qualified lists, with their LaTeX code
+    ql_citations = {
+        "cite" : "cites",
+        "Cite" : "Cites",
+        "citet" : "textcites",
+        "Citet" : "Textcites",
+        "citep" : "parencites",
+        "Citep" : "Parencites",
+        "Footcite" : "Smartcites",
+        "footcite" : "smartcites",
+        "Autocite" : "Autocites",
+        "autocite" : "autocites",
+        }
+
+    i = 0
+    while (True):
+        i = find_token(document.body, "\\begin_inset CommandInset citation", i)
+        if i == -1:
+            break
+        j = find_end_of_inset(document.body, i)
+        if j == -1:
+            document.warning("Can't find end of citation inset at line %d!!" %(i))
+            i += 1
+            continue
+
+        k = find_token(document.body, "LatexCommand", i, j)
+        if k == -1:
+            document.warning("Can't find LatexCommand for citation inset at line %d!" %(i))
+            i = j + 1
+            continue
+
+        cmd = get_value(document.body, "LatexCommand", k)
+        if not cmd in list(ql_citations.keys()):
+            i = j + 1
+            continue
+
+        pres = find_token(document.body, "pretextlist", i, j)
+        posts = find_token(document.body, "posttextlist", i, j)
+        if pres == -1 and posts == -1:
+            # nothing to do.
+            i = j + 1
+            continue
+
+        key = get_quoted_value(document.body, "key", i, j)
+        if not key:
+            document.warning("Citation inset at line %d does not have a key!" %(i))
+            i = j + 1
+            continue
+
+        keys = key.split(",")
+        ukeys = list(set(keys))
+        if len(keys) == len(ukeys):
+            # no duplicates.
+            i = j + 1
+            continue
+
+        pretexts = get_quoted_value(document.body, "pretextlist", pres)
+        posttexts = get_quoted_value(document.body, "posttextlist", posts)
+
+        pre = get_quoted_value(document.body, "before", i, j)
+        post = get_quoted_value(document.body, "after", i, j)
+        prelist = pretexts.split("\t")
+        premap = dict()
+        for pp in prelist:
+            ppp = pp.split(" ", 1)
+            val = ""
+            if len(ppp) > 1:
+                val = ppp[1]
+            else:
+                val = ""
+            if ppp[0] in premap:
+                premap[ppp[0]] = premap[ppp[0]] + "\t" + val
+            else:
+                premap[ppp[0]] = val
+        postlist = posttexts.split("\t")
+        postmap = dict()
+        num = 1
+        for pp in postlist:
+            ppp = pp.split(" ", 1)
+            val = ""
+            if len(ppp) > 1:
+                val = ppp[1]
+            else:
+                val = ""
+            if ppp[0] in postmap:
+                postmap[ppp[0]] = postmap[ppp[0]] + "\t" + val
+            else:
+                postmap[ppp[0]] = val
+        # Replace known new commands with ERT
+        if "(" in pre or ")" in pre:
+            pre = "{" + pre + "}"
+        if "(" in post or ")" in post:
+            post = "{" + post + "}"
+        res = "\\" + ql_citations[cmd]
+        if pre:
+            res += "(" + pre + ")"
+        if post:
+            res += "(" + post + ")"
+        elif pre:
+            res += "()"
+        for kk in keys:
+            if premap.get(kk, "") != "":
+                akeys = premap[kk].split("\t", 1)
+                akey = akeys[0]
+                if akey != "":
+                    res += "[" + akey + "]"
+                if len(akeys) > 1:
+                    premap[kk] = "\t".join(akeys[1:])
+                else:
+                    premap[kk] = ""
+            if postmap.get(kk, "") != "":
+                akeys = postmap[kk].split("\t", 1)
+                akey = akeys[0]
+                if akey != "":
+                    res += "[" + akey + "]"
+                if len(akeys) > 1:
+                    postmap[kk] = "\t".join(akeys[1:])
+                else:
+                    postmap[kk] = ""
+            elif premap.get(kk, "") != "":
+                res += "[]"
+            res += "{" + kk + "}"
+        document.body[i:j+1] = put_cmd_in_ert([res])
+
+
+def convert_pagesizenames(document):
+    " Convert LyX page sizes names "
+
+    i = find_token(document.header, "\\papersize", 0)
+    if i == -1:
+        document.warning("Malformed LyX document! Missing \\papersize header.")
+        return
+    oldnames = ["letterpaper", "legalpaper", "executivepaper", \
+                "a0paper", "a1paper", "a2paper", "a3paper", "a4paper", "a5paper", "a6paper", \
+               "b0paper", "b1paper", "b2paper", "b3paper", "b4paper", "b5paper", "b6paper", \
+               "c0paper", "c1paper", "c2paper", "c3paper", "c4paper", "c5paper", "c6paper"]
+    val = get_value(document.header, "\\papersize", i)
+    if val in oldnames:
+        newval = val.replace("paper", "")
+        document.header[i] = "\\papersize " + newval
+
+def revert_pagesizenames(document):
+    " Convert LyX page sizes names "
+
+    i = find_token(document.header, "\\papersize", 0)
+    if i == -1:
+        document.warning("Malformed LyX document! Missing \\papersize header.")
+        return
+    newnames = ["letter", "legal", "executive", \
+                "a0", "a1", "a2", "a3", "a4", "a5", "a6", \
+               "b0", "b1", "b2", "b3", "b4", "b5", "b6", \
+               "c0", "c1", "c2", "c3", "c4", "c5", "c6"]
+    val = get_value(document.header, "\\papersize", i)
+    if val in newnames:
+        newval = val + "paper"
+        document.header[i] = "\\papersize " + newval
+
+
+def revert_theendnotes(document):
+    " Reverts native support of \\theendnotes to TeX-code "
+
+    if not "endnotes" in document.get_module_list() and not "foottoend" in document.get_module_list():
+        return
+
+    i = 0
+    while True:
+        i = find_token(document.body, "\\begin_inset FloatList endnote", i + 1)
+        if i == -1:
+            return
+        j = find_end_of_inset(document.body, i)
+        if j == -1:
+            document.warning("Malformed LyX document: Can't find end of FloatList inset")
+            continue
+
+        document.body[i : j + 1] = put_cmd_in_ert("\\theendnotes")
+
+
+def revert_enotez(document):
+    " Reverts native support of enotez package to TeX-code "
+
+    if not "enotez" in document.get_module_list() and not "foottoenotez" in document.get_module_list():
+        return
+
+    use = False
+    if find_token(document.body, "\\begin_inset Flex Endnote", 0) != -1:
+        use = True
+
+    revert_flex_inset(document.body, "Endnote", "\\endnote")
+
+    i = 0
+    while True:
+        i = find_token(document.body, "\\begin_inset FloatList endnote", i + 1)
+        if i == -1:
+            break
+        j = find_end_of_inset(document.body, i)
+        if j == -1:
+            document.warning("Malformed LyX document: Can't find end of FloatList inset")
+            continue
+
+        use = True
+        document.body[i : j + 1] = put_cmd_in_ert("\\printendnotes")
+
+    if use:
+        add_to_preamble(document, ["\\usepackage{enotez}"])
+    document.del_module("enotez")
+    document.del_module("foottoenotez")
+
+
+def revert_memoir_endnotes(document):
+    " Reverts native support of memoir endnotes to TeX-code "
+
+    if document.textclass != "memoir":
+        return
+
+    encommand = "\\pagenote"
+    modules = document.get_module_list()
+    if "enotez" in modules or "foottoenotez" in modules or "endnotes" in modules or "foottoend" in modules:
+        encommand = "\\endnote"
+
+    revert_flex_inset(document.body, "Endnote", encommand)
+
+    i = 0
+    while True:
+        i = find_token(document.body, "\\begin_inset FloatList pagenote", i + 1)
+        if i == -1:
+            break
+        j = find_end_of_inset(document.body, i)
+        if j == -1:
+            document.warning("Malformed LyX document: Can't find end of FloatList inset")
+            continue
+
+        if document.body[i] == "\\begin_inset FloatList pagenote*":
+            document.body[i : j + 1] = put_cmd_in_ert("\\printpagenotes*")
+        else:
+            document.body[i : j + 1] = put_cmd_in_ert("\\printpagenotes")
+        add_to_preamble(document, ["\\makepagenote"])
+
+
+def revert_totalheight(document):
+    " Reverts graphics height parameter from totalheight to height "
+
+    i = 0
+    while (True):
+        i = find_token(document.body, "\\begin_inset Graphics", i)
+        if i == -1:
+            break
+        j = find_end_of_inset(document.body, i)
+        if j == -1:
+            document.warning("Can't find end of graphics inset at line %d!!" %(i))
+            i += 1
+            continue
+
+        rx = re.compile(r'\s*special\s*(\S+)$')
+        k = find_re(document.body, rx, i, j)
+        special = ""
+        oldheight = ""
+        if k != -1:
+            m = rx.match(document.body[k])
+            if m:
+                special = m.group(1)
+            mspecial = special.split(',')
+            for spc in mspecial:
+                if spc[:7] == "height=":
+                    oldheight = spc.split('=')[1]
+                    mspecial.remove(spc)
+                    break
+            if len(mspecial) > 0:
+                special = ",".join(mspecial)
+            else:
+                special = ""
+
+        rx = re.compile(r'(\s*height\s*)(\S+)$')
+        kk = find_re(document.body, rx, i, j)
+        if kk != -1:
+            m = rx.match(document.body[kk])
+            val = ""
+            if m:
+                val = m.group(2)
+                if k != -1:
+                    if special != "":
+                        val = val + "," + special
+                    document.body[k] = "\tspecial " + "totalheight=" + val
+                else:
+                    document.body.insert(kk, "\tspecial totalheight=" + val) 
+                if oldheight != "":
+                    document.body[kk] = m.group(1) + oldheight
+                else:
+                    del document.body[kk]
+        elif oldheight != "":
+            document.body.insert(k, "\theight " + oldheight) 
+        i = j + 1
+
+
+def convert_totalheight(document):
+    " Converts graphics height parameter from totalheight to height "
+
+    i = 0
+    while (True):
+        i = find_token(document.body, "\\begin_inset Graphics", i)
+        if i == -1:
+            break
+        j = find_end_of_inset(document.body, i)
+        if j == -1:
+            document.warning("Can't find end of graphics inset at line %d!!" %(i))
+            i += 1
+            continue
+
+        rx = re.compile(r'\s*special\s*(\S+)$')
+        k = find_re(document.body, rx, i, j)
+        special = ""
+        newheight = ""
+        if k != -1:
+            m = rx.match(document.body[k])
+            if m:
+                special = m.group(1)
+            mspecial = special.split(',')
+            for spc in mspecial:
+                if spc[:12] == "totalheight=":
+                    newheight = spc.split('=')[1]
+                    mspecial.remove(spc)
+                    break
+            if len(mspecial) > 0:
+                special = ",".join(mspecial)
+            else:
+                special = ""
+
+        rx = re.compile(r'(\s*height\s*)(\S+)$')
+        kk = find_re(document.body, rx, i, j)
+        if kk != -1:
+            m = rx.match(document.body[kk])
+            val = ""
+            if m:
+                val = m.group(2)
+                if k != -1:
+                    if special != "":
+                        val = val + "," + special
+                    document.body[k] = "\tspecial " + "height=" + val
+                else:
+                    document.body.insert(kk + 1, "\tspecial height=" + val) 
+                if newheight != "":
+                    document.body[kk] = m.group(1) + newheight
+                else:
+                    del document.body[kk]
+        elif newheight != "":
+            document.body.insert(k, "\theight " + newheight) 
+        i = j + 1
 
 ##
 # Conversion hub
@@ -2618,10 +3622,28 @@ convert = [
            [577, [convert_linggloss]],
            [578, []],
            [579, []],
-           [580, []]
+           [580, []],
+           [581, [convert_osf]],
+           [582, [convert_AdobeFonts,convert_latexFonts,convert_notoFonts,convert_CantarellFont,convert_FiraFont]],# old font re-converterted due to extra options
+           [583, [convert_ChivoFont,convert_Semibolds,convert_NotoRegulars,convert_CrimsonProFont]],
+           [584, []],
+           [585, [convert_pagesizes]],
+           [586, []],
+           [587, [convert_pagesizenames]],
+           [588, []],
+           [589, [convert_totalheight]]
           ]
 
-revert =  [[579, [revert_minionpro, revert_plainNotoFonts_xopts, revert_notoFonts_xopts, revert_IBMFonts_xopts, revert_AdobeFonts_xopts, revert_font_opts]], # keep revert_font_opts last!
+revert =  [[588, [revert_totalheight]],
+           [587, [revert_memoir_endnotes,revert_enotez,revert_theendnotes]],
+           [586, [revert_pagesizenames]],
+           [585, [revert_dupqualicites]],
+           [584, [revert_pagesizes,revert_komafontsizes]],
+           [583, [revert_vcsinfo_rev_abbrev]],
+           [582, [revert_ChivoFont,revert_CrimsonProFont]],
+           [581, [revert_CantarellFont,revert_FiraFont]],
+           [580, [revert_texfontopts,revert_osf]],
+           [579, [revert_minionpro, revert_plainNotoFonts_xopts, revert_notoFonts_xopts, revert_IBMFonts_xopts, revert_AdobeFonts_xopts, revert_font_opts]], # keep revert_font_opts last!
            [578, [revert_babelfont]],
            [577, [revert_drs]],
            [576, [revert_linggloss, revert_subexarg]],