Mostly documentation for the pythonic part of instant preview.

Patch from Ale.
http://www.mail-archive.com/lyx-devel@lists.lyx.org/msg167220.html


git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@38256 a592a061-630c-0410-9148-cb99ea01b6c8
This commit is contained in:
Pavel Sanda 2011-04-05 14:54:38 +00:00
parent c9f520dd2a
commit f9f7c4a4bd
2 changed files with 202 additions and 132 deletions

View File

@ -15,7 +15,7 @@
# 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
@ -31,7 +31,7 @@
# 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.
@ -40,6 +40,7 @@
# * preview.sty;
# * dvips;
# * gs;
# * pdflatex (optional);
# * pnmcrop (optional).
# preview.sty is part of the preview-latex project
@ -47,11 +48,29 @@
# 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
@ -62,6 +81,8 @@ from lyxpreview_tools import copyfileobj, error, find_exe, \
# 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"\
@ -249,7 +270,6 @@ def legacy_conversion(argv, skipMetrics = False):
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.
@ -266,10 +286,53 @@ def legacy_conversion(argv, skipMetrics = False):
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)
@ -312,6 +375,7 @@ def legacy_conversion_step2(latex_file, dpi, output_format, skipMetrics = False)
# 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 ' \
@ -323,61 +387,31 @@ def legacy_conversion_step2(latex_file, dpi, output_format, skipMetrics = False)
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:

View File

@ -19,6 +19,7 @@
# * A latex executable;
# * preview.sty;
# * dvipng;
# * dv2dt;
# * pngtoppm (if outputing ppm format images).
# preview.sty and dvipng are part of the preview-latex project
@ -46,6 +47,22 @@
# * 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, \
@ -59,6 +76,8 @@ from lyxpreview_tools import copyfileobj, error, find_exe, \
# 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"\
@ -163,6 +182,87 @@ def convert_to_ppm_format(pngtopnm, basename):
ppm.write(p2p_stdout)
os.remove(png_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):
error("No DVI output.")
# Check for PostScript specials in the dvi, badly supported by dvipng
# This is required for correct rendering of PSTricks and TikZ
dv2dt = find_exe_or_terminate(["dv2dt"], path)
dv2dt_call = '%s "%s"' % (dv2dt, 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.
page_has_ps = False
page_index = 0
ps_pages = []
for line in dv2dt_output.split("\n"):
# New page
if line.startswith("bop"):
page_has_ps = 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 psliteral_re.match(line) != None:
# Literal PostScript special detected!
page_has_ps = 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
# wanted pages through the -pp parameter. E.g., dvipng -pp 4-12,14,64
pages_parameter = " -pp "
skip = True
last = -1
# 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:
# 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.
pages_parameter += str(index)
# Save the starting index to avoid things such as "11-11"
last = index
# We're not skipping anymore
skip = False
elif (index in ps_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
# then it's not a range
pages_parameter += "-" + str(index - 1)
# Add a separator
pages_parameter += ","
# Now we're skipping
skip = True
# 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):
pages_parameter += "-" + str(index)
return (ps_pages, page_index, pages_parameter)
def main(argv):
# Parse and manipulate the command line arguments.
@ -183,7 +283,6 @@ def main(argv):
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:
@ -222,84 +321,28 @@ def main(argv):
# The dvi output file name
dvi_file = latex_file_re.sub(".dvi", latex_file)
# latex failed
# FIXME: try with pdflatex
# If there's no DVI output, look for PDF and go to legacy or fail
if not os.path.isfile(dvi_file):
error("No DVI output.")
# Check for PostScript specials in the dvi, badly supported by dvipng
# This is required for correct rendering of PSTricks and TikZ
dv2dt = find_exe_or_terminate(["dv2dt"], path)
dv2dt_call = '%s "%s"' % (dv2dt, 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:)")
# 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)))
# 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.
page_has_ps = False
page_index = 0
ps_pages = []
for line in dv2dt_output.split("\n"):
# New page
if line.startswith("bop"):
page_has_ps = 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 psliteral_re.match(line) != None:
# Literal PostScript special detected!
page_has_ps = True
pages_parameter = ""
# 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 len(ps_pages) == page_index:
# All pages need PostScript, so directly use the legacy method.
# 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)
elif len(ps_pages) > 0:
# 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 "
skip = True
last = -1
# 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:
# 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.
pages_parameter += str(index)
# Save the starting index to avoid things such as "11-11"
last = index
# We're not skipping anymore
skip = False
elif (index in ps_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
# then it's not a range
pages_parameter += "-" + str(index - 1)
# Add a separator
pages_parameter += ","
# Now we're skipping
skip = True
# 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):
pages_parameter += "-" + str(index)
# Run the dvi file through dvipng.
dvipng_call = '%s -Ttight -depth -height -D %d -fg "%s" -bg "%s" %s "%s"' \
@ -307,14 +350,17 @@ def main(argv):
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)
@ -326,9 +372,6 @@ def main(argv):
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)
@ -336,13 +379,6 @@ def main(argv):
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))