]> git.lyx.org Git - features.git/commitdiff
Rework preview generation to use Jan-��ke Larsson's dvipng.
authorAngus Leeming <leeming@lyx.org>
Fri, 16 Apr 2004 14:34:41 +0000 (14:34 +0000)
committerAngus Leeming <leeming@lyx.org>
Fri, 16 Apr 2004 14:34:41 +0000 (14:34 +0000)
git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@8655 a592a061-630c-0410-9148-cb99ea01b6c8

lib/ChangeLog
lib/scripts/lyxpreview2bitmap.py [new file with mode: 0755]
lib/scripts/lyxpreview2bitmap.sh [deleted file]
src/graphics/ChangeLog
src/graphics/PreviewLoader.C

index fc4080f14af3c605fd4ed0d6a82fc9235d518478..ebfb07b3204e550e0eb3f8001e4c6f658406e3b7 100644 (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
diff --git a/lib/scripts/lyxpreview2bitmap.py b/lib/scripts/lyxpreview2bitmap.py
new file mode 100755 (executable)
index 0000000..0a1f77c
--- /dev/null
@@ -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)
diff --git a/lib/scripts/lyxpreview2bitmap.sh b/lib/scripts/lyxpreview2bitmap.sh
deleted file mode 100644 (file)
index a324fa8..0000000
+++ /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!"
index 4932a2e328cf1606efa9cc0d06d66c757ed069fe..9399d003055d220055438873ae4038a816cc0f68 100644 (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):
index f0ad7291bbfbb4b5557c80774b967ade2cfa3115..f592de1c39e3d1a64fcf09f388e8bf224f5e7a30 100644 (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();
-                       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) {