X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=lib%2Fscripts%2Flyxpreview2bitmap.py;h=eee000deeae33840f085d6554a0a82305b811850;hb=dcf06fdb22bf39637aa7e11e59ca281d921fcbc7;hp=71849ce51e934ee24ac0ffe114777fcb6e0af9bf;hpb=4df9eb36a05c037bb4b191677de3a348d45456a4;p=lyx.git diff --git a/lib/scripts/lyxpreview2bitmap.py b/lib/scripts/lyxpreview2bitmap.py index 71849ce51e..eee000deea 100755 --- a/lib/scripts/lyxpreview2bitmap.py +++ b/lib/scripts/lyxpreview2bitmap.py @@ -1,4 +1,3 @@ -#! /usr/bin/env python # -*- coding: utf-8 -*- # file lyxpreview2bitmap.py @@ -76,15 +75,21 @@ # Moreover dvipng can't work with PDF files, so, if the CONVERTER # paramter is pdflatex we have to fallback to legacy route (step 2). -import getopt, glob, os, re, shutil, string, sys +from __future__ import print_function -from legacy_lyxpreview2ppm import legacy_conversion_step1 +import getopt, glob, os, re, shutil, sys, tempfile -from lyxpreview_tools import bibtex_commands, copyfileobj, error, \ - filter_pages, find_exe, find_exe_or_terminate, join_metrics_and_rename, \ - latex_commands, latex_file_re, make_texcolor, mkstemp, pdflatex_commands, \ - progress, run_command, run_latex, run_tex, warning, write_metrics_info +import lyxpreview_tools +from legacy_lyxpreview2ppm import extract_resolution, legacy_conversion_step1 + +from lyxpreview_tools import bibtex_commands, check_latex_log, copyfileobj, \ + error, filter_pages, find_exe, find_exe_or_terminate, \ + join_metrics_and_rename, latex_commands, latex_file_re, make_texcolor, \ + pdflatex_commands, progress, run_command, run_latex, run_tex, \ + warning, write_metrics_info + +PY2 = sys.version_info[0] == 2 def usage(prog_name): msg = """ @@ -134,11 +139,14 @@ def extract_metrics_info(dvipng_stdout): success = 1 # Calculate the 'ascent fraction'. - descent = string.atof(match.group(1)) - ascent = string.atof(match.group(2)) + descent = float(match.group(1)) + ascent = float(match.group(2)) frac = 0.5 - if ascent >= 0 or descent >= 0: + if ascent < 0: + # This is an empty image, forbid its display + frac = -1.0 + elif ascent >= 0 or descent >= 0: if abs(ascent + descent) > 0.1: frac = ascent / (ascent + descent) @@ -155,20 +163,32 @@ def extract_metrics_info(dvipng_stdout): return results -def fix_latex_file(latex_file): - documentclass_re = re.compile("(\\\\documentclass\[)(1[012]pt,?)(.+)") +def fix_latex_file(latex_file, pdf_output): + # python 2 does not allow to declare a string as raw byte so we double + # the backslashes and remove the r preffix + def_re = re.compile(b"(\\\\newcommandx|\\\\global\\\\long\\\\def)" + b"(\\\\[a-zA-Z]+)") - tmp = mkstemp() + tmp = tempfile.TemporaryFile() - changed = 0 - for line in open(latex_file, 'r').readlines(): - match = documentclass_re.match(line) - if match == None: - tmp.write(line) - continue - - changed = 1 - tmp.write("%s%s\n" % (match.group(1), match.group(3))) + changed = False + macros = [] + for line in open(latex_file, 'rb').readlines(): + if not pdf_output and line.startswith(b"\\documentclass"): + changed = True + line += b"\\PassOptionsToPackage{draft}{microtype}\n" + else: + match = def_re.match(line) + if match != None: + macroname = match.group(2) + if macroname in macros: + definecmd = match.group(1) + if definecmd == b"\\newcommandx": + changed = True + line = line.replace(definecmd, b"\\renewcommandx") + else: + macros.append(macroname) + tmp.write(line) if changed: copyfileobj(tmp, open(latex_file,"wb"), 1) @@ -193,15 +213,17 @@ def convert_to_ppm_format(pngtopnm, basename): # Returns a tuple of: # ps_pages: list of page indexes of pages containing PS literals +# pdf_pages: list of page indexes of pages requiring running pdflatex # page_count: total number of pages -# pages_parameter: parameter for dvipng to exclude pages with PostScript +# pages_parameter: parameter for dvipng to exclude pages with PostScript/PDF def find_ps_pages(dvi_file): # latex failed # FIXME: try with pdflatex if not os.path.isfile(dvi_file): error("No DVI output.") - # Check for PostScript specials in the dvi, badly supported by dvipng + # Check for PostScript specials in the dvi, badly supported by dvipng, + # and inclusion of PDF/PNG/JPG files. # This is required for correct rendering of PSTricks and TikZ dv2dt = find_exe_or_terminate(["dv2dt"]) dv2dt_call = '%s "%s"' % (dv2dt, dvi_file) @@ -209,33 +231,53 @@ def find_ps_pages(dvi_file): # The output from dv2dt goes to stdout dv2dt_status, dv2dt_output = run_command(dv2dt_call) psliteral_re = re.compile("^special[1-4] [0-9]+ '(\"|ps:)") - - # Parse the dtl file looking for PostScript specials. - # Pages using PostScript specials are recorded in ps_pages and then - # used to create a different LaTeX file for processing in legacy mode. + hyperref_re = re.compile("^special[1-4] [0-9]+ 'ps:.*/DEST pdfmark") + pdffile_re = re.compile("^special[1-4] [0-9]+ 'PSfile=.*\\.(pdf|png|jpg|jpeg|PDF|PNG|JPG|JPEG)") + + # Parse the dtl file looking for PostScript specials and pdflatex files. + # Pages using PostScript specials or pdflatex files are recorded in + # ps_pages or pdf_pages, respectively, and then used to create a + # different LaTeX file for processing in legacy mode. + # If hyperref is detected, the corresponding page is recorded in pdf_pages. page_has_ps = False + page_has_pdf = False page_index = 0 ps_pages = [] + pdf_pages = [] + ps_or_pdf_pages = [] for line in dv2dt_output.split("\n"): # New page if line.startswith("bop"): page_has_ps = False + page_has_pdf = False page_index += 1 # End of page - if line.startswith("eop") and page_has_ps: - # We save in a list all the PostScript pages - ps_pages.append(page_index) + if line.startswith("eop") and (page_has_ps or page_has_pdf): + # We save in a list all the PostScript/PDF pages + if page_has_ps: + ps_pages.append(page_index) + else: + pdf_pages.append(page_index) + ps_or_pdf_pages.append(page_index) if psliteral_re.match(line) != None: # Literal PostScript special detected! - page_has_ps = True + # If hyperref is detected, put this page on the pdf pages list + if hyperref_re.match(line) != None: + page_has_ps = False + page_has_pdf = True + else: + page_has_ps = True + elif pdffile_re.match(line) != None: + # Inclusion of pdflatex image file detected! + page_has_pdf = True # Create the -pp parameter for dvipng pages_parameter = "" - if len(ps_pages) > 0 and len(ps_pages) < page_index: - # Don't process Postscript pages with dvipng by selecting the + if len(ps_or_pdf_pages) > 0 and len(ps_or_pdf_pages) < page_index: + # Don't process Postscript/PDF pages with dvipng by selecting the # wanted pages through the -pp parameter. E.g., dvipng -pp 4-12,14,64 pages_parameter = " -pp " skip = True @@ -243,8 +285,8 @@ def find_ps_pages(dvi_file): # Use page ranges, as a list of pages could exceed command line # maximum length (especially under Win32) - for index in xrange(1, page_index + 1): - if (not index in ps_pages) and skip: + for index in range(1, page_index + 1): + if (not index in ps_or_pdf_pages) and skip: # We were skipping pages but current page shouldn't be skipped. # Add this page to -pp, it could stay alone or become the # start of a range. @@ -253,7 +295,7 @@ def find_ps_pages(dvi_file): last = index # We're not skipping anymore skip = False - elif (index in ps_pages) and (not skip): + elif (index in ps_or_pdf_pages) and (not skip): # We weren't skipping but current page should be skipped if last != index - 1: # If the start index of the range is the previous page @@ -268,10 +310,10 @@ def find_ps_pages(dvi_file): # Remove the trailing separator pages_parameter = pages_parameter.rstrip(",") # We've to manage the case in which the last page is closing a range - if (not index in ps_pages) and (not skip) and (last != index): + if (not index in ps_or_pdf_pages) and (not skip) and (last != index): pages_parameter += "-" + str(index) - return (ps_pages, page_index, pages_parameter) + return (ps_pages, pdf_pages, page_index, pages_parameter) def main(argv): # Set defaults. @@ -290,24 +332,23 @@ def main(argv): (opts, args) = getopt.gnu_getopt(argv[1:], "dhv", ["bibtex=", "bg=", "debug", "dpi=", "fg=", "help", "latex=", "lilypond", "lilypond-book=", "png", "ppm", "verbose"]) - except getopt.GetoptError, err: + except getopt.GetoptError as err: error("%s\n%s" % (err, usage(script_name))) opts.reverse() for opt, val in opts: if opt in ("-h", "--help"): - print usage(script_name) + print(usage(script_name)) sys.exit(0) elif opt == "--bibtex": bibtex = [val] elif opt == "--bg": bg_color = val elif opt in ("-d", "--debug"): - import lyxpreview_tools lyxpreview_tools.debug = True elif opt == "--dpi": try: - dpi = string.atoi(val) + dpi = int(val) except: error("Cannot convert %s to an integer value" % val) elif opt == "--fg": @@ -321,7 +362,6 @@ def main(argv): elif opt in ("--png", "--ppm"): output_format = opt[2:] elif opt in ("-v", "--verbose"): - import lyxpreview_tools lyxpreview_tools.verbose = True # Determine input file @@ -332,23 +372,41 @@ def main(argv): input_path = args[0] dir, latex_file = os.path.split(input_path) + # Check for the input file + if not os.path.exists(input_path): + error('File "%s" not found.' % input_path) + if len(dir) != 0: + os.chdir(dir) + + if lyxpreview_tools.verbose: + f_out = open('verbose.txt', 'a') + sys.stdout = f_out + sys.stderr = f_out + # Echo the settings + progress("Running Python %s" % str(sys.version_info[:3])) progress("Starting %s..." % script_name) + if os.name == "nt": + progress("Use win32_modules: %d" % lyxpreview_tools.use_win32_modules) progress("Output format: %s" % output_format) progress("Foreground color: %s" % fg_color) progress("Background color: %s" % bg_color) progress("Resolution (dpi): %s" % dpi) progress("File to process: %s" % input_path) - # Check for the input file - if not os.path.exists(input_path): - error('File "%s" not found.' % input_path) - if len(dir) != 0: - os.chdir(dir) + # For python > 2 convert strings to bytes + if not PY2: + fg_color = bytes(fg_color, 'ascii') + bg_color = bytes(bg_color, 'ascii') fg_color_dvipng = make_texcolor(fg_color, False) bg_color_dvipng = make_texcolor(bg_color, False) + # For python > 2 convert bytes to string + if not PY2: + fg_color_dvipng = fg_color_dvipng.decode('ascii') + bg_color_dvipng = bg_color_dvipng.decode('ascii') + # External programs used by the script. latex = find_exe_or_terminate(latex or latex_commands) bibtex = find_exe(bibtex or bibtex_commands) @@ -366,8 +424,9 @@ def main(argv): progress("Preprocess through lilypond-book: %s" % lilypond) progress("Altering the latex file for font size and colors") - # Omit font size specification in latex file. - fix_latex_file(latex_file) + # Make sure that multiple defined macros and the microtype package + # don't cause issues in the latex file. + fix_latex_file(latex_file, pdf_output) if lilypond: progress("Preprocess the latex file through %s" % lilypond_book) @@ -409,7 +468,12 @@ def main(argv): fg_color, bg_color, latex, pdf_output) # Compile the latex file. + error_pages = [] latex_status, latex_stdout = run_latex(latex, latex_file, bibtex) + latex_log = latex_file_re.sub(".log", latex_file) + if latex_status: + progress("Will try to recover from %s failure" % latex) + error_pages = check_latex_log(latex_log) # The dvi output file name dvi_file = latex_file_re.sub(".dvi", latex_file) @@ -428,21 +492,29 @@ def main(argv): error("No DVI or PDF output. %s failed." \ % (os.path.basename(latex))) - # Look for PS literals in DVI pages - # ps_pages: list of page indexes of pages containing PS literals + # Look for PS literals or inclusion of pdflatex files in DVI pages + # ps_pages: list of indexes of pages containing PS literals + # pdf_pages: list of indexes of pages requiring running pdflatex # page_count: total number of pages # pages_parameter: parameter for dvipng to exclude pages with PostScript - (ps_pages, page_count, pages_parameter) = find_ps_pages(dvi_file) + (ps_pages, pdf_pages, page_count, pages_parameter) = find_ps_pages(dvi_file) - # If all pages need PostScript, directly use the legacy method. + # If all pages need PostScript or pdflatex, directly use the legacy method. if len(ps_pages) == page_count: progress("Using the legacy conversion method (PostScript support)") return legacy_conversion_step1(latex_file, dpi, output_format, fg_color, bg_color, latex, pdf_output) + elif len(pdf_pages) == page_count: + progress("Using the legacy conversion method (PDF support)") + return legacy_conversion_step1(latex_file, dpi, output_format, fg_color, + bg_color, "pdflatex", True) + + # Retrieve resolution + resolution = extract_resolution(latex_log, dpi) # Run the dvi file through dvipng. dvipng_call = '%s -Ttight -depth -height -D %d -fg "%s" -bg "%s" %s "%s"' \ - % (dvipng, dpi, fg_color_dvipng, bg_color_dvipng, pages_parameter, dvi_file) + % (dvipng, resolution, fg_color_dvipng, bg_color_dvipng, pages_parameter, dvi_file) dvipng_status, dvipng_stdout = run_command(dvipng_call) if dvipng_status: @@ -477,6 +549,38 @@ def main(argv): join_metrics_and_rename(dvipng_metrics, legacy_metrics, ps_pages, original_bitmap, destination_bitmap) + # If some pages require running pdflatex pass them to legacy method + if len(pdf_pages) > 0: + # Create a new LaTeX file just for the snippets needing + # the legacy method + legacy_latex_file = latex_file_re.sub("_legacy.tex", latex_file) + filter_pages(latex_file, legacy_latex_file, pdf_pages) + + # Pass the new LaTeX file to the legacy method + progress("Pages %s require processing with pdflatex" % pdf_pages) + progress("Using the legacy conversion method (PDF support)") + legacy_status, legacy_metrics = legacy_conversion_step1(legacy_latex_file, + dpi, output_format, fg_color, bg_color, "pdflatex", True, True) + + # Now we need to mix metrics data from dvipng and the legacy method + original_bitmap = latex_file_re.sub("%d." + output_format, legacy_latex_file) + destination_bitmap = latex_file_re.sub("%d." + output_format, latex_file) + + # Join metrics from dvipng and legacy, and rename legacy bitmaps + join_metrics_and_rename(dvipng_metrics, legacy_metrics, pdf_pages, + original_bitmap, destination_bitmap) + + # Invalidate metrics for pages that produced errors + if len(error_pages) > 0: + error_count = 0 + for index in error_pages: + if index not in ps_pages and index not in pdf_pages: + dvipng_metrics.pop(index - 1) + dvipng_metrics.insert(index - 1, (index, -1.0)) + error_count += 1 + if error_count: + warning("Failed to produce %d preview snippet(s)" % error_count) + # Convert images to ppm format if necessary. if output_format == "ppm": convert_to_ppm_format(pngtopnm, latex_file_re.sub("", latex_file))