Rework preview generation to use Jan-��ke Larsson's dvipng.

git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@8655 a592a061-630c-0410-9148-cb99ea01b6c8
This commit is contained in:
Angus Leeming 2004-04-16 14:34:41 +00:00
parent 7b511275f6
commit 8be99f7b3b
5 changed files with 241 additions and 290 deletions

View File

@ -1,3 +1,9 @@
2004-04-15 Angus Leeming <leeming@lyx.org>
* scripts/lyxpreview2bitmap.sh: removed.
* scripts/lyxpreview2bitmap.py: added. Uses Jan-Åke Larsson's dvipng
to perform the conversion.
2004-04-12 Georg Baum <Georg.Baum@post.rwth-aachen.de>
* configure.m4: merge \viewer and \format. Add editor to \format

196
lib/scripts/lyxpreview2bitmap.py Executable file
View File

@ -0,0 +1,196 @@
#! /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, dak@gnu.org and
# Jan-Åke Larsson, jalar@mai.liu.se.
# 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/support/preview-latex/
# 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, 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 = handle.read()
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
pos = 0
while 1:
match = metrics_re.search(dvipng_stdout, pos)
if match == None:
break
success = 1
# Calculate the 'ascent fraction'.
descent = string.atof(match.group(2))
ascent = string.atof(match.group(3))
frac = 0.5
if abs(ascent + descent) > 0.1:
frac = ascent / (ascent + descent)
metrics.write("Snippet %s %f\n" % (match.group(1), frac))
pos = match.end(3) + 2
return success
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(p2p_stdout)
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 = string.atoi(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" \
% (os.path.basename(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" \
% (os.path.basename(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)

View File

@ -1,231 +0,0 @@
#! /bin/sh
# file lyxpreview2bitmap.sh
# 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 David Kastrup, david.kastrup@t-online.de.
#
# Full author contact details are available in file CREDITS
# This script takes a LaTeX file and generates bitmap image files,
# one per page.
# The idea is to use it with preview.sty from the preview-latex project
# (http://preview-latex.sourceforge.net/) to create small bitmap
# previews of things like math equations.
# preview.sty can be obtained from
# CTAN/macros/latex/contrib/supported/preview.
# This script takes three arguments:
# TEXFILE: the name of the .tex file to be converted.
# SCALEFACTOR: a scale factor, used to ascertain the resolution of the
# generated image which is then passed to gs.
# OUTPUTFORMAT: the format of the output bitmap image files.
# Two formats are recognised: "ppm" and "png".
# If successful, this script will leave in dir ${DIR}:
# a (possibly large) number of image files with names like
# ${BASE}\([0-9]*\).${SUFFIX} where SUFFIX is ppm or png.
# a file containing info needed by LyX to position the images correctly
# on the screen.
# ${BASE}.metrics
# All other files ${BASE}* will be deleted.
# A quick note on the choice of OUTPUTFORMAT:
# In general files in PPM format are 10-100 times larger than the
# equivalent files in PNG format. Larger files results in longer
# reading and writing times as well as greater disk usage.
# However, whilst the Qt image loader can load files in PNG format
# without difficulty, the xforms image loader cannot. They must first
# be converted to a loadable format (eg PPM!). Thus, previews will take
# longer to appear if the xforms loader is used to load snippets in
# PNG format.
# You can always experiment by adding a line to your
# ${LYXUSERDIR}/preferences file
# \converter lyxpreview ${FORMAT} "lyxpreview2bitmap.sh" ""
# where ${FORMAT} is either ppm or png.
# These four programs are used by the script.
# Adjust their names to suit your setup.
test -n "$LATEX" || LATEX=latex
DVIPS=dvips
GS=gs
PNMCROP=pnmcrop
readonly LATEX DVIPS GS PNMCROP
# Three helper functions.
FIND_IT ()
{
test $# -eq 1 || exit 1
type $1 > /dev/null || {
echo "Unable to find \"$1\". Please install."
exit 1
}
}
BAIL_OUT ()
{
test $# -eq 1 && echo $1
# Remove everything except the original .tex file.
FILES=`ls ${BASE}* | sed -e "/${BASE}\.tex/d"`
rm -f ${FILES} texput.log
echo "Leaving ${BASE}.tex in ${DIR}"
exit 1
}
REQUIRED_VERSION ()
{
test $# -eq 1 || exit 1
echo "We require preview.sty version 0.73 or newer. You're using"
grep 'Package: preview' $1
}
# Preliminary check.
if [ $# -ne 3 ]; then
exit 1
fi
# We use latex, dvips and gs, so check that they're all there.
FIND_IT ${LATEX}
FIND_IT ${DVIPS}
FIND_IT ${GS}
# Extract the params from the argument list.
DIR=`dirname $1`
BASE=`basename $1 .tex`
SCALEFACTOR=$2
if [ "$3" = "ppm" ]; then
GSDEVICE=pnmraw
GSSUFFIX=ppm
elif [ "$3" = "png" ]; then
GSDEVICE=png16m
GSSUFFIX=png
else
BAIL_OUT "Unrecognised output format ${OUTPUTFORMAT}. \
Expected either \"ppm\" or \"png\"."
fi
# Initialise some variables.
TEXFILE=${BASE}.tex
LOGFILE=${BASE}.log
DVIFILE=${BASE}.dvi
PSFILE=${BASE}.ps
METRICSFILE=${BASE}.metrics
readonly TEXFILE LOGFILE DVIFILE PSFILE METRICSFILE
# LaTeX -> DVI.
cd ${DIR}
${LATEX} ${TEXFILE} ||
{
BAIL_OUT "Failed: ${LATEX} ${TEXFILE}"
}
# Parse ${LOGFILE} to obtain bounding box info to output to
# ${METRICSFILE}.
# This extracts lines starting "Preview: Tightpage" and
# "Preview: Snippet".
grep -E 'Preview: [ST]' ${LOGFILE} > ${METRICSFILE} ||
{
REQUIRED_VERSION ${LOGFILE}
BAIL_OUT "Failed: grep -E 'Preview: [ST]' ${LOGFILE}"
}
# Parse ${LOGFILE} to obtain ${RESOLUTION} for the gs process to follow.
# 1. Extract font size from a line like "Preview: Fontsize 20.74pt"
# Use grep for speed and because it gives an error if the line is
# not found.
LINE=`grep 'Preview: Fontsize' ${LOGFILE}` ||
{
REQUIRED_VERSION ${LOGFILE}
BAIL_OUT "Failed: grep 'Preview: Fontsize' ${LOGFILE}"
}
# The sed script strips out everything that won't form a decimal number
# from the line. It bails out after the first match has been made in
# case there are multiple lines "Preview: Fontsize". (There shouldn't
# be.)
# Note: use "" quotes in the echo to preserve newlines.
LATEXFONT=`echo "${LINE}" | sed 's/[^0-9\.]//g; 1q'`
# 2. Extract magnification from a line like
# "Preview: Magnification 2074"
# If no such line found, default to MAGNIFICATION=1000.
LINE=`grep 'Preview: Magnification' ${LOGFILE}`
if LINE=`grep 'Preview: Magnification' ${LOGFILE}`; then
# Strip out everything that won't form an /integer/.
MAGNIFICATION=`echo "${LINE}" | sed 's/[^0-9]//g; 1q'`
else
MAGNIFICATION=1000
fi
# 3. Compute resolution.
# "bc" allows floating-point arithmetic, unlike "expr" or "dc".
RESOLUTION=`echo "scale=2; \
${SCALEFACTOR} * (10/${LATEXFONT}) * (1000/${MAGNIFICATION})" \
| bc`
# DVI -> PostScript
${DVIPS} -o ${PSFILE} ${DVIFILE} ||
{
BAIL_OUT "Failed: ${DVIPS} -o ${PSFILE} ${DVIFILE}"
}
# PostScript -> Bitmap files
# Older versions of gs have problems with a large degree of
# anti-aliasing at high resolutions
# test expects integer arguments.
# ${RESOLUTION} may be a float. Truncate it.
INT_RESOLUTION=`echo "${RESOLUTION} / 1" | bc`
ALPHA=4
if [ ${INT_RESOLUTION} -gt 150 ]; then
ALPHA=2
fi
${GS} -q -dNOPAUSE -dBATCH -dSAFER \
-sDEVICE=${GSDEVICE} -sOutputFile=${BASE}%d.${GSSUFFIX} \
-dGraphicsAlphaBit=${ALPHA} -dTextAlphaBits=${ALPHA} \
-r${RESOLUTION} ${PSFILE} ||
{
BAIL_OUT "Failed: ${GS} ${PSFILE}"
}
# All has been successful, so remove everything except the bitmap files
# and the metrics file.
FILES=`ls ${BASE}* | sed -e "/${BASE}.metrics/d" \
-e "/${BASE}\([0-9]*\).${GSSUFFIX}/d"`
rm -f ${FILES} texput.log
# The bitmap files can have large amounts of whitespace to the left and
# right. This can be cropped if so desired.
CROP=1
type ${PNMCROP} > /dev/null || CROP=0
# There's no point cropping the image if using PNG images. If you want to
# crop, use PPM.
# Apparently dvipng will support cropping at some stage in the future...
if [ ${CROP} -eq 1 -a "${GSDEVICE}" = "pnmraw" ]; then
for FILE in ${BASE}*.${GSSUFFIX}
do
if ${PNMCROP} -left ${FILE} 2> /dev/null |\
${PNMCROP} -right 2> /dev/null > ${BASE}.tmp; then
mv ${BASE}.tmp ${FILE}
else
rm -f ${BASE}.tmp
fi
done
rm -f ${BASE}.tmp
fi
echo "Previews generated!"

View File

@ -1,3 +1,16 @@
2004-04-15 Angus Leeming <leeming@lyx.org>
* PreviewLoader.C (startLoading): change arguments passed to the
preview-generation script to include the foreground and background
colours.
(dumpPreamble): no longer write the foreground and background
colours to the latex file as PostScript specials.
(setConverter): consider only those 'to' formats that are
loadable natively by the GUI library, rather than all formats
for which a converter exists.
(setAscentFractions): re-written to parse much simplified metrics
file.
2004-04-13 Angus Leeming <leeming@lyx.org>
* PreviewLoader.C (dumpPreamble):

View File

@ -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(ostream & os) const
// 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,30 @@ void setAscentFractions(vector<double> & ascent_fractions,
bool error = false;
// Tightpage dimensions affect all subsequent dimensions
int tp_ascent;
int tp_descent;
int snippet_counter = 1;
while (!in.eof() && it != end) {
string snippet;
int id;
double ascent_fraction;
int snippet_counter = 0;
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;
in >> snippet >> id >> ascent_fraction;
if (!in.good())
// eof after all
break;
error = preview != "Preview:"
|| (type != "Tightpage" && type != "Snippet");
error = snippet != "Snippet";
if (error)
break;
if (type == "Tightpage") {
int dummy;
in >> dummy >> tp_descent >> dummy >> tp_ascent;
error = !in.good();
error = id != snippet_counter;
if (error)
break;
} else {
int dummy;
int snippet_id;
int ascent;
int descent;
in >> snippet_id >> ascent >> descent >> dummy;
*it = ascent_fraction;
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;
}
++snippet_counter;
++it;
}
if (error) {