Hi!

As you may know, I've been trying to figure out how to get WYSIWYG in
AbiWord.  Before implementing anything in AbiWord, I though that it will
pay off to implement my ideas in a little program, see how they work,
and we all agree that we're in the right path, then implement it in
AbiWord.

I've thus written a little program to show how my first idea works, ie.
use linearly scaled metrics to lay out individual characters on screen,
and default horizontal advances on printers (I guess that trying to lay
out individual characters in the printer will inflate too much our PS
file).

That way, if my thoughts are right, we will get WYSIWYG on
high-resolution printers, and very near WYSIWYG on low-resolution
printers (where rounding errors can still affect the layout).  It seems
that that's the way MS Word works.

When you start this little app, you can start typing anything.  You will
see in the first line what you type, layed out using this algorithm.  In
the second line the text is layed out using our current algorithm.

I rendered this way a line in Times New Roman at 12 points, with a
resolution of 96dpi.  I then started MS Word, and I wrote the same line
using the same font at the same size and with the same resolution.

I took a screenshot and a pasted the MS Word rendered line just above
the two lines of my little program (screenshot at
http://www.ie2.u-psud.fr/~cuenca/abi_vs_word.png).

As you may see, the word positioned letters are exactly at the same
position than the letters positioned by the new algorithm, but for some
letters, rendered by word 1 pixel before the position picked by the new
algorithm.

I guess that this difference comes from the way the new algorithm pass
from high-resolution to 96dpi (rounding), instead of what MS Word seems
to do (truncating).  So it may only be after all a little ms word bug
(this truncating error is not cumulated, so it's not so important).

Attached the code of this little application (I've only tried it with
Xft2, but I think that it will also run with Xft1).

Comments?

Cheers,

-- 
Joaqu�n Cuenca Abela
[EMAIL PROTECTED]
// g++ test_layout.cpp -g -o test_layout `pkg-config --cflags --libs gtk+-2.0` `xft-config --cflags --libs`

#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <Xft/Xft.h>
#include <assert.h>
#include <vector>
#include <iostream>
#include <algorithm>

using namespace std;

static const int screen_resolution = 96;
static const int printer_resolution = 3000;
static const float mm_per_inch = 25.4;
static const int x_off = 10;
static const int y_off = 10;
static float cursor_pos = x_off;

// double buffer pixmap for the drawing area
static GdkPixmap* pixmap = 0;
static GdkGC* gc = 0;
static GdkGC* white_gc = 0;

// Buffer of characters
typedef vector<FcChar32> TextBuffer;
static TextBuffer text_buffer;

// Xft related structures
static XftDraw* xft_draw = 0;
static XftFont* font = 0;
static XftColor color;

void
destroy_cb (GtkWidget *widget, gpointer   data)
{
    gtk_main_quit ();
}

inline static int
inches_to_pixels (float inches)
{
    return (int) (inches * 96 + 0.5);
}

class FaceGetter
{
public:
    FaceGetter(XftFont* font) : face_(XftLockFace(font)), font_(font) {}
    ~FaceGetter() { XftUnlockFace(font_); }

    FT_Face getFace() { return face_; }
    
private:
    FaceGetter(const FaceGetter&);
    FaceGetter& operator= (const FaceGetter&);
    
    FT_Face face_;
    XftFont* font_;
};

// that kinda of sucks.  I'm calculating everything in the drawing handler,
// when all that can be calculated before...
static void
draw_text()
{
    float dx;
    const size_t nb_chars = text_buffer.size();
    vector<XftCharSpec> chars(nb_chars);
    XftCharSpec char_spec;
    XGlyphInfo extents;
    FaceGetter face_getter(font);
    FT_Face face = face_getter.getFace();
    FT_UInt glyph_index;
    int error;
    
    cursor_pos = x_off;
    
    for (size_t i = 0; i < nb_chars; ++i)
    {
        FT_Fixed linearHoriAdvance;
        
        char_spec.ucs4 = text_buffer[i];
        char_spec.x = (short) (cursor_pos + 0.5);
        char_spec.y = y_off + 20;
        chars.push_back(char_spec);

        glyph_index = FT_Get_Char_Index(face, char_spec.ucs4);
        error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);
        if (error)
        {
            cout << "Error loading glyph" << endl;
            continue;
        }

        linearHoriAdvance = face->glyph->linearHoriAdvance;

        dx = ((float) linearHoriAdvance) / (1 << 16);
        cursor_pos += dx;
    }

    XftDrawCharSpec (xft_draw, &color, font, &chars[0], chars.size());
    XftDrawString32 (xft_draw, &color, font, x_off, y_off + 36, &text_buffer[0], text_buffer.size());
}

static void
draw_page_borders ()
{
    /* the weight and height of a A4 page (210 x 297 mm) */
    static const float width_inches = 8.268;
    static const float height_inches = 11.693;
    int x0 = x_off;
    int y0 = y_off;
    /* the -1 here is just because X sucks */
    int xf = x_off + inches_to_pixels (width_inches) - 1;
    int yf = y_off + inches_to_pixels (height_inches) - 1;
    
    assert (pixmap);
    assert (gc);
    
    gdk_draw_rectangle (pixmap, gc, 0, x0, y0, xf, yf);
}

static void
draw ()
{
    int width;
    int height;
    
    assert (pixmap);
    assert (gc);

    gdk_drawable_get_size (pixmap, &width, &height);
    gdk_draw_rectangle (pixmap, white_gc, TRUE, 0, 0, width, height);
    
    draw_page_borders ();
    draw_text ();
}

static gboolean
key_press_event (GtkWidget* widget, GdkEventKey* event, gpointer user_data)
{
    FcChar32 unichar;

    unichar = gdk_keyval_to_unicode (event->keyval);
    if (unichar)
    {
        text_buffer.push_back (unichar);
        draw ();
        gtk_widget_queue_draw_area (widget, 0, 0, widget->allocation.width, widget->allocation.height);
    }

    return TRUE;
}

/* Create a new backing pixmap of the appropriate size */
static gint
configure_event (GtkWidget* widget, GdkEventConfigure* event)
{
    if (pixmap)
        gdk_pixmap_unref(pixmap);

    gc = widget->style->black_gc;
    white_gc = widget->style->white_gc;
    
    pixmap = gdk_pixmap_new(widget->window,
                            widget->allocation.width,
                            widget->allocation.height,
                            -1);

    if (xft_draw)
        XftDrawChange(xft_draw, GDK_WINDOW_XWINDOW (pixmap));
    else
        xft_draw = XftDrawCreate (GDK_DISPLAY (), GDK_WINDOW_XWINDOW (pixmap),
                                  GDK_VISUAL_XVISUAL (gdk_drawable_get_visual (pixmap)),
                                  GDK_COLORMAP_XCOLORMAP (gdk_drawable_get_colormap (pixmap)));

    draw ();

    return TRUE;
}

static gint
expose_event (GtkWidget* widget, GdkEventExpose* event)
{
    gdk_draw_pixmap(widget->window,
                    widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
                    pixmap,
                    event->area.x, event->area.y,
                    event->area.x, event->area.y,
                    event->area.width, event->area.height);

    return FALSE;
}

int
main(int argc, char* argv[])
{
    GtkDrawingArea* da;
    GtkWindow* main_window;
    GdkWindow* win;
    
    gtk_init (&argc, &argv);
    main_window = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL));

    g_signal_connect (G_OBJECT (main_window), "destroy",
                      G_CALLBACK (destroy_cb), NULL);
 
    da = GTK_DRAWING_AREA (gtk_drawing_area_new ());
    win = GTK_WIDGET (da)->window;
    
    GdkColormap* colormap = gdk_rgb_get_cmap();
    GdkColor c;
    c.red = 0;
    c.green = 0;
    c.blue = 0;
    
    gdk_color_alloc(colormap, &c);

    color.color.red = c.red;
    color.color.green = c.green;
    color.color.blue = c.blue;
    color.color.alpha = 0xffff;
    color.pixel = c.pixel;
    
    font = XftFontOpenName(GDK_DISPLAY (), DefaultScreen (GDK_DISPLAY ()), "Times-12");
    
    gtk_container_add(GTK_CONTAINER (main_window), GTK_WIDGET (da));

    g_signal_connect (G_OBJECT (da), "configure_event",
                      G_CALLBACK (configure_event), NULL);
    g_signal_connect (G_OBJECT (da), "expose_event",
                      G_CALLBACK (expose_event), NULL);
    g_signal_connect (G_OBJECT (main_window), "key_press_event",
                      G_CALLBACK (key_press_event), NULL);
    
    gtk_widget_show_all (GTK_WIDGET (main_window));
    
    gtk_main ();
    return 0;
}

Reply via email to