]> git.neil.brown.name Git - edlib.git/commitdiff
devel
authorNeilBrown <neil@brown.name>
Sat, 30 Jan 2016 21:25:47 +0000 (08:25 +1100)
committerNeilBrown <neil@brown.name>
Sat, 30 Jan 2016 21:25:47 +0000 (08:25 +1100)
core-doc.c
core-mark.c
core.h
doc-text.c
edlib.c
lang-python.c
lib-messageline.c
lib-view.c
python/display-pygtk.py
python/render-present.py [new file with mode: 0644]
render-lines.c

index ff068f749f2f375db70c18f1c5376c7a7565abba..5b0461761408cdd5f66b5c00e5676d720fb8d1a4 100644 (file)
@@ -494,8 +494,12 @@ DEF_CMD(doc_handle)
 
        if (strcmp(ci->key, "doc:vmark-get") == 0) {
                struct mark *m, *m2;
-               m = do_vmark_first(dd->doc, ci->numeric);
-               m2 = do_vmark_last(dd->doc, ci->numeric);
+               if (ci->extra == 2) {
+                       m = m2 = NULL;
+               } else {
+                       m = do_vmark_first(dd->doc, ci->numeric);
+                       m2 = do_vmark_last(dd->doc, ci->numeric);
+               }
                if (ci->extra == 1 && dd->point)
                        m2 = do_vmark_at_point(dd->doc, dd->point,
                                               ci->numeric);
@@ -858,6 +862,8 @@ DEF_CMD(docs_open)
 
        if (strcmp(ci->key, "Chr-h") == 0)
                renderer = "hex";
+       if (strcmp(ci->key, "Chr-p") == 0)
+               renderer = "present";
 
        if (strcmp(ci->key, "Chr-o") == 0) {
                struct pane *p2 = call_pane("OtherPane", ci->focus, 0, NULL, 0);
@@ -896,6 +902,7 @@ void doc_make_docs(struct editor *ed)
 
        key_add(docs_map, "Chr-f", &docs_open);
        key_add(docs_map, "Chr-h", &docs_open);
+       key_add(docs_map, "Chr-p", &docs_open);
        key_add(docs_map, "Return", &docs_open);
        key_add(docs_map, "Chr-o", &docs_open);
        key_add(docs_map, "Chr-q", &docs_bury);
index 5475fd8c72c47970421775e2a25b92b1fe2d85ec..ec70f30587b13559986120ad854d8dd41a744ddb 100644 (file)
@@ -374,7 +374,8 @@ struct mark *doc_new_mark(struct doc *d, int view)
 {
        struct mark *ret;
 
-       if (view == MARK_POINT || view >= d->nviews || d->views[view].state != 1)
+       if (view == MARK_POINT || view >= d->nviews ||
+           (view != MARK_UNGROUPED && d->views[view].state != 1))
                return NULL;
        ret = calloc(sizeof(*ret), 1);
        ret->viewnum = view;
@@ -981,8 +982,10 @@ struct mark *do_vmark_at_or_before(struct doc *d, struct mark *m, int view)
                /* Just use this, or nearby */
                struct mark *vm2;
                while ((vm2 = vmark_next(vm)) != NULL &&
-                      mark_same(d, vm, vm2))
+                      mark_same(d, vm, m))
                        vm = vm2;
+               while (vm && vm->seq > m->seq && !mark_same(d, vm, m))
+                       vm = vmark_prev(vm);
        }
        return vm;
 }
