diff --git a/.travis.yml b/.travis.yml index 7f92b993e..bbcd22401 100644 --- a/.travis.yml +++ b/.travis.yml @@ -56,7 +56,7 @@ install: sudo apt-get install python-gi python-gi-cairo libgirepository1.0-dev # wxPython doen't publish linux wheels in pypi - wget https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-14.04/wxPython-4.0.0b2-cp27-cp27mu-linux_x86_64.whl + wget -q https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-14.04/wxPython-4.0.0b2-cp27-cp27mu-linux_x86_64.whl pip install wxPython-4.0.0b2-cp27-cp27mu-linux_x86_64.whl # We can't use the shapely wheel because it includes the geos diff --git a/Makefile b/Makefile index ce4a9a8b0..55f286379 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -EXTENSIONS:=embroider embroider_params embroider_simulate embroider_update +EXTENSIONS:=embroider embroider_params embroider_simulate embroider_update embroider_print # This gets the branch name or the name of the tag VERSION:=$(TRAVIS_BRANCH) @@ -8,7 +8,8 @@ ARCH:=$(shell uname -m) dist: distclean locales bin/build-dist $(EXTENSIONS) cp *.inx dist - mv locales dist/inkstitch/bin + mkdir -p dist/inkstitch/bin/locales + cp -a locales/* dist/inkstitch/bin/locales cp -a templates dist/inkstitch/bin/ if [ "$$BUILD" = "windows" ]; then \ cd dist; zip -r ../inkstitch-$(VERSION)-win32.zip *; \ diff --git a/bin/build-dist b/bin/build-dist index 8fa4b59f9..44adfea34 100755 --- a/bin/build-dist +++ b/bin/build-dist @@ -29,6 +29,12 @@ fi # This lets pyinstaller see inkex.py, etc. pyinstaller_args+="-p inkscape-0.92.2/share/extensions " +# output useful debugging info that helps us trace library dependency issues +pyinstaller_args+="--log-level DEBUG " + +# for cefpython3 +pyinstaller_args+="--additional-hooks-dir pyinstaller-hooks " + mkdir -p dist/inkstitch/bin for extension in "$@"; do if [ "$BUILD" = "windows" ]; then diff --git a/embroider_print.py b/embroider_print.py index 4d687a8b1..bbe40b8eb 100644 --- a/embroider_print.py +++ b/embroider_print.py @@ -4,6 +4,7 @@ import sys import traceback import os +import threading import inkex import inkstitch @@ -14,22 +15,15 @@ from inkstitch.svg import render_stitch_plan from jinja2 import Environment, FileSystemLoader, select_autoescape from datetime import date -import wx, wx.html2 +from cefpython3 import cefpython as cef +import base64 -class PrintPreview(wx.Dialog): - def __init__(self, *args, **kwds): - wx.Dialog.__init__(self, *args, **kwds) - sizer = wx.BoxSizer(wx.VERTICAL) - self.browser = wx.html2.WebView.New(self) - self.browser.RegisterHandler(wx.html2.WebViewFSHandler('memory')) - #self.browser.Bind(wx.html2.EVT_WEBVIEW_LOADED, self.error) - sizer.Add(self.browser, 1, wx.EXPAND, 10) - self.SetSizer(sizer) - self.SetSize((1280, 800)) - self.Bind(wx.EVT_CLOSE, self.on_close) - def on_close(self, event): - self.Destroy() +def html_to_data_uri(html): + html = html.encode("utf-8", "replace") + b64 = base64.b64encode(html).decode("utf-8", "replace") + ret = "data:text/html;base64,{data}".format(data=b64) + return ret def datetimeformat(value, format='%Y/%m/%d'): @@ -120,14 +114,12 @@ class Print(InkstitchExtension): sys.exit(0) def show_print_preview(self, html): - app = wx.App() - print_preview = PrintPreview(None, -1) - print_preview.browser.SetPage(html, "about:print") - print_preview.Show() - wx.CallLater(3000, print_preview.browser.Print) - app.SetTopWindow(print_preview) - app.MainLoop() + cef.Initialize() + self.browser = cef.CreateBrowserSync(url=html_to_data_uri(html), window_title='Ink/Stitch Print Preview') + threading.Timer(3.0, self.browser.Print).start() + cef.MessageLoop() + cef.Shutdown() if __name__ == '__main__': effect = Print() diff --git a/pyinstaller-hooks/hook-cefpython3.py b/pyinstaller-hooks/hook-cefpython3.py new file mode 100644 index 000000000..04daea59a --- /dev/null +++ b/pyinstaller-hooks/hook-cefpython3.py @@ -0,0 +1,200 @@ +""" +This is PyInstaller hook file for CEF Python. This file +helps pyinstaller find CEF Python dependencies that are +required to run final executable. + +See PyInstaller docs for hooks: +https://pyinstaller.readthedocs.io/en/stable/hooks.html +""" + +import glob +import os +import platform +import re +import sys +import PyInstaller +from PyInstaller.utils.hooks import is_module_satisfies, get_package_paths +from PyInstaller.compat import is_linux +from PyInstaller import log as logging + +# Constants +CEFPYTHON_MIN_VERSION = "57.0" +PYINSTALLER_MIN_VERSION = "3.2.1" + +CEFPYTHON3_DIR = get_package_paths("cefpython3")[1] + +if platform.system() == "Windows": + CYTHON_MODULE_EXT = ".pyd" +else: + CYTHON_MODULE_EXT = ".so" + +# Globals +logger = logging.getLogger(__name__) + + +# Functions +def check_pyinstaller_version(): + """Using is_module_satisfies() for pyinstaller fails when + installed using 'pip install develop.zip' command + (PyInstaller Issue #2802).""" + # Example version string for dev version of pyinstaller: + # > 3.3.dev0+g5dc9557c + version = PyInstaller.__version__ + match = re.search(r"^\d+\.\d+", version) + if not (match.group(0) >= PYINSTALLER_MIN_VERSION): + raise SystemExit("Error: pyinstaller %s or higher is required" + % PYINSTALLER_MIN_VERSION) + + +def check_cefpython3_version(): + if not is_module_satisfies("cefpython3 >= %s" % CEFPYTHON_MIN_VERSION): + raise SystemExit("Error: cefpython3 %s or higher is required" + % CEFPYTHON_MIN_VERSION) + + +def get_cefpython_modules(): + """Get all cefpython Cython modules in the cefpython3 package. + It returns a list of names without file extension. Eg. + 'cefpython_py27'. """ + pyds = glob.glob(os.path.join(CEFPYTHON3_DIR, + "cefpython_py*" + CYTHON_MODULE_EXT)) + assert len(pyds) > 1, "Missing cefpython3 Cython modules" + modules = [] + for path in pyds: + filename = os.path.basename(path) + mod = filename.replace(CYTHON_MODULE_EXT, "") + modules.append(mod) + return modules + + +def get_excluded_cefpython_modules(): + """CEF Python package includes Cython modules for various Python + versions. When using Python 2.7 pyinstaller should not + bundle modules for eg. Python 3.6, otherwise it will + cause to include Python 3 dll dependencies. Returns a list + of fully qualified names eg. 'cefpython3.cefpython_py27'.""" + pyver = "".join(map(str, sys.version_info[:2])) + pyver_string = "py%s" % pyver + modules = get_cefpython_modules() + excluded = [] + for mod in modules: + if pyver_string in mod: + continue + excluded.append("cefpython3.%s" % mod) + logger.info("Exclude cefpython3 module: %s" % excluded[-1]) + return excluded + + +def get_cefpython3_binaries(): + binaries = [] + + if is_linux: + binaries.append((CEFPYTHON3_DIR + "/libcef.so", "")) + + # pyinstaller misses these because they're loaded by nss at runtime + for file in glob.glob('/usr/lib/*/nss/*.so'): + binaries.append((file, "")) + + return binaries + +def get_cefpython3_datas(): + """Returning all cefpython binaries as DATAS, because + pyinstaller does strange things and fails if these are + returned as BINARIES. It first updates manifest in .dll files: + >> Updating manifest in chrome_elf.dll + + And then because of that it fails to load the library: + >> hsrc = win32api.LoadLibraryEx(filename, 0, LOAD_LIBRARY_AS_DATAFILE) + >> pywintypes.error: (5, 'LoadLibraryEx', 'Access is denied.') + + It is not required for pyinstaller to modify in any way + CEF binaries or to look for its dependencies. CEF binaries + does not have any external dependencies like MSVCR or similar. + + The .pak .dat and .bin files cannot be marked as BINARIES + as pyinstaller would fail to find binary depdendencies on + these files. + + DATAS are in format: tuple(full_path, dest_subdir). + """ + ret = list() + + # Binaries, licenses and readmes in the cefpython3/ directory + for filename in os.listdir(CEFPYTHON3_DIR): + # Ignore Cython modules which are already handled by + # pyinstaller automatically. + if filename[:-4] in get_cefpython_modules(): + continue + # CEF binaries and datas + if filename[-4:] in [".exe", ".dll", ".pak", ".dat", ".bin", + ".txt"]\ + or filename in ["License", "subprocess"]: + logger.info("Include cefpython3 data: %s" % filename) + + if is_linux: + ret.append((os.path.join(CEFPYTHON3_DIR, filename), "cefpython3")) + else: + ret.append((os.path.join(CEFPYTHON3_DIR, filename), "")) + + # The .pak files in cefpython3/locales/ directory + locales_dir = os.path.join(CEFPYTHON3_DIR, "locales") + assert os.path.exists(locales_dir), "locales/ dir not found in cefpython3" + for filename in os.listdir(locales_dir): + logger.info("Include cefpython3 data: %s/%s" % ( + os.path.basename(locales_dir), filename)) + ret.append((os.path.join(locales_dir, filename), + "locales")) + return ret + + +# ---------------------------------------------------------------------------- +# Main +# ---------------------------------------------------------------------------- + +# Checks +check_pyinstaller_version() +check_cefpython3_version() + +# Info +logger.info("CEF Python package directory: %s" % CEFPYTHON3_DIR) + +# Hidden imports. +# PyInstaller has no way on detecting imports made by Cython +# modules, so all pure Python imports made in cefpython .pyx +# files need to be manually entered here. +# TODO: Write a tool script that would find such imports in +# .pyx files automatically. +hiddenimports = [ + "codecs", + "copy", + "datetime", + "inspect", + "json", + "os", + "platform", + "random", + "re", + "sys", + "time", + "traceback", + "types", + "urllib", + "weakref", +] +if sys.version_info.major == 2: + hiddenimports += [ + "urlparse", + ] + +# Excluded modules +excludedimports = get_excluded_cefpython_modules() + +# Include binaries +binaries = get_cefpython3_binaries() + +# Include datas +datas = get_cefpython3_datas() + +# Notify pyinstaller.spec code that this hook was executed +# and that it succeeded. +os.environ["PYINSTALLER_CEFPYTHON3_HOOK_SUCCEEDED"] = "1" diff --git a/requirements.txt b/requirements.txt index aecce0b44..c5c59aeac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ lxml appdirs numpy jinja2 +flask diff --git a/templates/color_swatch.html b/templates/color_swatch.html index 013a2c53c..96b86775d 100644 --- a/templates/color_swatch.html +++ b/templates/color_swatch.html @@ -1,4 +1,4 @@ -