The patch, attached, changes the method used to generate previews from
latex->dvi->ps->ppm using latex, dvips, gs and pnmcrop
to
latex->dvi->png using latex, dvipng
The effects of this are three-fold:
1. Much faster rendering of previews.
2. Qt users get a reduced disk usage.
3. Less obtuse code (No PostScript specials).
I'd like to commit this patch with the understanding that people
wanting to use the preview feature in future should go grab Jan-Åke
Larsson's 'dvipng'. See http://tinyurl.com/3ekdn
You'll need to add these to your preferences file:
\converter "lyxpreview" "ppm" "python $$s/lyxpreview2bitmap.py" ""
\converter "lyxpreview" "png" "python $$s/lyxpreview2bitmap.py" ""
Jose, could you have a quick look at the python script. In particular,
I've moved to use of popen and the pipes module. I could get rid of
this latter dependency, but for reasons I don't understand, the
alternative implementation, also below, leads to the ppm file having
a strange background colour. Any ideas?
def run_command(cmd):
handle = os.popen(cmd, 'r')
cmd_stdout = ""
cmd_stdout = handle.readlines()
cmd_status = handle.close()
return cmd_status, cmd_stdout
def convert_to_ppm_format(pngtopnm, basename):
png_file_re = re.compile("\.png$")
t = pipes.Template()
t.append("%s $IN > $OUT" % pngtopnm, 'ff')
for png_file in glob.glob("%s*.png" % basename):
ppm_file = png_file_re.sub(".ppm", png_file)
if t.copy(png_file, ppm_file) != 0:
error("Unable to convert %s to ppm format" % png_file)
os.remove(png_file)
Alternative:
def convert_to_ppm_format(pngtopnm, basename):
png_file_re = re.compile("\.png$")
for png_file in glob.glob("%s*.png" % basename):
ppm_file = png_file_re.sub(".ppm", png_file)
p2p_cmd = "%s %s" % (pngtopnm, png_file)
p2p_status, p2p_stdout = run_command(p2p_cmd)
if p2p_status != None:
error("Unable to convert %s to ppm format" % png_file)
ppm = open(ppm_file, 'w')
ppm.write(string.join(p2p_stdout))
os.remove(png_file)
--
Angus
Index: lib/scripts/lyxpreview2bitmap.py
===================================================================
RCS file: lib/scripts/lyxpreview2bitmap.py
diff -N lib/scripts/lyxpreview2bitmap.py
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ lib/scripts/lyxpreview2bitmap.py 15 Apr 2004 17:04:45 -0000
@@ -0,0 +1,185 @@
+#! /usr/bin/env python
+
+# file lyxpreview2bitmap.py
+# This file is part of LyX, the document processor.
+# Licence details can be found in the file COPYING.
+
+# author Angus Leeming
+# with much advice from members of the preview-latex project,
+# David Kastrup, [EMAIL PROTECTED] and
+# Jan-Ã…ke Larsson, [EMAIL PROTECTED]
+
+# Full author contact details are available in file CREDITS
+
+# This script takes a LaTeX file and generates a collection of
+# png or ppm image files, one per previewed snippet.
+
+# Pre-requisites:
+# * A latex executable;
+# * preview.sty;
+# * dvipng;
+# * pngtoppm (if outputing ppm format images).
+
+# preview.sty and dvipng are part of the preview-latex project
+# http://preview-latex.sourceforge.net/
+
+# preview.sty can alternatively be obtained from
+# CTAN/macros/latex/contrib/supported/preview/
+
+# Example usage:
+# lyxpreview2bitmap.py png 0lyxpreview.tex 128 000000 faf0e6
+
+# This script takes five arguments:
+# FORMAT: either 'png' or 'ppm'. The desired output format.
+# TEXFILE: the name of the .tex file to be converted.
+# DPI: a scale factor, passed to dvipng.
+# FG_COLOR: the foreground color as a hexadecimal string, eg '000000'.
+# BG_COLOR: the background color as a hexadecimal string, eg '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]+.png
+# * a file BASE.metrics, containing info needed by LyX to position
+# the images correctly on the screen.
+
+import glob, os, pipes, re, string, sys
+
+
+# Pre-compiled regular expressions.
+hexcolor_re = re.compile("^[0-9a-fA-F]{6}$")
+latex_file_re = re.compile("\.tex$")
+
+
+def usage(prog_name):
+ return "Usage: %s <latex file> <dpi> <fg color> <bg color>\n"\
+ "\twhere the colors are hexadecimal strings, eg 'faf0e6'"\
+ % prog_name
+
+
+def error(message):
+ sys.stderr.write(message + '\n')
+ sys.exit(1)
+
+
+def find_exe(candidates, path):
+ for prog in candidates:
+ for directory in path:
+ full_path = os.path.join(directory, prog)
+ if os.access(full_path, os.X_OK):
+ return full_path
+
+ return None
+
+
+def find_exe_or_terminate(candidates, path):
+ exe = find_exe(candidates, path)
+ if exe == None:
+ error("Unable to find executable from '%s'" % string.join(candidates))
+
+ return exe
+
+
+def run_command(cmd):
+ handle = os.popen(cmd, 'r')
+ cmd_stdout = ""
+ cmd_stdout = handle.readlines()
+ cmd_status = handle.close()
+
+ return cmd_status, cmd_stdout
+
+
+def make_texcolor(hexcolor):
+ # Test that the input string contains 6 hexadecimal chars.
+ if not hexcolor_re.match(hexcolor):
+ error("Cannot convert color '%s'" % hexcolor)
+
+ red = float(string.atoi(hexcolor[0:2], 16)) / 255.0
+ green = float(string.atoi(hexcolor[2:4], 16)) / 255.0
+ blue = float(string.atoi(hexcolor[4:6], 16)) / 255.0
+ return "rgb %f %f %f" % (red, green, blue)
+
+
+def extract_metrics_info(dvipng_stdout, metrics_file):
+ metrics = open(metrics_file, 'w')
+ metrics_re = re.compile("\[([0-9]+) depth=([0-9]+) height=([0-9]+)\]")
+ success = 0
+ for line in dvipng_stdout:
+ pos = 0
+ while 1:
+ match = metrics_re.match(line, pos)
+ if match == None:
+ break
+ success = 1
+ metrics.write("Snippet %s %s %s\n" % match.groups())
+ pos = match.end(3) + 2
+
+ return success
+
+
+def convert_to_ppm_format(pngtopnm, basename):
+ png_file_re = re.compile("\.png$")
+
+ t = pipes.Template()
+ t.append("%s $IN > $OUT" % pngtopnm, 'ff')
+
+ for png_file in glob.glob("%s*.png" % basename):
+ ppm_file = png_file_re.sub(".ppm", png_file)
+
+ if t.copy(png_file, ppm_file) != 0:
+ error("Unable to convert %s to ppm format" % png_file)
+
+ os.remove(png_file)
+
+
+def main(argv):
+ # Parse and manipulate the command line arguments.
+ if len(argv) != 6:
+ 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 = int(argv[3])
+ fg_color = make_texcolor(argv[4])
+ bg_color = make_texcolor(argv[5])
+
+ # External programs used by the script.
+ path = string.split(os.getenv("PATH"), os.pathsep)
+ latex = find_exe_or_terminate(["pplatex", "latex2e", "latex"], path)
+ dvipng = find_exe_or_terminate(["dvipng"], path)
+ pngtopnm = ""
+ if output_format == "ppm":
+ pngtopnm = find_exe_or_terminate(["pngtopnm"], path)
+
+ # Compile the latex file.
+ latex_call = "%s %s" % (latex, latex_file)
+
+ latex_status, latex_stdout = run_command(latex_call)
+ if latex_status != None:
+ error("%s failed to compile %s" % (latex, latex_file))
+
+ # Run the dvi file through dvipng.
+ dvi_file = latex_file_re.sub(".dvi", latex_file)
+ dvipng_call = "%s -Ttight -depth -height -D %d -fg '%s' -bg '%s' %s" \
+ % (dvipng, dpi, fg_color, bg_color, dvi_file)
+
+ dvipng_status, dvipng_stdout = run_command(dvipng_call)
+ if dvipng_status != None:
+ error("%s failed to generate images from %s" % (dvipng, dvi_file))
+
+ # Extract metrics info from dvipng_stdout.
+ metrics_file = latex_file_re.sub(".metrics", latex_file)
+ if not extract_metrics_info(dvipng_stdout, metrics_file):
+ error("Failed to extract metrics info from dvipng")
+
+ # Convert images to ppm format if necessary
+ if output_format == "ppm":
+ convert_to_ppm_format(pngtopnm, latex_file_re.sub("", latex_file))
+
+
+if __name__ == "__main__":
+ main(sys.argv)
Index: src/graphics/PreviewLoader.C
===================================================================
RCS file: /usr/local/lyx/cvsroot/lyx-devel/src/graphics/PreviewLoader.C,v
retrieving revision 1.76
diff -u -p -r1.76 PreviewLoader.C
--- src/graphics/PreviewLoader.C 13 Apr 2004 13:10:33 -0000 1.76
+++ src/graphics/PreviewLoader.C 15 Apr 2004 17:04:46 -0000
@@ -12,6 +12,7 @@
#include "PreviewLoader.h"
#include "PreviewImage.h"
+#include "GraphicsCache.h"
#include "buffer.h"
#include "converter.h"
@@ -492,10 +493,12 @@ void PreviewLoader::Impl::startLoading()
// The conversion command.
ostringstream cs;
- cs << pconverter_->command << ' ' << latexfile << ' '
- << int(font_scaling_factor_) << ' ' << pconverter_->to;
+ cs << pconverter_->command << ' ' << pconverter_->to << ' '
+ << latexfile << ' ' << int(font_scaling_factor_) << ' '
+ << lyx_gui::hexname(LColor::preview) << ' '
+ << lyx_gui::hexname(LColor::background);
- string const command = "sh " + support::LibScriptSearch(cs.str());
+ string const command = support::LibScriptSearch(cs.str());
// Initiate the conversion from LaTeX to bitmap images files.
support::Forkedcall::SignalTypePtr
@@ -616,21 +619,8 @@ void PreviewLoader::Impl::dumpPreamble(o
// Use the preview style file to ensure that each snippet appears on a
// fresh page.
os << "\n"
- << "\\usepackage[active,delayed,dvips,tightpage,showlabels,lyx]{preview}\n"
+ << "\\usepackage[active,delayed,dvips,showlabels,lyx]{preview}\n"
<< "\n";
-
- // This piece of PostScript magic ensures that the foreground and
- // background colors are the same as the LyX screen.
- string fg = lyx_gui::hexname(LColor::preview);
- if (fg.empty()) fg = "000000";
-
- string bg = lyx_gui::hexname(LColor::background);
- if (bg.empty()) bg = "ffffff";
-
- os << "\\AtBeginDocument{\\AtBeginDvi{%\n"
- << "\\special{!userdict begin/bop-hook{//bop-hook exec\n"
- << '<' << fg << bg << ">{255 div}forall setrgbcolor\n"
- << "clippath fill setrgbcolor}bind def end}}}\n";
}
@@ -667,11 +657,14 @@ Converter const * setConverter()
{
string const from = "lyxpreview";
- Formats::FormatList::const_iterator it = formats.begin();
- Formats::FormatList::const_iterator end = formats.end();
+ typedef vector<string> FmtList;
+ typedef lyx::graphics::Cache GCache;
+ FmtList const loadableFormats = GCache::get().loadableFormats();
+ FmtList::const_iterator it = loadableFormats.begin();
+ FmtList::const_iterator const end = loadableFormats.end();
for (; it != end; ++it) {
- string const to = it->name();
+ string const to = *it;
if (from == to)
continue;
@@ -712,56 +705,35 @@ void setAscentFractions(vector<double> &
bool error = false;
- // Tightpage dimensions affect all subsequent dimensions
- int tp_ascent;
- int tp_descent;
-
- int snippet_counter = 0;
+ int snippet_counter = 1;
while (!in.eof()) {
// Expecting lines of the form
- // Preview: Tightpage tp_bl_x tp_bl_y tp_tr_x tp_tr_y
- // Preview: Snippet id ascent descent width
- string preview;
- string type;
- in >> preview >> type;
+ // Snippet id descent ascent
+ string snippet;
+ int id, ascent, descent;
+ in >> snippet >> id >> descent >> ascent;
if (!in.good())
// eof after all
break;
- error = preview != "Preview:"
- || (type != "Tightpage" && type != "Snippet");
+ error = snippet != "Snippet";
+ if (error)
+ break;
+
+ error = id != snippet_counter;
if (error)
break;
- if (type == "Tightpage") {
- int dummy;
- in >> dummy >> tp_descent >> dummy >> tp_ascent;
-
- error = !in.good();
- if (error)
- break;
-
- } else {
- int dummy;
- int snippet_id;
- int ascent;
- int descent;
- in >> snippet_id >> ascent >> descent >> dummy;
-
- error = !in.good() || ++snippet_counter != snippet_id;
- if (error)
- break;
-
- double const a = ascent + tp_ascent;
- double const d = descent - tp_descent;
-
- if (!support::float_equal(a + d, 0, 0.1))
- *it = a / (a + d);
-
- if (++it == end)
- break;
- }
+ double const a = ascent;
+ double const d = descent;
+
+ if ((ascent + descent) != 0)
+ *it = a / (a + d);
+
+ ++snippet_counter;
+ if (++it == end)
+ break;
}
if (error) {