@@ -1046,7 +1049,7 @@ static void point_notify_change(struct doc *d, struct mark *p, struct mark *m)
 
 /* doc_notify_change is slower than point_notify_change, but only
  * requires a mark, not a point.
- * A second mark should only be given in the first mark is a point
+ * A second mark should only be given if the first mark is a point
  */
 void doc_notify_change(struct doc *d, struct mark *m, struct mark *m2)
 {
diff --git a/core.h b/core.h
index 21989138423dc6156926efa35dbc324e0dec8b72..02b8c4cfff2163e4cccf2e56a30cb8195ccf92a1 100644 (file)
--- a/core.h
+++ b/core.h
@@ -548,6 +548,24 @@ static inline int call_xy(char *key, struct pane *focus, int numeric,
        return key_handle(&ci);
 }
 
+static inline int call_xy7(char *key, struct pane *focus, int numeric, int extra,
+                          char *str, char *str2, int x, int y, struct mark *m, struct mark *m2)
+{
+       struct cmd_info ci = {0};
+
+       ci.key = key;
+       ci.focus = focus;
+       ci.numeric = numeric;
+       ci.extra = extra;
+       ci.str = str;
+       ci.str2 = str2;
+       ci.x = x;
+       ci.y = y;
+       ci.mark = m;
+       ci.mark2 = m2;
+       return key_handle(&ci);
+}
+
 static inline int call7(char *key, struct pane *focus, int numeric, struct mark *m,
                        char *str, int extra, char *str2, struct mark *m2)
 {
index 2e35baa660d877402ee17b0f1652e78272304ec5..ae0acf15717ea2f0b296800ecac65ece957270a4 100644 (file)
@@ -1446,8 +1446,11 @@ static char *__text_get_attr(struct doc *d, struct mark *m,
                char *a = attr_get_str(d->attrs, attr, -1);
                if (a)
                        return a;
-               if (strcmp(attr, "default-renderer") == 0)
+               if (strcmp(attr, "default-renderer") == 0) {
+//                     if (t->fname && strcmp(t->fname + strlen(t->fname)-3, ".md") == 0)
+//                             return "present";
                        return "lines";
+               }
                if (strcmp(attr, "filename") == 0)
                        return t->fname;
                return NULL;
diff --git a/edlib.c b/edlib.c
index a5b52835d621c6cc2b4520f00ed096bf84528a66..f1d9664a71c227c9201f55b6d4f143c3926ad555 100644 (file)
--- a/edlib.c
+++ b/edlib.c
 
 #include "core.h"
 
-char WelcomeText[] =
+static char WelcomeText[] =
+       ":H1:center,bg:green\n"
+       "\n"
+       "# heading\n"
+       "line\n"
        "\n"
        "Welcome to 'edlib' - the beginning of what one day might be an editor\n"
        "\n"
-       "Current functionality includes:\n"
+       "Current functionality includes:\n"
        "  splitting and closing windows (C-x 0,1,2,3)\n"
        "  Resize current window (C-x },{,^)\n"
        "  Move among windows (C-x o,O  or mouse click)\n"
@@ -95,6 +99,12 @@ int main(int argc, char *argv[])
        editor_load_module(ed, "mode-emacs");
        call5("global-set-keymap", global, 0, NULL, "mode-emacs", 0);
 
+       memset(&ci, 0, sizeof(ci));
+       ci.home = ci.focus = global;
+       ci.key = "python-load";
+       ci.str = "python/render-present.py";
+       key_handle(&ci);
+
        b = pane_attach(global, "tile", NULL, NULL);
        if (b)
                p = doc_from_text(b, "*Welcome*", WelcomeText);
@@ -105,6 +115,23 @@ int main(int argc, char *argv[])
                ci.str = "python/test.py";
                key_handle(&ci);
 
+               /* New window.. */
+               if (gtk)  {
+                       memset(&ci, 0, sizeof(ci));
+                       ci.home = ci.focus = vroot;
+                       ci.key = "display-pygtk";
+                       cr.c = take_pane;
+                       cr.p = NULL;
+                       ci.comm2 = &cr.c;
+                       key_handle(&ci);
+                       root = pane_attach(cr.p, "input", NULL, NULL);
+                       global = pane_attach(root, "messageline", NULL, NULL);
+                       global = pane_attach(global, "global-keymap", NULL, NULL);
+                       call5("global-set-keymap", global, 0, NULL, "mode-emacs", 0);
+                       b = pane_attach(global, "tile", NULL, NULL);
+                       p = doc_from_text(b, "*Welcome*", WelcomeText);
+               }
+
                pane_refresh(&ed->root);
                while (call3("event:run", vroot, 0, NULL) == 1)
                        ;
index c309eb749b0bce49e9a0f1572db0d34f60924862..a7445c55c677e37df4b868d9f0244fd29dff0582 100644 (file)
@@ -306,7 +306,7 @@ static PyObject *Pane_call(Pane *self, PyObject *args, PyObject *kwds)
                Py_INCREF(Py_None);
                return Py_None;
        }
-       if (rv 0) {
+       if (rv == -1000) {
                PyErr_SetString(Edlib_CommandFailed, ci.str ? ci.str : "Command Failed");
                return NULL;
        }
@@ -329,7 +329,7 @@ static PyObject *Pane_call_focus(Pane *self, PyObject *args, PyObject *kwds)
                Py_INCREF(Py_None);
                return Py_None;
        }
-       if (rv 0) {
+       if (rv == -1000) {
                PyErr_SetString(Edlib_CommandFailed, ci.str ? ci.str : "Command Failed");
                return NULL;
        }
@@ -357,7 +357,7 @@ static PyObject *Pane_call_xy(Pane *self, PyObject *args, PyObject *kwds)
                Py_INCREF(Py_None);
                return Py_None;
        }
-       if (rv 0) {
+       if (rv == -1000) {
                PyErr_SetString(Edlib_CommandFailed, ci.str ? ci.str : "Command Failed");
                return NULL;
        }
@@ -381,7 +381,7 @@ static PyObject *Pane_call_filter(Pane *self, PyObject *args, PyObject *kwds)
                Py_INCREF(Py_None);
                return Py_None;
        }
-       if (rv 0) {
+       if (rv == -1000) {
                PyErr_SetString(Edlib_CommandFailed, ci.str ? ci.str : "Command Failed");
                return NULL;
        }
@@ -421,6 +421,30 @@ static PyObject *Pane_rel(Pane *self, PyObject *args)
        return Py_BuildValue("ii", x, y);
 }
 
+static PyObject *Pane_render_attach(Pane *self, PyObject *args)
+{
+       char *type = NULL;
+       struct pane *p;
+       int ret = PyArg_ParseTuple(args, "s", &type);
+       if (ret <= 0)
+               return NULL;
+       p = render_attach(type, self->pane);
+       if (!p) {
+               Py_INCREF(Py_None);
+               return Py_None;
+       }
+       return Pane_Frompane(p);
+}
+
+static PyObject *Pane_damaged(Pane *self, PyObject *args)
+{
+       if (self->pane) {
+               pane_damaged(self->pane, DAMAGED_SIZE);
+       }
+       Py_INCREF(Py_None);
+       return Py_None;
+}
+
 static PyObject *Pane_close(Pane *self)
 {
        struct pane *p = self->pane;
@@ -469,6 +493,10 @@ static PyMethodDef pane_methods[] = {
         "Convert absolute co-orders to pane-relative"},
        {"add_notify", (PyCFunction)Pane_add_notify, METH_VARARGS,
         "Add notified for an event on some other pane"},
+       {"render_attach", (PyCFunction)Pane_render_attach, METH_VARARGS,
+        "Attach a renderer to a pane"},
+       {"damage", (PyCFunction)Pane_damaged, METH_VARARGS,
+        "Mark pane as damaged"},
        {NULL}
 };
 
@@ -609,6 +637,52 @@ static PyGetSetDef pane_getseters[] = {
     {NULL}  /* Sentinel */
 };
 
+static PyObject *Pane_get_item(Pane *self, PyObject *key)
+{
+       char *k, *v;
+       if (!self->pane) {
+               PyErr_SetString(PyExc_TypeError, "Pane is NULL");
+               return NULL;
+       }
+       if (!PyString_Check(key)) {
+               PyErr_SetString(PyExc_TypeError, "Key must be a string");
+               return NULL;
+       }
+       k = PyString_AsString(key);
+       v = attr_get_str(self->pane->attrs, k, -1);
+       if (v)
+               return Py_BuildValue("s", v);
+       Py_INCREF(Py_None);
+       return Py_None;
+}
+
+static int Pane_set_item(Pane *self, PyObject *key, PyObject *val)
+{
+       char *k, *v;
+       if (!self->pane) {
+               PyErr_SetString(PyExc_TypeError, "Pane is NULL");
+               return -1;
+       }
+       if (!PyString_Check(key)) {
+               PyErr_SetString(PyExc_TypeError, "Key must be a string");
+               return -1;
+       }
+       if (!PyString_Check(val)) {
+               PyErr_SetString(PyExc_TypeError, "value must be a string");
+               return -1;
+       }
+       k = PyString_AsString(key);
+       v = PyString_AsString(val);
+       attr_set_str(&self->pane->attrs, k, v, -1);
+       return 0;
+}
+
+static PyMappingMethods pane_mapping = {
+       .mp_length = NULL,
+       .mp_subscript = (binaryfunc)Pane_get_item,
+       .mp_ass_subscript = (objobjargproc)Pane_set_item,
+};
+
 static PyTypeObject PaneType = {
     PyObject_HEAD_INIT(NULL)
     0,                         /*ob_size*/
@@ -623,7 +697,7 @@ static PyTypeObject PaneType = {
     (reprfunc)pane_repr,       /*tp_repr*/
     0,                         /*tp_as_number*/
     0,                         /*tp_as_sequence*/
-    0,                         /*tp_as_mapping*/
+    &pane_mapping,             /*tp_as_mapping*/
     (hashfunc)pane_hash,       /*tp_hash */
     0,                         /*tp_call*/
     0,                         /*tp_str*/
@@ -690,6 +764,21 @@ static int mark_nosetview(Mark *m, PyObject *v, void *which)
        return -1;
 }
 
+static PyObject *Mark_getseq(Mark *m, void *x)
+{
+       if (m->mark == NULL) {
+               PyErr_SetString(PyExc_TypeError, "Mark is NULL");
+               return NULL;
+       }
+       return PyInt_FromLong(m->mark->seq);
+}
+
+static int Mark_nosetseq(Mark *m, PyObject *v, void *which)
+{
+       PyErr_SetString(PyExc_TypeError, "Cannot set mark seq num");
+       return -1;
+}
+
 static PyObject *mark_getdata(Mark *m, void *x)
 {
        PyObject *ret;
@@ -731,15 +820,27 @@ static int mark_setdata(Mark *m, PyObject *v, void *x)
 PyObject *mark_compare(Mark *a, Mark *b, int op)
 {
        int ret = 0;
-       switch(op) {
-       case Py_LT: ret = mark_ordered(a->mark, b->mark); break;
-       case Py_LE: ret = mark_ordered(a->mark, b->mark); break;
-       case Py_GT: ret = mark_ordered(a->mark, b->mark); break;
-       case Py_GE: ret = mark_ordered(a->mark, b->mark); break;
-       case Py_EQ: ret = mark_ordered(a->mark, b->mark); break;
-       case Py_NE: ret = mark_ordered(a->mark, b->mark); break;
-       }
-       return ret ? Py_True : Py_False;
+       PyObject *rv;
+       if ((PyObject*)a == Py_None)
+               ret = (op == Py_LT || op == Py_LE || op == Py_NE);
+       else if ((PyObject*)b == Py_None)
+               ret = (op == Py_GT || op == Py_GE || op == Py_EQ);
+       else if (PyObject_TypeCheck(a, &MarkType) == 0 ||
+                PyObject_TypeCheck(b, &MarkType) == 0) {
+               PyErr_SetString(PyExc_TypeError, "Mark compared with non-Mark");
+               return NULL;
+       } else
+               switch(op) {
+               case Py_LT: ret = mark_ordered(a->mark, b->mark); break;
+               case Py_LE: ret = !mark_ordered(b->mark, a->mark); break;
+               case Py_GT: ret = mark_ordered(b->mark, a->mark); break;
+               case Py_GE: ret = !mark_ordered(a->mark, b->mark); break;
+               case Py_EQ: ret = !mark_ordered(a->mark, b->mark) && !mark_ordered(b->mark, a->mark); break;
+               case Py_NE: ret = mark_ordered(a->mark, b->mark) || mark_ordered(b->mark, a->mark); break;
+               }
+       rv = ret ? Py_True : Py_False;
+       Py_INCREF(rv);
+       return rv;
 }
 
 static PyGetSetDef mark_getseters[] = {
@@ -749,6 +850,9 @@ static PyGetSetDef mark_getseters[] = {
     {"viewnum",
      (getter)mark_getview, (setter)mark_nosetview,
      "Index for view list", NULL},
+    {"seq",
+     (getter)Mark_getseq, (setter)Mark_nosetseq,
+     "Seq number of mark", NULL},
     {"data",
      (getter)mark_getdata, (setter)mark_setdata,
      "Application data", NULL},
@@ -831,20 +935,37 @@ static PyObject *Mark_next_any(Mark *self)
        return Py_None;
 }
 
-static PyObject *Mark_dup(Mark *self)
+static PyObject *Mark_dup(Mark *self, PyObject *args)
 {
        struct mark *new;
+       int ret;
+       int no_type = 0;
        if (!self->mark) {
                PyErr_SetString(PyExc_TypeError, "Mark is NULL");
                return NULL;
        }
-       new = mark_dup(self->mark, 0);
+       ret = PyArg_ParseTuple(args, "|i", &no_type);
+       if (ret <= 0)
+               return NULL;
+       new = mark_dup(self->mark, no_type);
        if (new)
                return Mark_Frommark(new);
        Py_INCREF(Py_None);
        return Py_None;
 }
 
+static PyObject *Mark_unlink(Mark *self)
+{
+       if (!self->mark) {
+               PyErr_SetString(PyExc_TypeError, "Mark is NULL");
+               return NULL;
+       }
+       mark_free(self->mark);
+       self->mark = NULL;
+       Py_INCREF(Py_None);
+       return Py_None;
+}
+
 static PyMethodDef mark_methods[] = {
        {"to_mark", (PyCFunction)Mark_to_mark, METH_VARARGS,
         "Move one mark to another"},
@@ -854,8 +975,10 @@ static PyMethodDef mark_methods[] = {
         "previous vmark"},
        {"next_any", (PyCFunction)Mark_next_any, METH_NOARGS,
         "next any_mark"},
-       {"dup", (PyCFunction)Mark_dup, METH_NOARGS,
-        "duplicate a mark, preserving type"},
+       {"dup", (PyCFunction)Mark_dup, METH_VARARGS,
+        "duplicate a mark, preserving type .. or not if '1' is given"},
+       {"unlink", (PyCFunction)Mark_unlink, METH_NOARGS,
+        "unlink mark and discard it"},
        {NULL}
 };
 
@@ -978,7 +1101,7 @@ static PyObject *Comm_call(Comm *c, PyObject *args, PyObject *kwds)
                Py_INCREF(Py_None);
                return Py_None;
        }
-       if (rv 0) {
+       if (rv == -1000) {
                PyErr_SetString(Edlib_CommandFailed, ci.str ? ci.str : "Command Failed");
                return NULL;
        }
@@ -1071,7 +1194,7 @@ static int get_cmd_info(struct cmd_info *ci, PyObject *args, PyObject *kwds)
        }
        for (i = 1; i < argc; i++) {
                a = PyTuple_GetItem(args, i);
-               if (Py_TYPE(a) == &PaneType) {
+               if (PyObject_TypeCheck(a, &PaneType)) {
                        if (ci->focus == NULL)
                                ci->focus = ((Pane*)a)->pane;
                        else if (ci->home == NULL)
index d91194394b9610b0575cb0f330dcdc80edee1136..7722aad54e45bb6419a3b9ec52658e68dbabf9a7 100644 (file)
@@ -18,7 +18,8 @@ struct mlinfo {
        char *message;
        struct pane *line;
        int height; /* height of line */
-       int ascent; /* how for down to baseline */
+       int ascent; /* how far down to baseline */
+       int hidden;
 };
 
 static void pane_str(struct pane *p, char *s, char *attr, int x, int y)
@@ -39,6 +40,15 @@ DEF_CMD(messageline_handle)
 {
        struct mlinfo *mli = ci->home->data;
 
+       if (strcmp(ci->key, "Window:borderless") == 0) {
+               if (ci->numeric > 0)
+                       mli->hidden = 1;
+               else
+                       mli->hidden = 0;
+               pane_damaged(ci->home, DAMAGED_SIZE);
+               return 0; /* Allow other handlers */
+       }
+
        if (strcmp(ci->key, "Message") == 0) {
                if (ci->extra == 0 || mli->message == NULL) {
                        free(mli->message);
@@ -63,15 +73,19 @@ DEF_CMD(messageline_handle)
                        mli->ascent = cr.i;
                }
                if (ci->home == mli->line) {
-                       pane_resize(ci->home, 0, ci->home->parent->h - mli->height,
-                                   ci->home->parent->w, mli->height);
+                       if (mli->hidden)
+                               pane_resize(ci->home, 0, ci->home->parent->h,
+                                           ci->home->parent->w, mli->height);
+                       else
+                               pane_resize(ci->home, 0, ci->home->parent->h - mli->height,
+                                           ci->home->parent->w, mli->height);
                        pane_clear(mli->line, "bg:cyan");
                        if (mli->message)
                                pane_str(mli->line, mli->message, "bold,fg:red,bg:cyan",
                                         0, 0 + mli->ascent);
                } else {
                        pane_resize(ci->home, 0, 0, ci->home->parent->w,
-                                   ci->home->parent->h - mli->height);
+                                   ci->home->parent->h - (mli->hidden ? 0 : mli->height));
                }
                return 1;
        }
@@ -92,6 +106,7 @@ DEF_CMD(messageline_attach)
 
        mli->message = NULL;
        mli->height = 0;
+       mli->hidden = 0;
        ret = pane_register(p, 0, &messageline_handle, mli, NULL);
        mli->line = pane_register(p, 1, &messageline_handle, mli, NULL);
        pane_focus(ci->focus);
index c86d4cde0851fdfc5e502c7b873493cf5eb0865e..08951afabd564e82050fc2976f66d975fb721620 100644 (file)
@@ -22,6 +22,7 @@
 
 struct view_data {
        int             border;
+       int             old_border;
        int             border_width, border_height;
        int             line_height;
        int             ascent;
@@ -253,6 +254,7 @@ static struct pane *do_view_attach(struct pane *par, int border)
 
        vd = malloc(sizeof(*vd));
        vd->border = border;
+       vd->old_border = border;
        vd->line_height = -1;
        vd->border_width = vd->border_height = -1;
        p = pane_register(par, 0, &view_handle, vd, NULL);
@@ -317,12 +319,27 @@ DEF_CMD(view_click)
        return call3(key, p, num, NULL);
 }
 
+DEF_CMD(view_borderless)
+{
+       struct pane *p = ci->home;
+       struct view_data *vd = p->data;
+
+       if (ci->numeric > 0) {
+               vd->border = 0;
+       } else {
+               vd->border = vd->old_border;
+       }
+       pane_damaged(p, DAMAGED_SIZE);
+       return 0; /* Allow other handlers */
+}
+
 void edlib_init(struct editor *ed)
 {
        view_map = key_alloc();
 
        key_add(view_map, "Click-1", &view_click);
        key_add(view_map, "Press-1", &view_click);
+       key_add(view_map, "Window:borderless", &view_borderless);
 
        key_add(ed->commands, "attach-view", &view_attach);
 }
index b86b65450ced8df2400faeabee4d783be24fc958..6ec876397703bdd95d8ab42ea42938ce6cef6d87 100644 (file)
@@ -30,6 +30,12 @@ class EdDisplay(gtk.Window):
 
     def handle(self, key, **a):
 
+        if key == "Window:fullscreen":
+            if a['numeric'] > 0:
+                self.fullscreen()
+            else:
+                self.unfullscreen()
+
         if key == "Close":
             self.pane.close()
             # FIXME close the window??
@@ -38,15 +44,16 @@ class EdDisplay(gtk.Window):
         if key == "pane-clear":
             f = a["focus"]
             if "str2" in a:
+                print "CLEAR", a['str2']
                 fg, bg = self.get_colours(a["str2"])
             else:
-                fg, bg = self.get_colours("")
+                fg, bg = self.get_colours("bg:white")
             pm = self.get_pixmap(f)
             self.do_clear(pm, bg)
             return True
 
         if key == "text-size":
-            fd = self.extract_font(a["str2"])
+            fd = self.extract_font(a["str2"], a['extra'])
             ctx = self.text.get_pango_context()
             metric = ctx.get_metrics(fd)
             self.text.modify_font(fd)
@@ -73,6 +80,8 @@ class EdDisplay(gtk.Window):
                 self.gc.set_foreground(cmap.alloc_color(gtk.gdk.color_parse("blue")))
             if not self.bg:
                 self.bg = t.window.new_gc()
+                cmap = t.get_colormap()
+                self.bg.set_foreground(cmap.alloc_color(gtk.gdk.color_parse("white")))
 
             (x,y) = a["xy"]
             f = a["focus"]
@@ -80,7 +89,7 @@ class EdDisplay(gtk.Window):
                 attr = a['str2']
             else:
                 attr = ''
-            fd = self.extract_font(attr)
+            fd = self.extract_font(attr, a['extra'])
             ctx = self.text.get_pango_context()
             self.text.modify_font(fd)
             layout = self.text.create_pango_layout(a["str"])
@@ -89,9 +98,10 @@ class EdDisplay(gtk.Window):
             metric = ctx.get_metrics(fd)
             ascent = metric.get_ascent() / pango.SCALE
             ink,(lx,ly,width,height) = layout.get_pixel_extents()
-            self.bg.set_foreground(bg)
-            pm.draw_rectangle(self.bg, True, x+lx, y-ascent+ly, width, height)
-            pm.draw_layout(self.gc, x, y-ascent, layout, fg)
+            if bg:
+                self.bg.set_foreground(bg)
+                pm.draw_rectangle(self.bg, True, x+lx, y-ascent+ly, width, height)
+            pm.draw_layout(self.gc, x, y-ascent, layout, fg, bg)
             if a['numeric'] >= 0:
                 cx,cy,cw,ch = layout.index_to_pos(a["numeric"])
                 if cw <= 0:
@@ -107,6 +117,7 @@ class EdDisplay(gtk.Window):
                     if f.parent.focus != f:
                         extra = False
                     f = f.parent
+                print x,cx,y,cy,cw,ch,extra,a['numeric'],len(a['str'])
                 if extra:
                     pm.draw_rectangle(self.gc, True, x+cx, y-ascent+cy,
                                       cw, ch);
@@ -115,7 +126,42 @@ class EdDisplay(gtk.Window):
                         s = unicode(a["str"][c:], "utf-8")
                         l2 = pango.Layout(ctx)
                         l2.set_text(s[0])
-                        pm.draw_layout(self.gc, x+cx, y-ascent+cy, l2, bg)
+                        pm.draw_layout(self.gc, x+cx, y-ascent+cy, l2, bg, fg)
+                    else:
+                        pm.draw_rectangle(self.gc, False, x+cx, y-ascent+cy,
+                                          cw-1, ch-1);
+            return True
+
+        if key == "image-display":
+            # 'str' is a file name of an image
+            # xy is location for top/left
+            # numeric/extra are max width/height
+            # I should use a pane-per-image but I'm in a rush
+            fl = a['str']
+            f = a['focus']
+            w = a['numeric']
+            h = a['extra']
+            if h == 0:
+                h = w
+            (x,y) = a['xy']
+            try:
+                pb = gtk.gdk.pixbuf_new_from_file(fl)
+            except:
+                pb = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, w, h)
+                pb.fill(0xff000000)
+            if pb.get_width() * h > pb.get_height() * w:
+                # image is wider than space, so reduce height
+                h2 = pb.get_height() * w / pb.get_width()
+                y += (h-h2)/2
+                h = h2
+            else:
+                # image is taller than space
+                w2 = pb.get_width() * h / pb.get_height()
+                x += (w-w2)/2
+                w = w2
+            scale = pb.scale_simple(w, h, gtk.gdk.INTERP_HYPER)
+            pm = self.get_pixmap(f)
+            pm.draw_pixbuf(self.gc, scale, 0, 0, x, y)
             return True
 
         if key == "Notify:Close":
@@ -128,24 +174,33 @@ class EdDisplay(gtk.Window):
 
     styles=["oblique","italic","bold","small-caps"]
 
-    def extract_font(self, attrs):
+    def extract_font(self, attrs, scale):
         "Return a pango.FontDescription"
         family="mono"
         style=""
         size=10
+        if scale <= 10:
+            scale = 1000
         for word in attrs.split(','):
             if word in self.styles:
                 style += " " + word
+            elif len(word) and word[0].isdigit():
+                size = word
             elif word == "large":
-                size = 14
+                size = 40
+            elif word == "small":
+                size = 8.9
             elif word[0:7] == "family:":
                 family = word[7:]
-        return pango.FontDescription(family+' '+style+' '+str(size))
+        fd = pango.FontDescription(family+' '+style+' '+str(size))
+        if scale != 1000:
+            fd.set_size(fd.get_size() * scale / 1000)
+        return fd
 
     def get_colours(self, attrs):
         "Return a foreground and a background colour"
-        fg = "black"
-        bg = "white"
+        fg = None
+        bg = None
         inv = False
         for word in attrs.split(','):
             if word[0:3] == "fg:":
@@ -155,12 +210,32 @@ class EdDisplay(gtk.Window):
             if word == "inverse":
                 inv = True
         cmap = self.text.get_colormap()
-        fgc = cmap.alloc_color(gtk.gdk.color_parse(fg))
-        bgc = cmap.alloc_color(gtk.gdk.color_parse(bg))
         if inv:
-            return (bgc, fgc)
+            fg,bg = bg,fg
+            if fg is None:
+                fg = "white"
+            if bg is None:
+                bg = "black"
+        if fg is None:
+            fg = "black"
+
+        if fg:
+            try:
+                c = gtk.gdk.color_parse(fg)
+            except:
+                c = gtk.gdk.color_parse("black")
+            fgc = cmap.alloc_color(c)
+        else:
+            fgc = None
+        if bg:
+            try:
+                c = gtk.gdk.color_parse(bg)
+            except:
+                c = gtk.gdk.color_parse("white")
+            bgc = cmap.alloc_color(c)
         else:
-            return (fgc, bgc)
+            bgc = None
+        return fgc, bgc
 
     def get_pixmap(self, p):
         if p in self.panes:
diff --git a/python/render-present.py b/python/render-present.py
new file mode 100644 (file)
index 0000000..a6e69d8
--- /dev/null
@@ -0,0 +1,537 @@
+# -*- coding: utf-8 -*-
+# Copyright Neil Brown Â©2015 <neil@brown.name>
+# May be distributed under terms of GPLv2 - see file:COPYING
+
+# edlib rendering module for slide presentation.
+# this reads lines from document, interprets as soemthing vaguely like
+# markdown or restructuredtext, and displays a page at a time with formatting.
+#
+# Parsing is very line-oriented.
+# - a line starting ":ptype:" defines attributes for given paragraph type
+# - a line starting "#" is a heading, "##" is a level-2 heading  (H1 H2 ...)
+# - a line starting ">" is indented   (I)
+# - a line starting "-" is a list item.  (L1)
+# - a line starting "    -" after a list item is a second-level list item (L2)
+# - a line starting "    " after a list item is more text in that list item
+# - a line starting "    " elsewhere is a code fragment (C)
+# - a line starting "!" treats rest of line a name of file containing an image
+# - other lines are text (P)
+#
+# A level-1 heading (H1) starts a new page. attribute lines immediately preceding
+# it are part of the page and apply only to the page
+# Attribute lines before the first page apply to whole document but can be over-ridden
+# in a page.
+#
+# Within text *word* becomes bold (b), _word_ is italic (i) `word` is monospaced (m)
+#
+# attributes can be defined for paragraph types (H1,h2,I,L1,L2,C,P) and text types
+# (b,i,m).  Possible attributes for para or text are:
+#  normal oblique italic bold small-caps large NN family:fontfamily fg:colour bg:colour inverse
+# Possible attributes for para only:
+#   left:size right:size centre space-after:size space-before:size
+#
+# All the above can also be given for "default" and apply before other defaults and
+# global/local setting.  "default" can also be given:
+#   xscale:size yscale:size
+#
+# All sizes are in "points".  A "point" is either the height of the window divided by
+# the yscale size, or the width divided by xscale size, whichever is smaller.
+#
+# global defaults are:
+
+default_attrs = "normal 10 family:sans fg:black bg:white left:5 space-after:1 space-before:1 xscale:10 yscale:40"
+
+import re
+
+def take(name, place, args, default=None):
+    if name in args:
+        place.append(args[name])
+    else:
+        place.append(default)
+    return 1
+
+class PresenterPane(edlib.Pane):
+    def __init__(self, focus):
+        edlib.Pane.__init__(self, focus, self.handle)
+        self.globals = {}
+        self.pageview = focus.call("doc:add-view") - 1
+        self.attrview = focus.call("doc:add-view") - 1
+        self.borderless = False
+
+    def marks_same(self, m1, m2):
+        if isinstance(m1, edlib.Mark) and isinstance(m1, edlib.Mark):
+            return 1 == self.call("doc:mark-same", m1, m2);
+        return None
+
+    def first_page(self):
+        m = []
+        self.call("doc:vmark-get", self.pageview, lambda key, **a: take('mark', m, a))
+        return m[0]
+
+    def first_line(self):
+        m = []
+        self.call("doc:vmark-get", self.attrview, lambda key, **a: take('mark', m, a))
+        return m[0]
+
+    def prev_page(self, m):
+        m2 = []
+        self.call("doc:vmark-get", self.pageview, 3, m, lambda key, **a: take('mark2', m2, a))
+        return m2[0]
+
+    def prev_line(self, m):
+        m2 = []
+        self.call("doc:vmark-get", self.attrview, 3, m, lambda key, **a: take('mark2', m2, a))
+        return m2[0]
+
+    def get_line_at(self, m):
+        # call render-line at m
+        line = []
+        self.parent.call_filter("render-line", m, -1, lambda key, **a: take('str', line, a, ''))
+        return line[0]
+
+    def get_line_before(self, m):
+        m2 = m.dup(1)
+        ret = self.parent.call_filter("render-line-prev", m2, 1)
+        if ret <= 0:
+            m2.unlink()
+            return None
+        l = self.get_line_at(m2)
+        m2.unlink()
+        return l
+
+    def print_line_at(self, m):
+        x = m.dup(1)
+        l = self.get_line_at(x)
+        x.unlink()
+        return l[0:-1]
+
+    def find_page(self, start, sof = False):
+        # Assumming 'm' is either start-of-file (if 'sof') or the start of
+        # a page, find the start of the next page.  Return an untyped mark
+        # or None if there is no next page.
+        # A page starts with some ":attr:" lines and then a "# " line.
+        # If not 'sof' we first need to skip over the start of 'this' page.
+        skipping = not sof
+        m = start.dup(1)
+        maybe = None
+        globals = None
+        if sof:
+            globals = {}
+            extra = {}
+        while True:
+            if not maybe and not skipping:
+                maybe = m.dup()
+            l = self.get_line_at(m)
+            if not l:
+                # End of document
+                if maybe:
+                    maybe.unlink()
+                m.unlink()
+                return None
+            if l[0] == ':':
+                # attribute - doesn't advance possible start
+                if globals is not None:
+                    mt = re.match("^:([^: ]+):(.*)", l.strip('\n'))
+                    if mt:
+                        extra[mt.group(1)] = mt.group(2)
+            elif l[0:2] == "# ":
+                # level-1 heading. This is it if not skipping
+                if skipping:
+                    skipping = False
+                else:
+                    m.unlink()
+                    if globals is not None:
+                        self.globals = globals
+                        if 'background' in globals:
+                            self['background'] = globals['background']
+                        if 'scale' in globals:
+                            self['scale'] = globals['scale']
+                        self.call("render-lines:redraw")
+                    return maybe
+            else:
+                # not part of start-of-page
+                skipping = False
+                if maybe:
+                    maybe.unlink()
+                maybe = None
+                if globals is not None:
+                    globals.update(extra)
+                    extra = {}
+
+    def check_start(self, start):
+        # This was the start of a page, but might not be any more.  Must check.
+        # Following lines must be ":attr:" or "# ".
+        # Preceeding line, if any must not be ":attr:"
+        m = start.dup(1)
+        l = self.get_line_at(m)
+        while l and l[0] == ':':
+            l = self.get_line_at(m)
+        m.unlink()
+        if not l or l[0:2] != '# ':
+            # No appropriate heading
+            return False
+
+        m = start.dup(1)
+        l = self.get_line_before(m)
+        m.unlink()
+        if l and l[0] == ':':
+            return False
+        return True
+
+    def find_pages(self, m):
+        # I need to find the start of the page that contains 'm' - though if m is before
+        # the first page, then I want the first page.
+        # I need to know that the 'next' page after than really is the start of the following
+        # page, or is None iff this is the last page.
+        # Each page mark has a 'valid' attribute which confirms it is at start of page,
+        # and a 'next-valid' attribute which confirms next page is valid.  These are clear
+        # when nearby text changes.
+        # The PresenterPane has a 'first_valid' flag is the first page mark is valid.
+        # it is like 'next-valid'.
+        # If these check out, answer is easy.  If they don't we need to revalidate anything
+        # that doesn't match and either set a flag or delete a mark or add a mark.  Then
+        # try again.
+        pm = None
+        while not pm:
+            pm = self.prev_page(m)
+            if not pm:
+                # could be in pre-amble
+                pm = self.first_page()
+                if not pm:
+                    # no marks at all.  Create mark-at-start and find page
+                    pma=[]
+                    self.call("doc:vmark-get", self.pageview, 2, lambda key,**a: take('mark2', pma, a))
+                    pm = pma[0]
+                    first = self.find_page(pm, True)
+                    if first:
+                        # Good, there is a page.
+                        pm.to_mark(first)
+                        self.first_valid = True
+                        pm['valid'] = 'yes'
+                        first.unlink()
+                        pm = None
+                        continue
+                    # no pages at all
+                    break
+                if not self.first_valid:
+                    # We have a first page, but it might not be valid
+                    pma=[]
+                    self.call("doc:vmark-get", -2, 2, lambda key,**a: take('mark2', pma, a))
+                    t = pma[0]
+                    first = self.find_page(t, True)
+                    t.unlink()
+                    if not first:
+                        # no pages any more
+                        pass
+                    elif self.marks_same(pm, first):
+                        # Oh good!
+                        self.first_valid = True
+                        pm['valid'] = 'yes'
+                        first.unlink()
+                    elif first < pm:
+                        pm.to_mark(first)
+                        self.first_valid = True
+                        pm['valid'] = 'yes'
+                        # but having moved,..
+                        pm['next-valid'] = 'no'
+                        first.unlink()
+                    else:
+                        # .first_page() is before real first, just delete it
+                        pm.unlink()
+                        first.unlink()
+                    pm = None
+                    continue
+                # So this first page is valid, but is after the target.
+                # Fall through to checking next-page
+            elif pm['valid'] != 'yes':
+                # We have a previous page but need to validate it
+                if self.check_start(pm):
+                    pm['valid'] = 'yes'
+                else:
+                    if pm.prev():
+                        pm.prev()['next-valid'] = 'no'
+                    else:
+                        self.first_valid = False
+                    pm.unlink()
+                    pm = None
+                    continue
+
+            # pm is the start of this page, just need to check next
+            if pm['next-valid'] != 'yes':
+                next = self.find_page(pm)
+                if not next:
+                    # this is last page
+                    while pm.next():
+                        pm.next().unlink()
+                    pm['next-valid'] = 'yes'
+                    # all good now
+                elif pm.next() is None:
+                    n = pm.dup()
+                    n.to_mark(next)
+                    next.unlink()
+                    pm['next-valid'] = 'yes'
+                else:
+                    if self.marks_same(next, pm.next()):
+                        pm['next-valid'] = 'yes'
+                    elif next <= pm.next():
+                        pm.next().to_mark(next)
+                    else:
+                        pm.next().unlink()
+                    next.unlink()
+                    pm = None
+
+        if not self.first_valid:
+            # need to update globals
+            pma=[]
+            self.call("doc:vmark-get", -2, 2, lambda key,**a: take('mark2', pma, a))
+            t = pma[0]
+            first = self.find_page(t, True)
+            if first:
+                self.first_valid = True
+            t.unlink()
+
+        #x = self.first_page()
+        #while x:
+        #    print "PAGE:", x.seq, self.print_line_at(x)
+        #    x = x.next()
+        #print "RETURN", pm.seq
+        return pm
+
+    # We maintain marks at the start of every line in the current page
+    # These have tags storing attributes extracted from the line.
+    # - If there are marks not in the current page, discard them.
+    # - If there are no marks, create them
+    # - get an attribute by searching back for marks of the right type
+    #   If one has no type, validate it
+    #
+    def clean_lines(self, page):
+        next = page.next()
+        first = self.first_line()
+        while first and ((first < page and not self.marks_same(first,page)) or
+                         (next and first > next and not self.marks_same(first, next))):
+            # first is outside this page
+            first.unlink()
+            first = self.first_line()
+
+    def annotate(self, mark, line):
+        m = re.match("^:([^: ]+):(.*)", line.strip('\n'))
+        if m:
+            mark['type'] = 'attr:'+m.group(1)
+            mark['value'] = m.group(2)
+        else:
+            mark['type'] = 'text'
+            mark['value'] = '-None-'
+
+    def mark_lines(self, page):
+        first = self.first_line()
+        if first:
+            return
+        # there are currently no lines
+        next = page.next()
+        la=[]
+        self.call("doc:vmark-get", self.attrview, 2, lambda key,**a: take('mark2', la, a))
+        line = la[0]
+
+        while not next or (line < next and not self.marks_same(line, next)):
+            # There is a line there that we care about - unless EOF
+            this = line.dup()
+            l = self.get_line_at(line)
+            if not l:
+                break
+            self.annotate(this, l)
+        line.unlink()
+
+    def get_local_attr(self, m, attr):
+        t = 'attr:' + attr
+        l = self.prev_line(m)
+        while l:
+            if l['type'] == 'unknown':
+                # Ouch - need to re-evaluate.  Just assume still at start-of-line
+                self.annotate(l, self.print_line_at(l))
+                pass
+            if l['type'] == t:
+                return l['value']
+            l = l.prev()
+        return None
+
+    para_ptn =  ['# ', '## ', '> ', '- ', '    ', '!',  None, '']
+    para_type = ['H1', 'H2',  'I',  'L1', 'C',    'IM', 'BL', 'P']
+    defaults = {
+        'H1': 'center,large,family:serif',
+        'H2': 'right:20,fg:red,bold',
+        'I': 'left:5,family:Mufferaw',
+        'C': 'family:mono',
+        'L1': 'fg:blue,left:40,family:sans',
+        'P': 'left:30,family:sans,space-below:20',
+        'BL': '3',
+        }
+
+    def handle(self, key, **a):
+        if key == "render-line-prev":
+            # Go to start of page
+            here = a['mark']
+            start = self.find_pages(here)
+            if not start:
+                return -2
+            if start > here:
+                start = here
+
+            if self.marks_same(start, here) and a['numeric'] == 1:
+                return -2
+            here.to_mark(start)
+            return 1
+
+        if key == "render-line":
+            here = a['mark']
+            cb = a['comm2']
+            page = self.find_pages(here)
+            if not page:
+                # No pages at all
+                cb("callback", self)
+                return 1
+
+            if here < page:
+                here.to_mark(page)
+
+            self.clean_lines(page)
+            self.mark_lines(page)
+
+            end = page.next()
+
+            line = None
+            while end is None or here < end:
+                lines = []
+                self.parent.call_filter("render-line", here,a['numeric'],
+                                 lambda key2, **aa: take('str', lines, aa))
+                if len(lines) == 0 or lines[0] is None:
+                    line = None
+                    break
+                line = lines[0]
+                if line[0] == ':':
+                    #skip attributes
+                    continue
+                line = line.strip("\n")
+                break
+
+            if line is None:
+                cb("callback", self)
+            else:
+                mode = 'P'
+                for n in range(len(self.para_ptn)):
+                    prefix = self.para_ptn[n]
+                    if prefix == None and line == "":
+                        mode = self.para_type[n]
+                        break
+                    if prefix is not None and prefix == line[0:len(prefix)]:
+                        mode = self.para_type[n]
+                        line = line[len(prefix):]
+                        break
+                v = self.get_local_attr(here, mode)
+                if not v and mode in self.globals:
+                    v = self.globals[mode]
+                if not v and mode in self.defaults:
+                    v = self.defaults[mode]
+                if not v:
+                    v = ""
+
+                if mode == 'IM':
+                    cb("callback", self, "<image:"+line+",width=200,height=100>")
+                    return 1
+
+                line = re.sub("\*([A-Za-z0-9][^*<]*)\*", "<italic>\\1</>", line)
+                line = re.sub("`([A-Za-z0-9][^*<]*)`", "<family:mono>\\1</>", line)
+                line = "<"+v+">"+ line + "</>"
+                line += '\n'
+                if end and (here > end or self.marks_same(here,end)):
+                    line += '\f'
+                cb("callback", self, line)
+            return 1
+
+        if key == "Notify:Replace":
+            m = a['mark']
+            # A change has happened at 'm'.  The following page might not
+            # be valid, and the previous may not be valid or have next-valid.
+            # If no previous, self.first_valid may not be.
+            page = self.prev_page(m)
+            if not page:
+                # m is before first page
+                page = self.first_page()
+                if page:
+                    self.first_valid = False
+                    page['valid'] = 'no'
+                # attributes probably changed so...
+                self.call("render-lines:redraw")
+            else:
+                page['valid'] = 'no'
+                page['next-valid'] = 'no'
+                page = page.next()
+                if page:
+                    page['valid'] = 'no'
+
+            l = self.prev_line(m)
+            if l:
+                if l['type'] and l['type'][0:5] == "attr:":
+                    self.call("render-lines:redraw")
+                l['type'] = 'unknown'
+                l = l.next()
+                if l:
+                    l['type'] = 'unknown'
+            return 1
+
+        if key == "Close":
+            # destroy all marks
+            self.release()
+
+        if key == "Clone":
+            # Need to create a new PresenterPane I guess, then recurse on children
+            pass
+
+        if key == "M-Chr-f":
+            if self.borderless:
+                self.call("Window:borderless", -1)
+                self.call("Window:fullscreen", -1)
+                self.borderless = False
+            else:
+                self.call("Window:borderless", 1)
+                self.call("Window:fullscreen", 1)
+                self.borderless = True
+            return 1
+
+        if key == "Next":
+            p = a['mark']
+            page = self.find_pages(p)
+            if a['numeric'] < 0:
+                page = page.prev()
+            else:
+                page = page.next()
+            if page is not None:
+                p.to_mark(page)
+                self.call("Move-View-Pos", page)
+                a['focus'].damage(1)
+            return 1
+        if key == "Prior":
+            p = a['mark']
+            page = self.find_pages(p)
+            if a['numeric'] < 0:
+                page = page.next()
+            else:
+                page = page.prev()
+            if page is not None:
+                p.to_mark(page)
+                self.call("Move-View-Pos", page)
+                a['focus'].damage(1)
+            return 1
+
+        return None
+
+def present_attach(key, focus, comm2, **a):
+    p = PresenterPane(focus)
+    p['render-wrap'] = 'no'
+    p['background'] = 'color:yellow'
+
+    p.call("Request:Notify:Replace")
+    p = p.render_attach("lines")
+    comm2('callback', p)
+    return 1
+
+editor.call("global-set-command", pane, "render-present-attach", present_attach)
index 72d0808dea0aa21cc3e50c0a9212db7859bad229..918a19d65bc9db0b3ce28b810d0c05c80f0061a0 100644 (file)
@@ -25,7 +25,7 @@
  * The returned line can contain attribute markings as <attr,attr>.
  * </> is used to pop most recent attributes.  << is used to include a literal '<'.
  * Lines generally contains UTF-8.  Control character '\n' is end of line and
- * '\t' tabs 1-8 spaces.  Other control characters should be rendered as
+ * '\t' tabs 1-<8 spaces.  Other control characters should be rendered as
  * e.g. <fg:red>^X</> - in particular, nul must not appear in the line.
  *
  * We currently assume a constant-width font 1x1.
  * the slow way if the target point wasn't found.
  */
 
+#define _GNU_SOURCE for-asprintf
 #include <unistd.h>
 #include <stdlib.h>
 #include <wchar.h>
 #include <string.h>
-
+#include <stdio.h>
 #define        MARK_DATA_PTR char
 #include "core.h"
 #include "misc.h"
@@ -112,7 +113,7 @@ DEF_CMD(text_size_callback)
 #define CURS 2
 
 static int draw_some(struct pane *p, int *x, int y, char *start, char **endp,
-                    char *attr, int margin, int cursorpos, int cursx)
+                    char *attr, int margin, int cursorpos, int cursx, int scale)
 {
        /* draw some text from 'start' for length 'len' into p[x,y].
         * Update 'x' and 'startp' past what as drawn.
@@ -124,7 +125,7 @@ static int draw_some(struct pane *p, int *x, int y, char *start, char **endp,
         */
        int len = *endp - start;
        char *str = strndup(start, len);
-       struct call_return cr;
+       struct call_return cr = {0};
        int max;
        int ret = WRAP;
        int rmargin = p->w - margin;
@@ -135,24 +136,25 @@ static int draw_some(struct pane *p, int *x, int y, char *start, char **endp,
        }
 
        cr.c = text_size_callback;
-       call_comm7("text-size", p, rmargin - *x, NULL, str, 0, attr, &cr.c);
+       /* FIXME check error - python might crash */
+       call_comm7("text-size", p, rmargin - *x, NULL, str, scale, attr, &cr.c);
        max = cr.i;
        if (max == 0 && ret == CURS) {
                /* must already have CURS position. */
                ret = WRAP;
                rmargin = p->w - margin;
-               call_comm7("text-size", p, rmargin - *x, NULL, str, 0, attr, &cr.c);
+               call_comm7("text-size", p, rmargin - *x, NULL, str, scale, attr, &cr.c);
                max = cr.i;
        }
        if (max < len) {
                str[max] = 0;
-               call_comm7("text-size", p, rmargin - *x, NULL, str, 0, attr, &cr.c);
+               call_comm7("text-size", p, rmargin - *x, NULL, str, scale, attr, &cr.c);
        }
        if (y >= 0) {
                if (cursorpos >= 0 && cursorpos <= len)
-                       call_xy("text-display", p, cursorpos, str, attr, *x, y);
+                       call_xy7("text-display", p, cursorpos, scale, str, attr, *x, y, NULL, NULL);
                else
-                       call_xy("text-display", p, -1, str, attr, *x, y);
+                       call_xy7("text-display", p, -1, scale, str, attr, *x, y, NULL, NULL);
        }
        free(str);
        *x += cr.x;
@@ -163,36 +165,65 @@ static int draw_some(struct pane *p, int *x, int y, char *start, char **endp,
        return ret;
 }
 
-static void update_line_height_attr(struct pane *p, int *h, int *a, char *attr)
+static void update_line_height_attr(struct pane *p, int *h, int *a, int *w, char *attr, char *str, int scale)
 {
        struct call_return cr;
        cr.c = text_size_callback;
-       call_comm7("text-size", p, -1, NULL, "M", 0, attr, &cr.c);
+       call_comm7("text-size", p, -1, NULL, str, scale, attr, &cr.c);
        if (cr.y > *h)
                *h = cr.y;
        if (cr.i2 > *a)
                *a = cr.i2;
+       if (w)
+               *w += cr.x;
 }
 
-static void update_line_height(struct pane *p, int *h, int *a, char *line)
+static void update_line_height(struct pane *p, int *h, int *a, int *w, int *center, char *line,
+                              int scale)
 {
        struct buf attr;
+       char *tst = line;
+       char *l;
+       int above = 0, below = 0;
+       int attr_found = 0;
 
        buf_init(&attr);
-       update_line_height_attr(p, h, a, "");
+       buf_append(&attr, ',');
        while (*line) {
                char c = *line++;
                char *st = line;
                if (c != '<' || *line == '<')
                        continue;
 
+               if (line-1 > tst) {
+                       l = strndup(tst, line-1-tst);
+                       update_line_height_attr(p, h, a, w, buf_final(&attr), l, scale);
+                       free(l);
+               }
                while (*line && line[-1] != '>')
                        line += 1;
+               tst = line;
                if (st[0] != '/') {
+                       char *c;
                        buf_concat_len(&attr, st, line-st);
                        attr.b[attr.len-1] = ',';
                        buf_append(&attr, ',');
-                       update_line_height_attr(p, h, a, buf_final(&attr));
+                       if (center && strstr(buf_final(&attr), ",center,"))
+                               *center = 1;
+                       if (center && (c=strstr(buf_final(&attr), ",left:")) != NULL) {
+                               *center = atoi(c+6) * scale/1000;
+                       }
+                       if (center && (c=strstr(buf_final(&attr), ",right:")) != NULL) {
+                               *center = - atoi(c+7) * scale/1000;
+                       }
+                       if ((c=strstr(buf_final(&attr), ",space-above:")) != NULL) {
+                               above = atoi(c+13) * scale/1000;
+                       }
+                       if ((c=strstr(buf_final(&attr), ",space-below:")) != NULL) {
+                               below = atoi(c+13) * scale/1000;
+                       }
+                       attr_found = 1;
+                       update_line_height_attr(p, h, a, w, buf_final(&attr), "", scale);
                } else {
                        /* strip back to ",," */
                        if (attr.len > 0)
@@ -203,13 +234,64 @@ static void update_line_height(struct pane *p, int *h, int *a, char *line)
                                attr.len -= 1;
                }
        }
+       if (line[-1] == '\n')
+               line -= 1;
+       if (line > tst || !attr_found) {
+               l = strndup(tst, line-tst);
+               update_line_height_attr(p, h, a, w, buf_final(&attr), l, scale);
+               free(l);
+       }
+       *h += above + below;
+       *a += above;
        free(buf_final(&attr));
 }
 
+static void render_image(struct pane *p, char *line, int *yp, int dodraw, int scale)
+{
+       char *cp, *e;
+       char *fname = NULL;
+       int width = p->w/2, height = p->h/2;
+       int y;
+
+       while (*line == '<')
+               line ++;
+       while (line) {
+               e = strchr(line, ',');
+               if (!e)
+                       e = strchr(line, '>');
+               if (strncmp(line, "image:", 6) == 0) {
+                       cp = line + 6;
+                       if (e)
+                               fname = strndup(cp, e-cp);
+                       else
+                               fname = strdup(cp);
+               } else if (strncmp(line, "width:", 6) == 0) {
+                       width = atoi(line+6);
+                       width = width * scale / 1000;
+               } else if (strncmp(line, "height:", 7) == 0) {
+                       height = atoi(line+7);
+                       height = height * scale / 1000;
+               }
+               if (e)
+                       e++;
+               line = e;
+       }
+       y = *yp;
+       *yp = y + height;
+       if (!fname)
+               return;
+       if (!dodraw) {
+               free(fname);
+               return;
+       }
+       call_xy7("image-display", p, width, height, fname, NULL, (p->w - width)/2, y,
+                NULL, NULL);
+}
+
 /* render a line, with attributes and wrapping.  Report line offset where
  * cursor point cx,cy is passed. -1 if never seen.
  */
-static void render_line(struct pane *p, char *line, int *yp, int dodraw,
+static void render_line(struct pane *p, char *line, int *yp, int dodraw, int scale,
                       int *cxp, int *cyp, int *offsetp)
 {
        int x = 0;
@@ -226,17 +308,33 @@ static void render_line(struct pane *p, char *line, int *yp, int dodraw,
        int ascent = -1;
        int mwidth = -1;
        int ret = 0;
+       int twidth = 0;
+       int center = 0;
+
+       /* Temporary hack (I hope) */
+       if (strncmp(line, "<image", 6) == 0) {
+               render_image(p, line, yp, dodraw, scale);
+               if (cxp) *cxp = -1;
+               if (cyp) *cyp = -1;
+               if (offsetp) *offsetp = -1;
+               return;
+       }
 
-       update_line_height(p, &line_height, &ascent, line);
+       update_line_height(p, &line_height, &ascent, &twidth, &center, line, scale);
 
        if (prefix) {
                char *s = prefix + strlen(prefix);
-               update_line_height_attr(p, &line_height, &ascent, "bold");
-               draw_some(p, &x, dodraw?y+ascent:-1, prefix, &s, "bold", 0, -1, -1);
+               update_line_height_attr(p, &line_height, &ascent, NULL, "bold", prefix, scale);
+               draw_some(p, &x, dodraw?y+ascent:-1, prefix, &s, "bold", 0, -1, -1, scale);
        }
        rl->prefix_len = x;
+       if (center == 1)
+               x += (p->w - x - twidth)/2;
+       if (center > 1)
+               x += center;
+       if (center < 0)
+               x = p->w - x - twidth + center;
        rl->line_height = line_height;
-
        buf_init(&attr);
        buf_append(&attr, ' '); attr.len = 0;
        if (cxp && cyp && offsetp) {
@@ -270,17 +368,22 @@ static void render_line(struct pane *p, char *line, int *yp, int dodraw,
                        mwidth = cr.x;
                }
 
-               if (ret == WRAP) {
+               if (ret == WRAP && wrap) {
                        char buf[2], *b;
                        strcpy(buf, "\\");
                        b = buf+1;
                        x = p->w - mwidth;
                        draw_some(p, &x, dodraw?y+ascent:-1, buf, &b, "underline,fg:blue",
-                                 0, -1, -1);
+                                 0, -1, -1, scale);
 
                        x = 0;
                        y += line_height;
                }
+               if (ret == WRAP && !wrap) {
+                       while (*line && *line != '\n' && *line != '<')
+                               line += 1;
+                       start = line;
+               }
 
                ch = *line;
 
@@ -320,20 +423,22 @@ static void render_line(struct pane *p, char *line, int *yp, int dodraw,
                            (line-start) * mwidth > p->w - x) {
                                ret = draw_some(p, &x, dodraw?y+ascent:-1, start,
                                                &line,
-                                               buf_final(&attr), mwidth, CP, CX);
+                                               buf_final(&attr), wrap ? mwidth : 0, CP, CX, scale);
                                start = line;
                        }
                        continue;
                }
                ret = draw_some(p, &x, dodraw?y+ascent:-1, start, &line,
-                               buf_final(&attr), mwidth, CP, CX);
+                               buf_final(&attr), wrap ? mwidth : 0, CP, CX, scale);
+               if (!wrap && ret == WRAP && line == start)
+                       ret = 0;
                start = line;
                if (ret)
                        continue;
                if (ch == '<') {
                        line += 1;
                        if (*line == '<') {
-                               if (offset == start - line_start)
+                               if (offset >= 0 && offset == start - line_start)
                                        offset += 1;
                                start = line;
                                line += 1;
@@ -392,9 +497,10 @@ static void render_line(struct pane *p, char *line, int *yp, int dodraw,
                        buf[2] = 0;
                        b = buf+2;
                        buf_concat(&attr, ",underline,fg:red");
+                       if (ch == '\f') buf[0] = 0;
                        ret = draw_some(p, &x, dodraw?y+ascent:-1, buf, &b,
                                        buf_final(&attr),
-                                       mwidth*2, CP, CX);
+                                       wrap ? mwidth*2 : 0, CP, CX, scale);
                        attr.len = l;
                        start = line;
                }
@@ -403,7 +509,7 @@ static void render_line(struct pane *p, char *line, int *yp, int dodraw,
        if (!*line && (line > start || offset == start - line_start)) {
                /* Some more to draw */
                draw_some(p, &x, dodraw?y+ascent:-1, start, &line,
-                         buf_final(&attr), mwidth, offset - (start - line_start), cx);
+                         buf_final(&attr), wrap ? mwidth : 0, offset - (start - line_start), cx, scale);
        }
        if (y + line_height < cy ||
            (y <= cy && x <= cx))
@@ -437,7 +543,7 @@ static struct mark *call_render_line_prev(struct pane *p,
                return NULL;
        }
        /* if n>0 we can fail because start-of-file was found before
-        * and newline.  In that case ret == -2, and we return NULL.
+        * any newline.  In that case ret == -2, and we return NULL.
         */
        if (found)
                *found = (ret != -1);
@@ -558,19 +664,44 @@ static int call_render_line_to_point(struct pane *p, struct mark *pm,
        return len;
 }
 
+static int get_scale(struct pane *p)
+{
+       char *sc = pane_attr_get(p, "scale");
+       int x, y;
+       int xscale, yscale, scale;
+
+       if (!sc)
+               return 1000;
+
+       if (sscanf(sc, "x:%d,y:%d", &x, &y) != 2)
+               return 1000;
+
+       /* 'scale' is pixels per point times 1000.
+        * ":scale:x" is points across pane, so scale = p->w/x*1000
+        */
+       xscale = 1000 * p->w / x;
+       yscale = 1000 * p->h / y;
+       scale = (xscale < yscale) ? xscale: yscale;
+       if (scale < 10)
+               scale = 1000;
+       return scale;
+}
+
 static void find_lines(struct mark *pm, struct pane *p)
 {
        struct rl_data *rl = p->data;
        struct mark *top, *bot;
        struct mark *m;
        struct mark *start, *end;
+       int x;
        int y = 0;
        int offset;
        int found_start = 0, found_end = 0;
        int lines_above = 0, lines_below = 0;
+       int scale = get_scale(p);
 
-       if (pm->viewnum != MARK_POINT)
-               return;
+//     if (pm->viewnum != MARK_POINT)
+//             return;
 
        top = vmark_first(p, rl->typenum);
        bot = vmark_last(p, rl->typenum);
@@ -586,14 +717,15 @@ static void find_lines(struct mark *pm, struct pane *p)
                m = vmark_next(start);
 
        end = m;
-       if (start->mdata) {
-               int x;
-               x = -1; lines_above = -1; y = 0;
-               render_line(p, start->mdata, &y, 0, &x, &lines_above, &offset);
-               lines_below = y - lines_above;
-       }
-       y = 1;
-       /* We have start/end of the focus line, and its height */
+       x = -1; lines_above = -1; y = 0;
+       render_line(p, start->mdata ?: "", &y, 0, scale, &x, &lines_above, &offset);
+       //lines_below = y - lines_above;
+       lines_above =  lines_below = 0;
+
+       /* We have start/end of the focus line, and its height
+        * Rendering just that "line" uses a height of 'y', of which
+        * 'lines_above' is above the cursor, and 'lines_below' is below.
+        */
        if (bot && !mark_ordered_or_same_pane(p, bot, start))
                /* already before 'bot', so will never "cross over" bot, so
                 * ignore 'bot'
@@ -602,64 +734,85 @@ static void find_lines(struct mark *pm, struct pane *p)
        if (top && !mark_ordered_or_same_pane(p, end, top))
                top = NULL;
 
-       rl->skip_lines = 0;
-       while (!((found_start && found_end) || y >= p->h - rl->header_lines)) {
-               if (!found_start) {
+       while ((!found_start || !found_end) && y < p->h - rl->header_lines) {
+               if (!found_start && lines_above == 0) {
                        /* step backwards moving start */
-                       if (lines_above > 0) {
-                               lines_above -= 1;
-                               y += 1;
+                       m = call_render_line_prev(p, mark_dup(start, 0),
+                                                 1, &rl->top_sol);
+                       if (!m) {
+                               /* no text before 'start' */
+                               found_start = 1;
                        } else {
-                               m = call_render_line_prev(p, mark_dup(start, 0),
-                                                         1, &rl->top_sol);
-                               if (!m) {
-                                       /* no text before 'start' */
-                                       found_start = 1;
-                               } else {
-                                       int h = 0;
-                                       start = m;
-                                       if (!start->mdata)
-                                               call_render_line(p, start);
-                                       render_line(p, start->mdata, &h, 0,
+                               int h = 0;
+                               start = m;
+                               if (!start->mdata)
+                                       call_render_line(p, start);
+                               if (start->mdata)
+                                       render_line(p, start->mdata, &h, 0, scale,
                                                    NULL, NULL, NULL);
-                                       if (h) {
-                                               lines_above = h - 1;
-                                               y += 1;
-                                       } else
-                                               found_start = 1;
-                               }
-                               if (bot && mark_ordered(start, bot))
-                                       found_end = 1;
+                               if (h) {
+                                       lines_above = h;
+                               } else
+                                       found_start = 1;
                        }
+                       if (bot && mark_ordered(start, bot))
+                               found_end = 1;
                }
-               if (!found_end) {
+               if (!found_end && lines_below == 0) {
                        /* step forwards */
-                       if (lines_below > 0) {
-                               lines_below -= 1;
-                               y += 1;
-                       } else {
-                               if (!end->mdata)
-                                       call_render_line(p, end);
-                               if (!end->mdata)
+                       if (!end->mdata)
+                               call_render_line(p, end);
+                       if (!end->mdata)
+                               found_end = 1;
+                       else {
+                               int h = 0;
+                               render_line(p, end->mdata, &h, 0, scale,
+                                           NULL, NULL, NULL);
+                               if (end->mdata[strlen(end->mdata)-1] == '\f')
+                                       found_end = 1;
+                               end = vmark_next(end);
+                               ASSERT(end != NULL);
+                               if (h) {
+                                       lines_below = h;
+                               } else
                                        found_end = 1;
-                               else {
-                                       int h = 0;
-                                       render_line(p, end->mdata, &h, 0,
-                                                   NULL, NULL, NULL);
-                                       end = vmark_next(end);
-                                       ASSERT(end != NULL);
-                                       if (h) {
-                                               lines_below = h - 1;
-                                               y += 1;
-                                       } else
-                                               found_end = 1;
-                               }
-                               if (top && mark_ordered(top, end))
-                                       found_start = 1;
                        }
+                       if (top && mark_ordered(top, end))
+                               found_start = 1;
+               }
+               if (lines_above > 0 && lines_below > 0) {
+                       int consume = (lines_above > lines_below ? lines_below:lines_above) * 2;
+                       if (consume > (p->h - rl->header_lines) - y)
+                               consume = (p->h - rl->header_lines) - y;
+                       if (lines_above > lines_below) {
+                               lines_above -= consume - (consume/2);
+                               lines_below -= consume/2;
+                       } else {
+                               lines_below -= consume - (consume/2);
+                               lines_above -= consume/2;
+                       }
+                       y += consume;
+                       /* We have just consumed all of one of lines_{above,below}
+                        * so they are no longer both > 0 */
+               }
+               if (found_end && lines_above) {
+                       int consume = p->h - rl->header_lines - y;
+                       if (consume > lines_above)
+                               consume = lines_above;
+                       lines_above -= consume;
+                       y += consume;
+               }
+               if (found_start && lines_below) {
+                       int consume = p->h - rl->header_lines - y;
+                       if (consume > lines_below)
+                               consume = lines_below;
+                       lines_below -= consume;
+                       y += consume;
                }
        }
        rl->skip_lines = lines_above;
+       if (start->seq > end->seq)
+               end = start;
        /* Now discard any marks outside start-end */
        while ((m = vmark_prev(start)) != NULL) {
                free(m->mdata);
@@ -682,17 +835,25 @@ static void render(struct mark *pm, struct pane *p)
        struct mark *m, *m2;
        int restarted = 0;
        char *hdr;
+       char *bg;
+       int scale = get_scale(p);
 
        hdr = pane_attr_get(p, "heading");
        if (hdr && !*hdr)
                hdr = NULL;
 
 restart:
-       pane_clear(p, NULL);
-       y = 0;
+       bg = pane_attr_get(p, "background");
+       if (bg && strncmp(bg, "color:", 6) == 0) {
+               char *a;
+               asprintf(&a, "bg:%s", bg+6);
+               pane_clear(p, a);
+               free(a);
+       } else
+               pane_clear(p, NULL);
        if (hdr) {
                rl->header_lines = 0;
-               render_line(p, hdr, &y, 1, NULL, NULL, NULL);
+               render_line(p, hdr, &y, 1, scale, NULL, NULL, NULL);
                rl->header_lines = y;
        }
        y -= rl->skip_lines;
@@ -713,7 +874,7 @@ restart:
                        int len = call_render_line_to_point(p, pm,
                                                            m);
                        rl->cursor_line = y;
-                       render_line(p, m->mdata ?: "", &y, 1, &p->cx, &p->cy, &len);
+                       render_line(p, m->mdata ?: "", &y, 1, scale, &p->cx, &p->cy, &len);
                        if (p->cy < 0)
                                p->cx = -1;
                        if (!rl->do_wrap && p->cy >= 0 && p->cx < rl->prefix_len) {
@@ -744,7 +905,9 @@ restart:
                                }
                        }
                } else
-                       render_line(p, m->mdata?:"", &y, 1, NULL, NULL, NULL);
+                       render_line(p, m->mdata?:"", &y, 1, scale, NULL, NULL, NULL);
+               if (m->mdata && m->mdata[strlen(m->mdata)-1]=='\f')
+                       break;
                if (!m2)
                        break;
                m = m2;
@@ -842,6 +1005,7 @@ DEF_CMD(render_lines_move)
        struct rl_data *rl = p->data;
        struct mark *top;
        int pagesize = rl->line_height;
+       int scale = get_scale(p);
 
        top = vmark_first(p, rl->typenum);
        if (!top)
@@ -870,7 +1034,7 @@ DEF_CMD(render_lines_move)
                                call_render_line(p, top);
                        if (top->mdata == NULL)
                                break;
-                       render_line(p, top->mdata, &y, 0, NULL, NULL, NULL);
+                       render_line(p, top->mdata, &y, 0, scale, NULL, NULL, NULL);
                        rl->skip_lines = y;
                }
        } else {
@@ -882,7 +1046,7 @@ DEF_CMD(render_lines_move)
                                call_render_line(p, top);
                        if (top->mdata == NULL)
                                break;
-                       render_line(p, top->mdata, &y, 0, NULL, NULL, NULL);
+                       render_line(p, top->mdata, &y, 0, scale, NULL, NULL, NULL);
                        if (rl->skip_lines + rpt < y) {
                                rl->skip_lines += rpt;
                                break;
@@ -913,6 +1077,7 @@ DEF_CMD(render_lines_set_cursor)
        int y = rl->header_lines - rl->skip_lines;
        int found = 0;
        int cihx, cihy;
+       int scale = get_scale(p);
 
        render_lines_other_move_func(ci);
 
@@ -926,7 +1091,7 @@ DEF_CMD(render_lines_set_cursor)
                cihy = y;
        while (y <= cihy && m && m->mdata) {
                int cx = cihx, cy = cihy, o = -1;
-               render_line(p, m->mdata, &y, 0, &cx, &cy, &o);
+               render_line(p, m->mdata, &y, 0, scale, &cx, &cy, &o);
                if (o >= 0) {
                        struct mark *m2 = call_render_line_offset(p, m, o);
                        if (m2) {
@@ -955,7 +1120,7 @@ DEF_CMD(render_lines_move_pos)
        bot = vmark_last(p, rl->typenum);
        if (top && bot &&
            mark_ordered(top, pm) &&
-           mark_ordered(pm, bot))
+           mark_ordered(pm, bot) && !mark_same_pane(p, pm, bot, NULL))
                /* pos already displayed */
                return 1;
        find_lines(pm, ci->home);
@@ -978,6 +1143,7 @@ DEF_CMD(render_lines_move_line)
        struct cmd_info ci2 = {0};
        int target_x, target_y;
        int o = -1;
+       int scale = get_scale(p);
 
        rl->ignore_point = 0;
 
@@ -1017,7 +1183,7 @@ DEF_CMD(render_lines_move_line)
                        pane_damaged(p, DAMAGED_CONTENT);
                        return 1;
                }
-               render_line(p, start->mdata, &y, 0, &target_x, &target_y, &o);
+               render_line(p, start->mdata, &y, 0, scale, &target_x, &target_y, &o);
                /* 'o' is the distance from start-of-line of the target */
                if (o >= 0) {
                        struct mark *m2 = call_render_line_offset(
@@ -1096,6 +1262,7 @@ DEF_CMD(render_lines_redraw)
                free(m->mdata);
                m->mdata = NULL;
        }
+       pane_damaged(p, DAMAGED_SIZE);
        return 1;
 }