# Paul A. Rubin, rubin@msu.edu.
# This script takes a LaTeX file and generates a collection of
-# ppm image files, one per previewed snippet.
+# png or ppm image files, one per previewed snippet.
# Example usage:
# legacy_lyxpreview2bitmap.py 0lyxpreview.tex 128 ppm 000000 faf0e6
# Decomposing TEXFILE's name as DIR/BASE.tex, this script will,
# if executed successfully, leave in DIR:
# * a (possibly large) number of image files with names
-# like BASE[0-9]+.ppm
+# like BASE[0-9]+.(ppm|png)
# * a file BASE.metrics, containing info needed by LyX to position
# the images correctly on the screen.
# * preview.sty;
# * dvips;
# * gs;
+# * pdflatex (optional);
# * pnmcrop (optional).
# preview.sty is part of the preview-latex project
# Alternatively, it can be obtained from
# CTAN/support/preview-latex/
-# The script uses the deprecated dvi->ps->ppm conversion route.
-# If possible, please grab 'dvipng'; it's faster and more robust.
-# If you have it then this script will not be invoked by
-# lyxpreview2bitmap.py.
-# Warning: this legacy support will be removed one day...
+# What does this script do?
+# [legacy_conversion]
+# 1) Call latex to create a DVI file from LaTeX
+# [legacy_conversion_step2]
+# 2) Call dvips to create one PS file for each DVI page
+# 3) If dvips fails look for PDF and call gs to produce bitmaps
+# 4) Otherwise call gs on each PostScript file to produce bitmaps
+# [legacy_conversion_pdflatex]
+# 5) Keep track of pages on which gs failed and pass them to pdflatex
+# 6) Call gs on the PDF output from pdflatex to produce bitmaps
+# 7) Extract and write to file (or return to lyxpreview2bitmap)
+# metrics from both methods (standard and pdflatex)
+
+# The script uses the old dvi->ps->png conversion route,
+# which is good when using PSTricks, TikZ or other packages involving
+# PostScript literals (steps 1, 2, 4).
+# This script also generates bitmaps from PDF created by a call to
+# lyxpreview2bitmap.py passing "pdflatex" to the CONVERTER parameter
+# (step 3).
+# Finally, there's also has a fallback method based on pdflatex, which
+# is required in certain cases, if hyperref is active for instance,
+# (step 5, 6).
+# If possible, dvipng should be used, as it's much faster.
import glob, os, pipes, re, string, sys
# Pre-compiled regular expression.
latex_file_re = re.compile("\.tex$")
+# PATH environment variable
+path = string.split(os.environ["PATH"], os.pathsep)
def usage(prog_name):
return "Usage: %s <latex file> <dpi> ppm <fg color> <bg color>\n"\
bg_color_gr = make_texcolor(argv[5], True)
# External programs used by the script.
- path = string.split(os.environ["PATH"], os.pathsep)
latex = find_exe_or_terminate(latex_commands, path)
# Move color information into the latex file.
return legacy_conversion_step2(latex_file, dpi, output_format, skipMetrics)
+# Creates a new LaTeX file from the original with pages specified in
+# failed_pages, pass it through pdflatex and updates the metrics
+# from the standard legacy route
+def legacy_conversion_pdflatex(latex_file, failed_pages, legacy_metrics, gs,
+ gs_device, gs_ext, alpha, resolution, output_format):
+
+ # Search for pdflatex executable
+ pdflatex = find_exe(["pdflatex"], path)
+ if pdflatex == None:
+ warning("Can't find pdflatex. Some pages failed with all the possible routes.")
+ else:
+ # Create a new LaTeX file from the original but only with failed pages
+ pdf_latex_file = latex_file_re.sub("_pdflatex.tex", latex_file)
+ filter_pages(latex_file, pdf_latex_file, failed_pages)
+
+ # pdflatex call
+ pdflatex_call = '%s "%s"' % (pdflatex, pdf_latex_file)
+ pdflatex_status, pdflatex_stdout = run_command(pdflatex_call)
+
+ pdf_file = latex_file_re.sub(".pdf", pdf_latex_file)
+
+ # GhostScript call to produce bitmaps
+ gs_call = '%s -dNOPAUSE -dBATCH -dSAFER -sDEVICE=%s ' \
+ '-sOutputFile="%s%%d.%s" ' \
+ '-dGraphicsAlphaBit=%d -dTextAlphaBits=%d ' \
+ '-r%f "%s"' \
+ % (gs, gs_device, latex_file_re.sub("", pdf_latex_file), \
+ gs_ext, alpha, alpha, resolution, pdf_file)
+ gs_status, gs_stdout = run_command(gs_call)
+ if gs_status != None:
+ # Give up!
+ warning("Some pages failed with all the possible routes")
+ else:
+ # We've done it!
+ pdf_log_file = latex_file_re.sub(".log", pdf_latex_file)
+ pdf_metrics = legacy_extract_metrics_info(pdf_log_file)
+
+ original_bitmap = latex_file_re.sub("%d." + output_format, pdf_latex_file)
+ destination_bitmap = latex_file_re.sub("%d." + output_format, latex_file)
+
+ # Join the metrics with the those from dvips and rename the bitmap images
+ join_metrics_and_rename(legacy_metrics, pdf_metrics, failed_pages,
+ original_bitmap, destination_bitmap)
+
def legacy_conversion_step2(latex_file, dpi, output_format, skipMetrics = False):
# External programs used by the script.
- path = string.split(os.environ["PATH"], os.pathsep)
dvips = find_exe_or_terminate(["dvips"], path)
gs = find_exe_or_terminate(["gswin32c", "gs"], path)
pnmcrop = find_exe(["pnmcrop"], path)
# Generate the bitmap images
if dvips_failed:
+ # dvips failed, maybe there's a PDF, try to produce bitmaps
gs_call = '%s -dNOPAUSE -dBATCH -dSAFER -sDEVICE=%s ' \
'-sOutputFile="%s%%d.%s" ' \
'-dGraphicsAlphaBit=%d -dTextAlphaBits=%d ' \
if gs_status != None:
error("Failed: %s %s" % (os.path.basename(gs), ps_file))
else:
+ # Model for calling gs on each file
gs_call = '%s -dNOPAUSE -dBATCH -dSAFER -sDEVICE=%s ' \
'-sOutputFile="%s%%d.%s" ' \
'-dGraphicsAlphaBit=%d -dTextAlphaBits=%d ' \
'-r%f "%%s"' \
% (gs, gs_device, latex_file_re.sub("", latex_file), \
gs_ext, alpha, alpha, resolution)
+
i = 0
+ # Collect all the PostScript files (like *.001, *.002, ...)
ps_files = glob.glob("%s.[0-9][0-9][0-9]" % latex_file_re.sub("", latex_file))
ps_files.sort()
- # Call GhostScript for each page
+ # Call GhostScript for each file
for file in ps_files:
i = i + 1
gs_status, gs_stdout = run_command(gs_call % (i, file))
if gs_status != None:
# gs failed, keep track of this
failed_pages.append(i)
-
# Pass failed pages to pdflatex
if len(failed_pages) > 0:
- pdflatex = find_exe(["pdflatex"], path)
- if pdflatex != None:
- # Create a new LaTeX file from the original but only with failed pages
- pdf_latex_file = latex_file_re.sub("_pdflatex.tex", latex_file)
- filter_pages(latex_file, pdf_latex_file, failed_pages)
-
- # pdflatex call
- pdflatex_call = '%s "%s"' % (pdflatex, pdf_latex_file)
- pdflatex_status, pdflatex_stdout = run_command(pdflatex_call)
-
- pdf_file = latex_file_re.sub(".pdf", pdf_latex_file)
-
- # GhostScript call to produce bitmaps
- gs_call = '%s -dNOPAUSE -dBATCH -dSAFER -sDEVICE=%s ' \
- '-sOutputFile="%s%%d.%s" ' \
- '-dGraphicsAlphaBit=%d -dTextAlphaBits=%d ' \
- '-r%f "%s"' \
- % (gs, gs_device, latex_file_re.sub("", pdf_latex_file), \
- gs_ext, alpha, alpha, resolution, pdf_file)
-
- gs_status, gs_stdout = run_command(gs_call)
- if gs_status != None:
- # Give up!
- warning("Some pages failed with all the possible routes")
- else:
- # We've done it!
- pdf_log_file = latex_file_re.sub(".log", pdf_latex_file)
- pdf_metrics = legacy_extract_metrics_info(pdf_log_file)
-
- original_bitmap = latex_file_re.sub("%d." + output_format, pdf_latex_file)
- destination_bitmap = latex_file_re.sub("%d." + output_format, latex_file)
-
- # Join the metrics with the those from dvips and rename the bitmap images
- join_metrics_and_rename(legacy_metrics, pdf_metrics, failed_pages, original_bitmap, destination_bitmap)
+ legacy_conversion_pdflatex(latex_file, failed_pages, legacy_metrics, gs,
+ gs_device, gs_ext, alpha, resolution, output_format)
# Crop the images
if pnmcrop != None:
# * A latex executable;
# * preview.sty;
# * dvipng;
+# * dv2dt;
# * pngtoppm (if outputing ppm format images).
# preview.sty and dvipng are part of the preview-latex project
# * a file BASE.metrics, containing info needed by LyX to position
# the images correctly on the screen.
+# What does this script do?
+# 1) Call latex/pdflatex/xelatex/whatever (CONVERTER parameter)
+# 2) If the output is a PDF fallback to legacy
+# 3) Otherwise check each page of the DVI (with dv2dt) looking for
+# PostScript literals, not well supported by dvipng. Pages
+# containing them are passed to the legacy method in a new LaTeX file.
+# 4) Call dvipng on the pages without PS literals
+# 5) Join metrics info coming from both methods (legacy and dvipng)
+# and write them to file
+
+# dvipng is fast but gives problem in several cases, like with
+# PSTricks, TikZ and other packages using PostScript literals
+# for all these cases the legacy route is taken (step 3).
+# 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 glob, os, re, string, sys
from legacy_lyxpreview2ppm import legacy_conversion, \
# Pre-compiled regular expressions.
latex_file_re = re.compile("\.tex$")
+# PATH environment variable
+path = string.split(os.environ["PATH"], os.pathsep)
def usage(prog_name):
return "Usage: %s <format> <latex file> <dpi> <fg color> <bg color>\n"\
ppm.write(p2p_stdout)
os.remove(png_file)
-
-def main(argv):
- # Parse and manipulate the command line arguments.
- if len(argv) != 6 and len(argv) != 7:
- error(usage(argv[0]))
-
- output_format = string.lower(argv[1])
-
- dir, latex_file = os.path.split(argv[2])
- if len(dir) != 0:
- os.chdir(dir)
-
- dpi = string.atoi(argv[3])
- fg_color = make_texcolor(argv[4], False)
- bg_color = make_texcolor(argv[5], False)
-
- fg_color_gr = make_texcolor(argv[4], True)
- bg_color_gr = make_texcolor(argv[5], True)
-
- # External programs used by the script.
- path = string.split(os.environ["PATH"], os.pathsep)
- if len(argv) == 7:
- latex = argv[6]
- else:
- latex = find_exe_or_terminate(["latex", "pplatex", "platex", "latex2e"], path)
-
- # This can go once dvipng becomes widespread.
- dvipng = find_exe(["dvipng"], path)
- if dvipng == None:
- # The data is input to legacy_conversion in as similar
- # as possible a manner to that input to the code used in
- # LyX 1.3.x.
- vec = [ argv[0], argv[2], argv[3], argv[1], argv[4], argv[5], latex ]
- return legacy_conversion(vec)
-
- pngtopnm = ""
- if output_format == "ppm":
- pngtopnm = find_exe_or_terminate(["pngtopnm"], path)
-
- # Move color information for PDF into the latex file.
- if not color_pdf(latex_file, bg_color_gr, fg_color_gr):
- error("Unable to move color info into the latex file")
-
- # Compile the latex file.
- latex_call = '%s "%s"' % (latex, latex_file)
-
- latex_status, latex_stdout = run_command(latex_call)
- if latex_status != None:
- warning("%s had problems compiling %s" \
- % (os.path.basename(latex), latex_file))
-
- if latex == "xelatex":
- warning("Using XeTeX")
- # FIXME: skip unnecessary dvips trial in legacy_conversion_step2
- return legacy_conversion_step2(latex_file, dpi, output_format)
-
- # The dvi output file name
- dvi_file = latex_file_re.sub(".dvi", latex_file)
-
+# Returns a tuple of:
+# ps_pages: list of page indexes of pages containing PS literals
+# page_count: total number of pages
+# pages_parameter: parameter for dvipng to exclude pages with PostScript
+def find_ps_pages(dvi_file):
# latex failed
# FIXME: try with pdflatex
if not os.path.isfile(dvi_file):
if psliteral_re.match(line) != None:
# Literal PostScript special detected!
page_has_ps = True
-
- pages_parameter = ""
- if len(ps_pages) == page_index:
- # All pages need PostScript, so directly use the legacy method.
- vec = [argv[0], argv[2], argv[3], argv[1], argv[4], argv[5], latex]
- return legacy_conversion(vec)
- elif len(ps_pages) > 0:
+ # 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
# wanted pages through the -pp parameter. E.g., dvipng -pp 4-12,14,64
pages_parameter = " -pp "
if (not index in ps_pages) and (not skip) and (last != index):
pages_parameter += "-" + str(index)
+ return (ps_pages, page_index, pages_parameter)
+
+def main(argv):
+ # Parse and manipulate the command line arguments.
+ if len(argv) != 6 and len(argv) != 7:
+ error(usage(argv[0]))
+
+ output_format = string.lower(argv[1])
+
+ dir, latex_file = os.path.split(argv[2])
+ if len(dir) != 0:
+ os.chdir(dir)
+
+ dpi = string.atoi(argv[3])
+ fg_color = make_texcolor(argv[4], False)
+ bg_color = make_texcolor(argv[5], False)
+
+ fg_color_gr = make_texcolor(argv[4], True)
+ bg_color_gr = make_texcolor(argv[5], True)
+
+ # External programs used by the script.
+ if len(argv) == 7:
+ latex = argv[6]
+ else:
+ latex = find_exe_or_terminate(["latex", "pplatex", "platex", "latex2e"], path)
+
+ # This can go once dvipng becomes widespread.
+ dvipng = find_exe(["dvipng"], path)
+ if dvipng == None:
+ # The data is input to legacy_conversion in as similar
+ # as possible a manner to that input to the code used in
+ # LyX 1.3.x.
+ vec = [ argv[0], argv[2], argv[3], argv[1], argv[4], argv[5], latex ]
+ return legacy_conversion(vec)
+
+ pngtopnm = ""
+ if output_format == "ppm":
+ pngtopnm = find_exe_or_terminate(["pngtopnm"], path)
+
+ # Move color information for PDF into the latex file.
+ if not color_pdf(latex_file, bg_color_gr, fg_color_gr):
+ error("Unable to move color info into the latex file")
+
+ # Compile the latex file.
+ latex_call = '%s "%s"' % (latex, latex_file)
+
+ latex_status, latex_stdout = run_command(latex_call)
+ if latex_status != None:
+ warning("%s had problems compiling %s" \
+ % (os.path.basename(latex), latex_file))
+
+ if latex == "xelatex":
+ warning("Using XeTeX")
+ # FIXME: skip unnecessary dvips trial in legacy_conversion_step2
+ return legacy_conversion_step2(latex_file, dpi, output_format)
+
+ # The dvi output file name
+ dvi_file = latex_file_re.sub(".dvi", latex_file)
+
+ # If there's no DVI output, look for PDF and go to legacy or fail
+ if not os.path.isfile(dvi_file):
+ # No DVI, is there a PDF?
+ pdf_file = latex_file_re.sub(".pdf", latex_file)
+ if os.path.isfile(pdf_file):
+ warning("%s produced a PDF output, fallback to legacy." % \
+ (os.path.basename(latex)))
+ return legacy_conversion_step2(latex_file, dpi, output_format)
+ else:
+ 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
+ # 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)
+
+ # If all pages need PostScript, directly use the legacy method.
+ if len(ps_pages) == page_count:
+ vec = [argv[0], argv[2], argv[3], argv[1], argv[4], argv[5], latex]
+ return legacy_conversion(vec)
+
# Run the dvi file through dvipng.
dvipng_call = '%s -Ttight -depth -height -D %d -fg "%s" -bg "%s" %s "%s"' \
% (dvipng, dpi, fg_color, bg_color, pages_parameter, dvi_file)
dvipng_status, dvipng_stdout = run_command(dvipng_call)
if dvipng_status != None:
- warning("%s failed to generate images from %s ... looking for PDF" \
+ warning("%s failed to generate images from %s... fallback to legacy method" \
% (os.path.basename(dvipng), dvi_file))
# FIXME: skip unnecessary dvips trial in legacy_conversion_step2
return legacy_conversion_step2(latex_file, dpi, output_format)
- dvipng_metrics = []
+ # Extract metrics info from dvipng_stdout.
+ metrics_file = latex_file_re.sub(".metrics", latex_file)
+ dvipng_metrics = extract_metrics_info(dvipng_stdout)
+
+ # If some pages require PostScript pass them to legacy method
if len(ps_pages) > 0:
- # Some pages require PostScript.
# Create a new LaTeX file just for the snippets needing
# the legacy method
legacy_latex_file = latex_file_re.sub("_legacy.tex", latex_file)
legacy_metrics = legacy_conversion(vec, True)[1]
# Now we need to mix metrics data from dvipng and the legacy method
- metrics_file = latex_file_re.sub(".metrics", latex_file)
- dvipng_metrics = extract_metrics_info(dvipng_stdout)
-
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_and_rename(dvipng_metrics, legacy_metrics, ps_pages,
original_bitmap, destination_bitmap)
- else:
- # Extract metrics info from dvipng_stdout.
- # In this case we just used dvipng, so no special metrics
- # handling is needed.
- metrics_file = latex_file_re.sub(".metrics", latex_file)
- dvipng_metrics = extract_metrics_info(dvipng_stdout)
-
# Convert images to ppm format if necessary.
if output_format == "ppm":
convert_to_ppm_format(pngtopnm, latex_file_re.sub("", latex_file))