[LyX/master] Add support for draw.io as an external template as promised

Koji Yokota yokota at lyx.org
Sun Feb 22 12:25:31 UTC 2026


commit 6f79bf845415a3c96de2a88a22c5b53f56e7ed65
Author: Koji Yokota <yokota at lyx.org>
Date:   Sun Feb 22 21:23:54 2026 +0900

    Add support for draw.io as an external template as promised
    
    Please let me know if additional work is required esp. related to file format
    
    https://lists.lyx.org/pipermail/lyx-devel/2025-July/thread.html#14905
---
 lib/Makefile.am                  |   1 +
 lib/configure.py                 | 101 +++++++++++++++++++++++++++++++++++++--
 lib/xtemplates/draw.io.xtemplate |  69 ++++++++++++++++++++++++++
 3 files changed, 167 insertions(+), 4 deletions(-)

diff --git a/lib/Makefile.am b/lib/Makefile.am
index c71e0d6fd3..401c937f06 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -3407,6 +3407,7 @@ xtemplatesdir = $(pkgdatadir)/xtemplates
 dist_xtemplates_DATA = \
 	xtemplates/chess.xtemplate \
 	xtemplates/dia.xtemplate \
+	xtemplates/draw.io.xtemplate \
 	xtemplates/gnumeric.xtemplate \
 	xtemplates/inkscape.xtemplate \
 	xtemplates/lilypond.xtemplate \
diff --git a/lib/configure.py b/lib/configure.py
index 5f1ea0fdc1..914812a48e 100644
--- a/lib/configure.py
+++ b/lib/configure.py
@@ -342,17 +342,47 @@ def check_java():
 
 
 def checkMacOSappInstalled(prog):
+    result = checkMacOSapp(prog)
+    if result != False:
+        return result != ''
+    else:
+        return result
+
+
+def checkMacOSapp(prog, retarderKeys = False):
     '''
         Use metadata lookup to search for an "installed" macOS application bundle.
+        It returns full path of only one program among those found.
+        If retarderKeys == False, the path of first-found program is returned.
+        If retarderKeys is specified, programs that contains one of retarderKeys
+        in their path name will have lower priority to be reported.
     '''
     if sys.platform == 'darwin' and len(prog) >= 1:
         command = r'mdfind "kMDItemContentTypeTree == \"com.apple.application\"c && kMDItemFSName == \"%s\""' % prog
-        result = cmdOutput(command)
+        # Take the one found first (outcome of cmdOutput is multiple lines if
+        # it finds more than one)
+        results = cmdOutput(command).split('\n')
+        if retarderKeys != False:
+            retarder = []
+            priority = []
+            isRetarder = False
+            for idx in range(len(results)):
+                path_components = results[idx].split(os.sep)
+                for com in path_components:
+                    if com in retarderKeys:
+                        retarder.append(results[idx])
+                        isRetarder = True
+                        break
+                if not isRetarder:
+                    priority.append(results[idx])
+                else:
+                    isRetarder = False
+            results = priority + retarder
+        result = results[0]
         logger.debug(command + ": " + result)
-        return result != ''
+        return result
     return False
 
