From: Angus Leeming Date: Fri, 16 Apr 2004 14:34:41 +0000 (+0000) Subject: Rework preview generation to use Jan-��ke Larsson's dvipng. X-Git-Tag: 1.6.10~15311 X-Git-Url: https://git.lyx.org/gitweb/?a=commitdiff_plain;h=8be99f7b3bc9018f01f737758348801a2176d589;p=features.git 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 --- diff --git a/lib/ChangeLog b/lib/ChangeLog index fc4080f14a..ebfb07b320 100644 --- a/lib/ChangeLog +++ b/lib/ChangeLog @@ -1,3 +1,9 @@ +2004-04-15 Angus Leeming + + * scripts/lyxpreview2bitmap.sh: removed. + * scripts/lyxpreview2bitmap.py: added. Uses Jan-Åke Larsson's dvipng + to perform the conversion. + 2004-04-12 Georg Baum * configure.m4: merge \viewer and \format. Add editor to \format diff --git a/lib/scripts/lyxpreview2bitmap.py b/lib/scripts/lyxpreview2bitmap.py new file mode 100755 index 0000000000..0a1f77c85f --- /dev/null +++ b/lib/scripts/lyxpreview2bitmap.py @@ -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 \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) diff --git a/lib/scripts/lyxpreview2bitmap.sh b/lib/scripts/lyxpreview2bitmap.sh deleted file mode 100644 index a324fa88f7..0000000000 --- a/lib/scripts/lyxpreview2bitmap.sh +++ /dev/null @@ -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!" diff --git a/src/graphics/ChangeLog b/src/graphics/ChangeLog index 4932a2e328..9399d00305 100644 --- a/src/graphics/ChangeLog +++ b/src/graphics/ChangeLog @@ -1,3 +1,16 @@ +2004-04-15 Angus Leeming + + * 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 * PreviewLoader.C (dumpPreamble): diff --git a/src/graphics/PreviewLoader.C b/src/graphics/PreviewLoader.C index f0ad7291bb..f592de1c39 100644 --- a/src/graphics/PreviewLoader.C +++ b/src/graphics/PreviewLoader.C @@ -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 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 & 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(); - 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; + error = id != snippet_counter; + if (error) + break; - if (!support::float_equal(a + d, 0, 0.1)) - *it = a / (a + d); + *it = ascent_fraction; - if (++it == end) - break; - } + ++snippet_counter; + ++it; } if (error) {