-
 def checkProgAlternatives(description, progs, rc_entry=None,
                           alt_rc_entry=None, path=None, not_found=''):
     '''
@@ -373,7 +403,10 @@ def checkProgAlternatives(description, progs, rc_entry=None,
     logger.info('checking for ' + description + '...')
     logger.debug('(' + ','.join(progs) + ')')
     additional_path = path
-    path = os.environ["PATH"].split(os.pathsep) + additional_path
+    sys_path = os.environ["PATH"].split(os.pathsep)
+    if (isinstance(sys_path, list)):
+        sys_path = [ x for x in sys_path if x !='' ]
+    path = sys_path + additional_path
     extlist = ['']
     if "PATHEXT" in os.environ:
         extlist = extlist + os.environ["PATHEXT"].split(os.pathsep)
@@ -631,6 +664,47 @@ def checkInkscapeStable():
         return True
 
 
+def checkDrawIO(progName, WinLMkey):
+    # Returns a list of additional paths to search for binary draw.io
+    # (draw.io inside of draw.io.app contents in the case of MacOS)
+
+    # check for MacOS
+    appPath = checkMacOSapp(progName + ".app",
+                            retarderKeys = [ 'Applications (Parallels)' ])
+    if appPath != False and appPath != '':
+        progPaths = [ appPath + "/Contents/MacOS" ]
+    elif os.name == 'nt':
+        result = checkWindowsProgram(progName, WinLMkey)[0]
+        if result == None:
+            progPaths = None
+        else:
+            progPaths = result
+    else:
+        progPaths = None
+    return progPaths
+
+
+def checkWindowsProgram(progName, WinLMkey):
+    # Returns a tuple of the installed path and filename of the program
+    # named 'progName' which has registered the fullpath in the registry key
+    # 'WinLMkey' in the hive HKEY_LOCAL_MACHINE
+    if os.name != 'nt':
+      return None, None
+
+    import winreg
+    reg_hive = winreg.HKEY_LOCAL_MACHINE
+    try:
+        with winreg.OpenKey(reg_hive, WinLMkey) as reg_key:
+          binPath = winreg.QueryValue(reg_key, None).replace('"%1"', '').strip()
+          logger.info('investigating in Windows registry for ' + progName + ': found ' + binPath)
+          pathComponents = binPath.replace('\\' + progName + '.exe', '')
+
+    except OSError:
+        return None, None
+
+    return [ pathComponents ], progName + '.exe'
+
+
 def checkLatex(dtl_tools):
     ''' Check latex, return lyx_check_config '''
     path, LATEX = checkProg('a Latex2e program', ['latex $$i', 'latex2e $$i'])
@@ -712,6 +786,10 @@ def checkFormatEntries(dtl_tools):
     checkViewerEditor('a Dia viewer and editor', ['dia'],
         rc_entry = [r'\Format dia        dia     DIA                    "" "%%"	"%%"	"vector,zipped=native", "application/x-dia-diagram"'])
     #
+    checkViewerEditor('a draw.io viewer and editor', [ 'draw.io', 'drawio', 'draw.io.app' ],
+        rc_entry = [r'\Format drawio     drawio  Draw.io                    "" "%%"	"%%"	"vector,zipped=native", "application/x-draw.io-diagram"'],
+        path = checkWindowsProgram('draw.io', "SOFTWARE\\Classes\\draw.io Diagram\\shell\\open\\command")[0])
+    #
     checkViewerEditor('an OpenDocument drawing viewer and editor', ['libreoffice', 'lodraw', 'ooffice', 'oodraw', 'soffice'],
         rc_entry = [r'\Format odg        "odg, sxd" "OpenDocument drawing"   "" "%%"	"%%"	"vector,zipped=native"	"application/vnd.oasis.opendocument.graphics"'])
     #
@@ -1208,6 +1286,21 @@ def checkConverterEntries():
         addToRC(r'''\converter dia        png        "dia -e $$o -t png $$i"	""
 \converter dia        eps        "dia -e $$o -t eps $$i"	""
 \converter dia        svg        "dia -e $$o -t svg $$i"	""''')
+    #
+    # draw.io
+    progNames = [ 'draw.io', 'drawio' ]
+    additionalPaths = checkDrawIO(progNames[0],
+                     "SOFTWARE\\Classes\\draw.io Diagram\\shell\\open\\command")
+    path, drawio = checkProg('a Draw.io -> Image converter', progNames,
+                             path=additionalPaths)
+    if drawio == progNames[0] or drawio == progNames[1]:
+        fullpath = os.path.join(path, drawio)
+        addToRC(r'''\converter drawio        pdf6        "\"%s\" -xf pdf -o $$o --crop $$i"	""
+\converter drawio        png        "\"%s\" -xf png -o $$o $$i"	""
+\converter drawio        jpg        "\"%s\" -xf jpg -o $$o $$i"	""
+\converter drawio        svg        "\"%s\" -xf svg -o $$o $$i"	""
+\converter drawio        docbook5        "\"%s\" -xf xml -o $$o $$i"	"xml"'''
+            % (fullpath, fullpath, fullpath, fullpath, fullpath))
 
     #
     # Actually, this produces EPS, but with a wrong bounding box (usually A4 or letter).
diff --git a/lib/xtemplates/draw.io.xtemplate b/lib/xtemplates/draw.io.xtemplate
new file mode 100644
index 0000000000..6ad0e54cb1
--- /dev/null
+++ b/lib/xtemplates/draw.io.xtemplate
@@ -0,0 +1,69 @@
+#
+# Draw.io External Template
+#
+# This file is part of LyX, the document processor.
+# Licence details can be found in the file COPYING.
+#
+# author Koji Yokota
+#
+# Full author contact details are available in file CREDITS.
+
+
+Template Draw.io
+	GuiName "Draw.io diagram"
+	HelpText
+		Draw.io diagram.
+		Draw.io provided as a browser-based and also as a stand-alone application. This template uses a stand-alone program so please make it ready before using this template. Included file will be cropped to the size of the diagram.
+	HelpTextEnd
+	InputFormat drawio
+	FileFilter "*.drawio"
+	AutomaticProduction true
+	# LyX has hard-coded support for these transformations
+	Transform Rotate
+	Transform Resize
+	Transform Clip
+	Transform Extra
+	Preview Graphics
+	Format LaTeX
+		TransformOption Rotate RotationLatexOption
+		TransformOption Resize ResizeLatexOption
+		TransformOption Clip   ClipLatexOption
+		TransformOption Extra  ExtraOption
+		Option Arg "[$$Extra,$$Rotate,$$Resize,$$Clip]"
+		# This string is what is output to the LaTeX file.
+		Product "\\includegraphics$$Arg{$$AbsOrRelPathMaster$$Basename}"
+		UpdateFormat pdf6
+		UpdateResult "$$AbsPath$$Basename.pdf"
+		Requirement "graphicx"
+		ReferencedFile latex "$$AbsPath$$Basename.pdf"
+		ReferencedFile dvi   "$$AbsPath$$Basename.pdf"
+	FormatEnd
+	Format PDFLaTeX
+		TransformOption Rotate RotationLatexOption
+		TransformOption Resize ResizeLatexOption
+		TransformOption Clip   ClipLatexOption
+		TransformOption Extra  ExtraOption
+		Option Arg "[$$Extra,$$Rotate,$$Resize,$$Clip]"
+		Product "\\includegraphics$$Arg{$$AbsOrRelPathMaster$$Basename}"
+		UpdateFormat pdf6
+		UpdateResult "$$AbsPath$$Basename.pdf"
+		Requirement "graphicx"
+		ReferencedFile pdflatex "$$AbsPath$$Basename.pdf"
+	FormatEnd
+	Format Ascii
+		Product "[Draw.io: $$FName]"
+	FormatEnd
+	Format DocBook
+		Product "<graphic fileref=\"$$AbsOrRelPathMaster$$Basename.png\"></graphic>"
+		UpdateFormat eps
+		UpdateResult "$$AbsPath$$Basename.png"
+		ReferencedFile docbook     "$$AbsPath$$Basename.png"
+		ReferencedFile docbook-xml "$$AbsPath$$Basename.png"
+	FormatEnd
+	Format XHTML
+		Product "<img src=\"$$AbsOrRelPathMaster$$Basename.svg\" />"
+		UpdateFormat svg
+		UpdateResult "$$AbsPath$$Basename.svg"
+		ReferencedFile xhtml "$$AbsPath$$Basename.svg"
+	FormatEnd
+TemplateEnd


More information about the lyx-cvs mailing list