diff options
Diffstat (limited to 'src-old/frame.c')
-rw-r--r-- | src-old/frame.c | 1820 |
1 files changed, 1820 insertions, 0 deletions
diff --git a/src-old/frame.c b/src-old/frame.c new file mode 100644 index 0000000..08b8fd9 --- /dev/null +++ b/src-old/frame.c @@ -0,0 +1,1820 @@ +/* + * frame.c + * + * Copyright © 2013-2018 Thomas White <taw@bitwiz.org.uk> + * + * This file is part of Colloquium. + * + * Colloquium is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include <gdk-pixbuf/gdk-pixbuf.h> + +#include "sc_parse.h" +#include "frame.h" +#include "imagestore.h" +#include "utils.h" + +struct text_run +{ + SCBlock *scblock; + char *real_text; /* Usually NULL */ + PangoFontDescription *fontdesc; + double col[4]; +}; + +struct _paragraph +{ + enum para_type type; + double height; + float space[4]; + SCBlock *newline_at_end; + int empty; + + /* For PARA_TYPE_TEXT */ + int n_runs; + struct text_run *runs; + PangoLayout *layout; + PangoAlignment alignment; + + /* For anything other than PARA_TYPE_TEXT + * (for text paragraphs, these things are in the runs) */ + SCBlock *scblock; + + /* For PARA_TYPE_IMAGE */ + char *filename; + double image_w; + double image_h; + int image_real_w; + int image_real_h; + + /* For PARA_TYPE_CALLBACK */ + double cb_w; + double cb_h; + SCCallbackDrawFunc draw_func; + SCCallbackClickFunc click_func; + void *bvp; + void *vp; +}; + + +/* Returns the height of the paragraph including all spacing, padding etc */ +double paragraph_height(Paragraph *para) +{ + return para->height + para->space[2] + para->space[3]; +} + + +static int alloc_ro(struct frame *fr) +{ + struct frame **new_ro; + + new_ro = realloc(fr->children, + fr->max_children*sizeof(struct frame *)); + if ( new_ro == NULL ) return 1; + + fr->children = new_ro; + + return 0; +} + + +struct frame *frame_new() +{ + struct frame *n; + + n = calloc(1, sizeof(struct frame)); + if ( n == NULL ) return NULL; + + n->children = NULL; + n->max_children = 32; + if ( alloc_ro(n) ) { + fprintf(stderr, "Couldn't allocate children\n"); + free(n); + return NULL; + } + n->num_children = 0; + + n->scblocks = NULL; + n->n_paras = 0; + n->paras = NULL; + + return n; +} + + +static void free_paragraph(Paragraph *para) +{ + int i; + + for ( i=0; i<para->n_runs; i++ ) { + pango_font_description_free(para->runs[i].fontdesc); + free(para->runs[i].real_text); /* free(NULL) is OK */ + } + free(para->runs); + if ( para->layout != NULL ) g_object_unref(para->layout); + free(para); +} + + +void frame_free(struct frame *fr) +{ + int i; + + if ( fr == NULL ) return; + + /* Free paragraphs */ + if ( fr->paras != NULL ) { + for ( i=0; i<fr->n_paras; i++ ) { + free_paragraph(fr->paras[i]); + } + free(fr->paras); + } + + /* Free all children */ + for ( i=0; i<fr->num_children; i++ ) { + frame_free(fr->children[i]); + } + free(fr->children); + + free(fr); +} + + +struct frame *add_subframe(struct frame *fr) +{ + struct frame *n; + + n = frame_new(); + if ( n == NULL ) return NULL; + + if ( fr->num_children == fr->max_children ) { + fr->max_children += 32; + if ( alloc_ro(fr) ) return NULL; + } + + fr->children[fr->num_children++] = n; + + return n; +} + + +void show_frame_hierarchy(struct frame *fr, const char *t) +{ + int i; + char tn[1024]; + + strcpy(tn, t); + strcat(tn, " "); + + printf("%s%p (%.2f x %.2f)\n", t, fr, fr->w, fr->h); + + for ( i=0; i<fr->num_children; i++ ) { + show_frame_hierarchy(fr->children[i], tn); + } + +} + + +struct frame *find_frame_with_scblocks(struct frame *fr, SCBlock *scblocks) +{ + int i; + + if ( fr->scblocks == scblocks ) return fr; + + for ( i=0; i<fr->num_children; i++ ) { + struct frame *tt; + tt = find_frame_with_scblocks(fr->children[i], scblocks); + if ( tt != NULL ) return tt; + } + + return NULL; +} + + +static const char *text_for_run(const struct text_run *run) +{ + if ( run == NULL ) { + fprintf(stderr, "NULL run passed to text_for_run\n"); + return 0; + } + + if ( run->scblock == NULL ) { + fprintf(stderr, "NULL scblock in text_for_run\n"); + return 0; + } + + if ( run->real_text != NULL ) { + return run->real_text; + } + + return sc_block_contents(run->scblock); +} + + +static size_t run_text_len(const struct text_run *run) +{ + if ( run == NULL ) { + fprintf(stderr, "NULL run passed to run_text_len\n"); + return 0; + } + + if ( run->scblock == NULL ) { + fprintf(stderr, "NULL scblock in run_text_len\n"); + return 0; + } + + if ( run->real_text != NULL ) { + return strlen(run->real_text); + } + + if ( sc_block_contents(run->scblock) == NULL ) { + fprintf(stderr, "NULL scblock contents in run_text_len\n"); + return 0; + } + + return strlen(sc_block_contents(run->scblock)); +} + + +void wrap_paragraph(Paragraph *para, PangoContext *pc, double w, + size_t sel_start, size_t sel_end) +{ + size_t total_len = 0; + int i; + char *text; + PangoAttrList *attrs; + PangoRectangle rect; + size_t pos = 0; + + w -= para->space[0] + para->space[1]; + + if ( para->type == PARA_TYPE_IMAGE ) { + if ( para->image_w < 0.0 ) { + para->image_w = w; + para->image_h = w*((float)para->image_real_h/para->image_real_w); + } + para->height = para->image_h; + return; + } + + if ( para->type != PARA_TYPE_TEXT ) return; + + for ( i=0; i<para->n_runs; i++ ) { + total_len += run_text_len(¶->runs[i]); + } + + /* Allocate the complete text */ + text = malloc(total_len+1); + if ( text == NULL ) { + fprintf(stderr, "Couldn't allocate combined text (%lli)\n", + (long long int)total_len); + return; + } + + /* Allocate the attributes */ + attrs = pango_attr_list_new(); + + /* Put all of the text together */ + text[0] = '\0'; + for ( i=0; i<para->n_runs; i++ ) { + + PangoAttribute *attr; + const char *run_text; + size_t run_len; + guint16 r, g, b; + + run_text = text_for_run(¶->runs[i]); + run_len = strlen(run_text); + + attr = pango_attr_font_desc_new(para->runs[i].fontdesc); + attr->start_index = pos; + attr->end_index = pos + run_len; + pango_attr_list_insert(attrs, attr); + + r = para->runs[i].col[0] * 65535; + g = para->runs[i].col[1] * 65535; + b = para->runs[i].col[2] * 65535; + attr = pango_attr_foreground_new(r, g, b); + attr->start_index = pos; + attr->end_index = pos + run_len; + pango_attr_list_insert(attrs, attr); + + pos += run_len; + strncat(text, run_text, run_len); + + } + + /* Add attributes for selected text */ + if ( sel_start > 0 || sel_end > 0 ) { + PangoAttribute *attr; + attr = pango_attr_background_new(42919, 58853, 65535); + attr->start_index = sel_start; + attr->end_index = sel_end; + pango_attr_list_insert(attrs, attr); + } + + if ( para->layout == NULL ) { + para->layout = pango_layout_new(pc); + } + pango_layout_set_width(para->layout, pango_units_from_double(w)); + pango_layout_set_text(para->layout, text, total_len); + pango_layout_set_alignment(para->layout, para->alignment); + pango_layout_set_attributes(para->layout, attrs); + free(text); + pango_attr_list_unref(attrs); + + pango_layout_get_extents(para->layout, NULL, &rect); + para->height = pango_units_to_double(rect.height); +} + +static SCBlock *get_newline_at_end(Paragraph *para) +{ + return para->newline_at_end; +} + + +SCBlock *para_debug_get_newline_at_end(Paragraph *para) +{ + return get_newline_at_end(para); +} + + +void set_newline_at_end(Paragraph *para, SCBlock *bl) +{ + para->newline_at_end = bl; +} + + +void add_run(Paragraph *para, SCBlock *scblock, + PangoFontDescription *fdesc, double col[4], const char *real_text) +{ + struct text_run *runs_new; + + runs_new = realloc(para->runs, + (para->n_runs+1)*sizeof(struct text_run)); + if ( runs_new == NULL ) { + fprintf(stderr, "Failed to add run.\n"); + return; + } + + para->runs = runs_new; + para->runs[para->n_runs].scblock = scblock; + if ( real_text != NULL ) { + para->runs[para->n_runs].real_text = strdup(real_text); + } else { + para->runs[para->n_runs].real_text = NULL; + } + para->runs[para->n_runs].fontdesc = pango_font_description_copy(fdesc); + para->runs[para->n_runs].col[0] = col[0]; + para->runs[para->n_runs].col[1] = col[1]; + para->runs[para->n_runs].col[2] = col[2]; + para->runs[para->n_runs].col[3] = col[3]; + para->empty = 0; + para->n_runs++; +} + + +Paragraph *create_paragraph(struct frame *fr, SCBlock *bl) +{ + Paragraph **paras_new; + Paragraph *pnew; + + paras_new = realloc(fr->paras, (fr->n_paras+1)*sizeof(Paragraph *)); + if ( paras_new == NULL ) return NULL; + + pnew = calloc(1, sizeof(struct _paragraph)); + if ( pnew == NULL ) return NULL; + + fr->paras = paras_new; + fr->paras[fr->n_paras++] = pnew; + + /* For now, assume the paragraph is going to be for text. + * However, this can easily be changed */ + pnew->type = PARA_TYPE_TEXT; + pnew->scblock = bl; + pnew->n_runs = 0; + pnew->runs = NULL; + pnew->layout = NULL; + pnew->height = 0.0; + pnew->alignment = PANGO_ALIGN_LEFT; + pnew->empty = 1; + + return pnew; +} + + +/* Create a new paragraph in 'fr' just after paragraph 'pos' */ +Paragraph *insert_paragraph(struct frame *fr, int pos) +{ + Paragraph **paras_new; + Paragraph *pnew; + int i; + + if ( pos >= fr->n_paras ) { + fprintf(stderr, "insert_paragraph(): pos too high!\n"); + return NULL; + } + + paras_new = realloc(fr->paras, (fr->n_paras+1)*sizeof(Paragraph *)); + if ( paras_new == NULL ) return NULL; + + pnew = calloc(1, sizeof(struct _paragraph)); + if ( pnew == NULL ) return NULL; + + fr->paras = paras_new; + fr->n_paras++; + + for ( i=fr->n_paras-1; i>pos; i-- ) { + fr->paras[i] = fr->paras[i-1]; + } + fr->paras[pos+1] = pnew; + + return pnew; +} + + +Paragraph *add_callback_para(struct frame *fr, SCBlock *bl, double w, double h, + SCCallbackDrawFunc draw_func, + SCCallbackClickFunc click_func, void *bvp, + void *vp) +{ + Paragraph *pnew; + + if ( (fr->n_paras > 0) && (fr->paras[fr->n_paras-1]->empty) ) { + pnew = fr->paras[fr->n_paras-1]; + } else { + pnew = create_paragraph(fr, bl); + if ( pnew == NULL ) { + fprintf(stderr, "Failed to add callback paragraph\n"); + return NULL; + } + } + + pnew->type = PARA_TYPE_CALLBACK; + pnew->scblock = bl; + pnew->cb_w = w; + pnew->cb_h = h; + pnew->draw_func = draw_func; + pnew->click_func = click_func; + pnew->bvp = bvp; + pnew->vp = vp; + pnew->height = h; + pnew->empty = 0; + + return pnew; +} + + +void add_image_para(struct frame *fr, SCBlock *scblock, + const char *filename, + ImageStore *is, double w, double h, int editable) +{ + Paragraph *pnew; + int wi, hi; + + if ( is == NULL ) { + fprintf(stderr, "Adding image without ImageStore!\n"); + return; + } + + if ( (fr->n_paras > 0) && (fr->paras[fr->n_paras-1]->empty) ) { + pnew = fr->paras[fr->n_paras-1]; + } else { + pnew = create_paragraph(fr, scblock); + if ( pnew == NULL ) { + fprintf(stderr, "Failed to add image paragraph\n"); + return; + } + } + + if ( imagestore_get_size(is, filename, &wi, &hi) ) { + fprintf(stderr, _("Couldn't determine size of image '%s'\n"), + filename); + wi = 100; + hi = 100; + } + + pnew->type = PARA_TYPE_IMAGE; + pnew->scblock = scblock; + pnew->filename = strdup(filename); + pnew->image_w = w; + pnew->image_h = h; + pnew->image_real_w = wi; + pnew->image_real_h = hi; + pnew->height = h; + pnew->space[0] = 0.0; + pnew->space[1] = 0.0; + pnew->space[2] = 0.0; + pnew->space[3] = 0.0; + pnew->empty = 0; +} + + +double total_height(struct frame *fr) +{ + int i; + double t = 0.0; + for ( i=0; i<fr->n_paras; i++ ) { + t += paragraph_height(fr->paras[i]); + } + return t; +} + + +Paragraph *last_para(struct frame *fr) +{ + if ( fr == NULL ) return NULL; + if ( fr->paras == NULL ) return NULL; + return fr->paras[fr->n_paras-1]; +} + + +static void render_from_surf(cairo_surface_t *surf, cairo_t *cr, + double w, double h, int border) +{ + double x, y; + int sw, sh; + + x = 0.0; y = 0.0; + cairo_user_to_device(cr, &x, &y); + x = rint(x); y = rint(y); + cairo_device_to_user(cr, &x, &y); + + sw = cairo_image_surface_get_width(surf); + sh = cairo_image_surface_get_height(surf); + + cairo_save(cr); + cairo_scale(cr, w/sw, h/sh); + cairo_new_path(cr); + cairo_rectangle(cr, x, y, sw, sh); + cairo_set_source_surface(cr, surf, 0.0, 0.0); + cairo_pattern_t *patt = cairo_get_source(cr); + cairo_pattern_set_extend(patt, CAIRO_EXTEND_PAD); + cairo_pattern_set_filter(patt, CAIRO_FILTER_BEST); + cairo_fill(cr); + cairo_restore(cr); + + if ( border ) { + cairo_new_path(cr); + cairo_rectangle(cr, x+0.5, y+0.5, w, h); + cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); + cairo_set_line_width(cr, 1.0); + cairo_stroke(cr); + } +} + + +void render_paragraph(cairo_t *cr, Paragraph *para, ImageStore *is) +{ + cairo_surface_t *surf; + cairo_surface_type_t type; + double w, h; + + cairo_translate(cr, para->space[0], para->space[2]); + + type = cairo_surface_get_type(cairo_get_target(cr)); + + switch ( para->type ) { + + case PARA_TYPE_TEXT : + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + pango_cairo_update_layout(cr, para->layout); + pango_cairo_show_layout(cr, para->layout); + cairo_fill(cr); + break; + + case PARA_TYPE_IMAGE : + w = para->image_w; + h = para->image_h; + cairo_user_to_device_distance(cr, &w, &h); + surf = lookup_image(is, para->filename, w); + if ( surf != NULL ) { + render_from_surf(surf, cr, para->image_w, para->image_h, 0); + } else { + printf("surf = NULL!\n"); + } + break; + + case PARA_TYPE_CALLBACK : + w = para->cb_w; + h = para->cb_h; + cairo_user_to_device_distance(cr, &w, &h); + if ( type == CAIRO_SURFACE_TYPE_PDF ) { + w *= 6; h *= 6; + } + surf = para->draw_func(w, h, + para->bvp, para->vp); + render_from_surf(surf, cr, para->cb_w, para->cb_h, 1); + cairo_surface_destroy(surf); /* FIXME: Cache like crazy */ + break; + + } +} + + +static size_t end_offset_of_para(struct frame *fr, int pn) +{ + int i; + size_t total = 0; + for ( i=0; i<fr->paras[pn]->n_runs; i++ ) { + total += run_text_len(&fr->paras[pn]->runs[i]); + } + return total; +} + + +/* Local x,y in paragraph -> text offset */ +static size_t text_para_pos(Paragraph *para, double x, double y, int *ptrail) +{ + int idx; + pango_layout_xy_to_index(para->layout, pango_units_from_double(x), + pango_units_from_double(y), &idx, ptrail); + return idx; +} + + +void show_edit_pos(struct edit_pos a) +{ + printf("para %i, pos %li, trail %i\n", a.para, (long int)a.pos, a.trail); +} + + +int positions_equal(struct edit_pos a, struct edit_pos b) +{ + if ( a.para != b.para ) return 0; + if ( a.pos != b.pos ) return 0; + if ( a.trail != b.trail ) return 0; + return 1; +} + + +void sort_positions(struct edit_pos *a, struct edit_pos *b) +{ + if ( a->para > b->para ) { + size_t tpos; + int tpara, ttrail; + tpara = b->para; tpos = b->pos; ttrail = b->trail; + b->para = a->para; b->pos = a->pos; b->trail = a->trail; + a->para = tpara; a->pos = tpos; a->trail = ttrail; + } + + if ( (a->para == b->para) && (a->pos > b->pos) ) + { + size_t tpos = b->pos; + int ttrail = b->trail; + b->pos = a->pos; b->trail = a->trail; + a->pos = tpos; a->trail = ttrail; + } +} + + +static PangoFontDescription *last_font_desc_and_col(struct frame *fr, int pn, double *col) +{ + int i; + + for ( i=pn-1; i>=0; i-- ) { + if ( fr->paras[i]->type != PARA_TYPE_TEXT ) continue; + if ( fr->paras[i]->n_runs == 0 ) continue; + col[0] = fr->paras[i]->runs[fr->paras[i]->n_runs-1].col[0]; + col[1] = fr->paras[i]->runs[fr->paras[i]->n_runs-1].col[1]; + col[2] = fr->paras[i]->runs[fr->paras[i]->n_runs-1].col[2]; + col[3] = fr->paras[i]->runs[fr->paras[i]->n_runs-1].col[3]; + return fr->paras[i]->runs[fr->paras[i]->n_runs-1].fontdesc; + } + + /* No previous text at all, so use the default text style + * (which is valid for new text in the frame, so this is OK) */ + col[0] = fr->col[0]; + col[1] = fr->col[1]; + col[2] = fr->col[2]; + col[3] = fr->col[3]; + return fr->fontdesc; +} + + +void ensure_run(struct frame *fr, struct edit_pos cpos) +{ + SCBlock *bl; + Paragraph *para; + PangoFontDescription *fontdesc; + double col[4]; + + para = fr->paras[cpos.para]; + + if ( para->n_runs > 0 ) return; + + if ( para->type != PARA_TYPE_TEXT ) return; + + if ( para->scblock != NULL ) { + + bl = sc_block_prepend(para->scblock, fr->scblocks); + if ( bl == NULL ) { + fprintf(stderr, "Couldn't prepend block\n"); + return; + } + sc_block_set_contents(bl, strdup("")); + + } else { + + /* If the paragraph's SCBlock is NULL, it means this paragraph + * is right at the end of the document. The last thing in the + * document is something like \newpara. */ + bl = sc_block_append_inside(fr->scblocks, NULL, NULL, strdup("")); + + } + + para->scblock = bl; + + fontdesc = last_font_desc_and_col(fr, cpos.para, col); + add_run(para, bl, fontdesc, col, NULL); + wrap_paragraph(para, NULL, fr->w - fr->pad_l - fr->pad_r, 0, 0); +} + + +int find_cursor(struct frame *fr, double x, double y, struct edit_pos *pos) +{ + double pad; + int i; + + if ( fr == NULL ) { + fprintf(stderr, "Cursor frame is NULL.\n"); + return 1; + } + + pad = fr->pad_t; + + for ( i=0; i<fr->n_paras; i++ ) { + double npos = pad + paragraph_height(fr->paras[i]); + if ( npos > y ) { + pos->para = i; + if ( fr->paras[i]->type == PARA_TYPE_TEXT ) { + pos->pos = text_para_pos(fr->paras[i], + x - fr->pad_l - fr->paras[i]->space[0], + y - pad - fr->paras[i]->space[2], + &pos->trail); + } else { + pos->pos = 0; + } + return 0; + } + pad = npos; + } + + if ( fr->n_paras == 0 ) { + printf("No paragraphs in frame.\n"); + return 1; + } + + /* Pretend it's in the last paragraph */ + pad -= fr->paras[fr->n_paras-1]->height; + pos->para = fr->n_paras - 1; + pos->pos = text_para_pos(fr->paras[fr->n_paras-1], + x - fr->pad_l, y - pad, &pos->trail); + return 0; +} + + +int get_para_highlight(struct frame *fr, int cursor_para, + double *cx, double *cy, double *cw, double *ch) +{ + Paragraph *para; + int i; + double py = 0.0; + + if ( fr == NULL ) { + fprintf(stderr, "Cursor frame is NULL.\n"); + return 1; + } + + if ( cursor_para >= fr->n_paras ) { + fprintf(stderr, "Highlight paragraph number is too high!\n"); + return 1; + } + + para = fr->paras[cursor_para]; + for ( i=0; i<cursor_para; i++ ) { + py += paragraph_height(fr->paras[i]); + } + + *cx = fr->pad_l; + *cy = fr->pad_t + py; + *cw = fr->w - fr->pad_l - fr->pad_r; + *ch = paragraph_height(para); + return 0; +} + + +int get_cursor_pos(struct frame *fr, int cursor_para, int cursor_pos, + double *cx, double *cy, double *ch) +{ + Paragraph *para; + PangoRectangle rect; + int i; + double py = 0.0; + + if ( fr == NULL ) { + fprintf(stderr, "Cursor frame is NULL.\n"); + return 1; + } + + if ( cursor_para >= fr->n_paras ) { + fprintf(stderr, "Cursor paragraph number is too high!\n"); + return 1; + } + + para = fr->paras[cursor_para]; + for ( i=0; i<cursor_para; i++ ) { + py += paragraph_height(fr->paras[i]); + } + + if ( para->type != PARA_TYPE_TEXT ) { + return 1; + } + + pango_layout_get_cursor_pos(para->layout, cursor_pos, &rect, NULL); + + *cx = pango_units_to_double(rect.x) + fr->pad_l + para->space[0]; + *cy = pango_units_to_double(rect.y) + fr->pad_t + py + para->space[2]; + *ch = pango_units_to_double(rect.height); + return 0; +} + + +//void cursor_moveh(struct frame *fr, int *cpara, size_t *cpos, int *ctrail, +void cursor_moveh(struct frame *fr, struct edit_pos *cp, signed int dir) +{ + Paragraph *para = fr->paras[cp->para]; + int np = cp->pos; + + pango_layout_move_cursor_visually(para->layout, 1, cp->pos, cp->trail, + dir, &np, &cp->trail); + if ( np == -1 ) { + if ( cp->para > 0 ) { + size_t end_offs; + cp->para--; + end_offs = end_offset_of_para(fr, cp->para); + if ( end_offs > 0 ) { + cp->pos = end_offs - 1; + cp->trail = 1; + } else { + /* Jumping into an empty paragraph */ + cp->pos = 0; + cp->trail = 0; + } + return; + } else { + /* Can't move any further */ + return; + } + } + + if ( np == G_MAXINT ) { + if ( cp->para < fr->n_paras-1 ) { + cp->para++; + cp->pos = 0; + cp->trail = 0; + return; + } else { + /* Can't move any further */ + return; + } + } + + cp->pos = np; +} + + +void check_callback_click(struct frame *fr, int para) +{ + Paragraph *p = fr->paras[para]; + if ( p->type == PARA_TYPE_CALLBACK ) { + p->click_func(0.0, 0.0, p->bvp, p->vp); + } +} + + +static int get_paragraph_offset(Paragraph *para, int nrun) +{ + int i; + size_t t = 0; + + for ( i=0; i<nrun; i++ ) { + struct text_run *run = ¶->runs[i]; + t += run_text_len(run); + } + return t; +} + + +static int which_run(Paragraph *para, size_t offs) +{ + int i; + size_t t = 0; + + for ( i=0; i<para->n_runs; i++ ) { + struct text_run *run = ¶->runs[i]; + t += run_text_len(run); + if ( t > offs ) return i; + } + + /* Maybe offs points exactly to the end of the last run? */ + if ( t == offs ) return para->n_runs-1; + + return para->n_runs; +} + + +size_t pos_trail_to_offset(Paragraph *para, size_t offs, int trail) +{ + glong char_offs; + size_t run_offs; + const char *run_text; + struct text_run *run; + int nrun; + char *ptr; + size_t para_offset_of_run; + + nrun = which_run(para, offs); + + if ( nrun == para->n_runs ) { + fprintf(stderr, "pos_trail_to_offset: Offset too high\n"); + return 0; + } + + if ( para->n_runs == 0 ) { + return 0; + } + + run = ¶->runs[nrun]; + + if ( para->type != PARA_TYPE_TEXT ) return 0; + + if ( run == NULL ) { + fprintf(stderr, "pos_trail_to_offset: No run\n"); + return 0; + } + + if ( run->scblock == NULL ) { + fprintf(stderr, "pos_trail_to_offset: SCBlock = NULL?\n"); + return 0; + } + + /* Get the text for the run */ + run_text = text_for_run(run); + if ( run_text == NULL ) { + fprintf(stderr, "pos_trail_to_offset: No contents " + "(%p name=%s, options=%s)\n", + run->scblock, sc_block_name(run->scblock), + sc_block_options(run->scblock)); + return 0; + } + + + /* Turn the paragraph offset into a run offset */ + para_offset_of_run = get_paragraph_offset(para, nrun); + run_offs = offs - para_offset_of_run; + + char_offs = g_utf8_pointer_to_offset(run_text, run_text+run_offs); + char_offs += trail; + + if ( char_offs > g_utf8_strlen(run_text, -1) ) { + printf("Offset outside string! '%s'\n" + "char_offs %li offs %li len %li\n", + run_text, (long int)char_offs, (long int)offs, + (long int)g_utf8_strlen(run_text, -1)); + } + + ptr = g_utf8_offset_to_pointer(run_text, char_offs); + return ptr - run_text + para_offset_of_run; +} + + +int position_editable(struct frame *fr, struct edit_pos cp) +{ + Paragraph *para; + int run; + size_t paraoffs; + + if ( fr == NULL ) { + fprintf(stderr, "Frame is NULL.\n"); + return 0; + } + + if ( cp.para >= fr->n_paras ) { + fprintf(stderr, "Paragraph number is too high!\n"); + return 0; + } + + para = fr->paras[cp.para]; + + if ( para->type != PARA_TYPE_TEXT ) { + fprintf(stderr, "Paragraph is not text.\n"); + return 0; + } + + paraoffs = pos_trail_to_offset(para, cp.pos, cp.trail); + run = which_run(para, paraoffs); + if ( run == para->n_runs ) { + fprintf(stderr, "Couldn't find run!\n"); + return 0; + } + + if ( para->runs[run].real_text != NULL ) return 0; + + return 1; +} + + +void insert_text_in_paragraph(Paragraph *para, size_t offs, const char *t) +{ + int nrun; + + /* Find which run we are in */ + nrun = which_run(para, offs); + if ( nrun == para->n_runs ) { + fprintf(stderr, "Couldn't find run to insert into.\n"); + return; + } + + if ( para->n_runs == 0 ) { + printf("No runs in paragraph?\n"); + } else { + struct text_run *run; + size_t run_offs; + run = ¶->runs[nrun]; + run_offs = offs - get_paragraph_offset(para, nrun); + sc_insert_text(run->scblock, run_offs, t); + } +} + + +static SCBlock *pos_to_scblock(struct frame *fr, struct edit_pos p, + enum para_type *type) +{ + int run; + size_t paraoffs; + Paragraph *para; + + para = fr->paras[p.para]; + if ( type != NULL ) { + *type = para->type; + } + + if ( para->type != PARA_TYPE_TEXT ) { + return para->scblock; + } + + paraoffs = pos_trail_to_offset(para, p.pos, p.trail); + + run = which_run(para, paraoffs); + assert(run < para->n_runs); + + return para->runs[run].scblock; +} + + +static size_t pos_to_offset(struct frame *fr, struct edit_pos p) +{ + int run; + size_t paraoffs; + Paragraph *para; + + para = fr->paras[p.para]; + if ( para->type != PARA_TYPE_TEXT ) { + return 0; + } + + /* Offset of this position into the paragraph */ + paraoffs = pos_trail_to_offset(para, p.pos, p.trail); + + run = which_run(para, paraoffs); + assert(run < para->n_runs); + + /* Offset of this position into the run + * (and therefore into the SCBlock) */ + return paraoffs - get_paragraph_offset(para, run); +} + + +static int pos_to_run_number(struct frame *fr, struct edit_pos p) +{ + int run; + size_t paraoffs; + Paragraph *para; + + para = fr->paras[p.para]; + if ( para->type != PARA_TYPE_TEXT ) { + return 0; + } + + paraoffs = pos_trail_to_offset(para, p.pos, p.trail); + + run = which_run(para, paraoffs); + assert(run < para->n_runs); + + return run; +} + + +static void delete_run(Paragraph *para, int nrun) +{ + printf("deleting run %i of %i from para %p\n", nrun, para->n_runs, para); + memmove(¶->runs[nrun], ¶->runs[nrun+1], + (para->n_runs-nrun-1)*sizeof(struct text_run)); + para->n_runs--; +} + + +static Paragraph *scan_runs_for_scblock(struct frame *fr, int pn1, int pn2, + SCBlock *bl, int *run) +{ + int i; + + for ( i=pn1; i<=pn2; i++ ) { + + int j; + + /* Non-text paragraph - just one thing to check */ + if ( fr->paras[i]->scblock == bl ) { + *run = 0; + return fr->paras[i]; + } + + /* Check all runs */ + for ( j=0; j<fr->paras[i]->n_runs; j++ ) { + if ( fr->paras[i]->runs[j].scblock == bl ) { + *run = j; + return fr->paras[i]; + } + } + } + return NULL; +} + + +static Paragraph *find_run_for_scblock_next(struct frame *fr, int pn1, int pn2, + SCBlock *bl, int *run) +{ + if ( sc_block_child(bl) != NULL ) { + Paragraph *para; + para = find_run_for_scblock_next(fr, pn1, pn2, + sc_block_child(bl), run); + if ( para != NULL ) return para; + } + + do { + Paragraph *para; + para = scan_runs_for_scblock(fr, pn1, pn2, bl, run); + if ( para != NULL ) return para; + bl = sc_block_next(bl); + } while ( bl != NULL ); + + return NULL; +} + + +/* Find the run which contains the text from "bl", + * taking into account that it might be a child block, for example: + * {some text} + * \italic <---- bl points here + * {more text} <---- but this block is referenced by the run + * {final text} + */ +static Paragraph *find_run_for_scblock(struct frame *fr, int pn1, int pn2, + SCBlock *bl, int *run) +{ + Paragraph *para; + + show_sc_block(bl, "searching "); + para = scan_runs_for_scblock(fr, pn1, pn2, bl, run); + if ( para != NULL ) return para; + + if ( sc_block_child(bl) != NULL ) { + para = find_run_for_scblock_next(fr, pn1, pn2, sc_block_child(bl), run); + if ( para != NULL ) return para; + } + + return NULL; +} + + +static int paragraph_number(struct frame *fr, Paragraph *p, int *err) +{ + int i; + for ( i=0; i<fr->n_paras; i++ ) { + if ( fr->paras[i] == p ) return i; + } + fprintf(stderr, "Couldn't find paragraph %p\n", p); + *err = 1; + return 0; +} + + +static int find_para(struct frame *fr, Paragraph *para) +{ + int i; + + for ( i=0; i<fr->n_paras; i++ ) { + if ( fr->paras[i] == para ) return i; + } + + return fr->n_paras; +} + + +static void delete_paragraph(struct frame *fr, Paragraph *para, int *pnp) +{ + int pn = find_para(fr, para); + if ( pn == fr->n_paras ) { + fprintf(stderr, "Couldn't find paragraph to delete (%p)\n", para); + return; + } + + printf("deleting paragraph %i (%p)\n", pn, para); + + memmove(&fr->paras[pn], &fr->paras[pn+1], + (fr->n_paras-pn-1)*sizeof(Paragraph *)); + fr->n_paras--; + + if ( (pnp != NULL) && (*pnp > pn) ) { + (*pnp)--; + } +} + + +static void delete_run_for_scblock(struct frame *fr, + Paragraph *p1, Paragraph *p2, SCBlock *bl, + int *pnp) +{ + int pn1, pn2; + int err = 0; + Paragraph *para; + int run; + + pn1 = paragraph_number(fr, p1, &err); + pn2 = paragraph_number(fr, p2, &err); + if ( err ) return; + + para = find_run_for_scblock(fr, pn1, pn2, bl, &run); + if ( para == NULL ) { + fprintf(stderr, "Couldn't find block %p between paragraphs %p and %p\n", + bl, p1, p2); + return; + } + + if ( (run==0) && (para->scblock == bl ) ) { + delete_paragraph(fr, para, pnp); + } else { + delete_run(para, run); + } +} + + +static signed int merge_paragraph_runs(Paragraph *p1, Paragraph *p2) +{ + struct text_run *runs_new; + int i, spos; + + /* All the runs from p2 get added to p1 */ + runs_new = realloc(p1->runs, + (p1->n_runs+p2->n_runs)*sizeof(struct text_run)); + if ( runs_new == NULL ) { + fprintf(stderr, "Failed to allocate merged runs.\n"); + return -1; + } + p1->runs = runs_new; + + spos = p1->n_runs; + + /* The end of the united paragraph should now be the end of the + * second one */ + set_newline_at_end(p1, get_newline_at_end(p2)); + + for ( i=0; i<p2->n_runs; i++ ) { + p1->runs[p1->n_runs] = p2->runs[i]; + p1->n_runs++; + } + free(p2->runs); + free(p2); + + return spos; +} + + +static void merge_paragraphs_by_newpara(struct frame *fr, SCBlock *np) +{ + int i; + Paragraph *p1; + Paragraph *p2; + + for ( i=0; i<fr->n_paras-1; i++ ) { + if ( fr->paras[i]->newline_at_end == np ) { + + int j; + signed int spos; + + p1 = fr->paras[i]; + p2 = fr->paras[i+1]; + + printf("-------------------------------\n"); + show_para(p1); + printf("---x--------x------------------\n"); + show_para(p2); + spos = merge_paragraph_runs(p1, p2); + if ( spos < 0 ) { + fprintf(stderr, "Failed to merge paragraphs\n"); + return; + } + printf("-------------------------------\n"); + show_para(p1); + + for ( j=i+1; j<fr->n_paras-1; j++ ) { + fr->paras[j] = fr->paras[j+1]; + } + fr->n_paras--; + + return; + + } + } + + fprintf(stderr, "Couldn't find paragraphs to merge by newpara\n"); +} + + +static int find_block_inside(SCBlock *needle, SCBlock *bl) +{ + if ( needle == bl ) return 1; + + if ( sc_block_child(bl) != NULL ) { + if ( find_block_inside(needle, sc_block_child(bl)) ) return 1; + } + + if ( sc_block_next(bl) != NULL ) { + if ( find_block_inside(needle, sc_block_next(bl)) ) return 1; + } + + return 0; +} + + +/* Return true if "top" either IS "child", or contains "child" somewhere + * underneath, even if via a macro expansion */ +static int block_is_under(SCBlock *needle, SCBlock *top) +{ + if ( needle == top ) return 1; + + if ( sc_block_child(top) != NULL ) { + if ( find_block_inside(needle, sc_block_child(top)) ) return 1; + } + + /* Do not look at top->next here */ + + return 0; +} + + +void delete_text_from_frame(struct frame *fr, struct edit_pos p1, struct edit_pos p2, + double wrapw) +{ + int i; + SCBlock *p1scblock, *p2scblock; + enum para_type type1, type2; + size_t p2offs; + SCBlock *scblock; + int wrap_end; + + sort_positions(&p1, &p2); + + /* To make sure there are no nasty surprises ahead, run through the + * paragraphs we're about to touch, and make sure they all have at least + * an empty dummy run */ + for ( i=p1.para; i<=p2.para; i++ ) { + struct edit_pos ep; + ep.para = i; + ep.pos = 0; + ep.trail = 0; + ensure_run(fr, ep); + } + + if ( !position_editable(fr, p1) || !position_editable(fr, p2) ) { + fprintf(stderr, "Delete outside editable region\n"); + return; + } + + /* Find SC positions for start and end */ + p1scblock = pos_to_scblock(fr, p1, &type1); + p2scblock = pos_to_scblock(fr, p2, &type2); + p2offs = pos_to_offset(fr, p2); + wrap_end = p2.para; + + printf("SCBlocks %p to %p\n", p1scblock, p2scblock); + if ( p1scblock == NULL ) { + fprintf(stderr, "Starting block NULL. Not deleting.\n"); + return; + } + if ( p2scblock == NULL ) { + fprintf(stderr, "Ending block NULL. Not deleting.\n"); + return; + } + //show_sc_blocks(p1scblock); + + if ( (p1scblock == p2scblock) && (type1 == PARA_TYPE_TEXT) ) { + + size_t p1offs; + printf("Simple case, one SCBlock\n"); + + assert(type1 == type2); + + /* Remove the text and update the run length */ + p1offs = pos_to_offset(fr, p1); + scblock_delete_text(p1scblock, p1offs, p2offs); + + wrap_paragraph(fr->paras[p1.para], NULL, wrapw, 0, 0); + + return; + } + + /* Starting point for iteration over blocks in middle of range. + * Record this now, because p1scblock might be about to get deleted */ + scblock = sc_block_next(p1scblock); + + /* First SCBlock in range: delete whole thing or second half */ + printf("First block %p\n", p1scblock); + if ( type1 == PARA_TYPE_TEXT ) { + + size_t p1offs = pos_to_offset(fr, p1); + int p1run = pos_to_run_number(fr, p1); + printf(" offs %li\n", (long int)p1offs); + if ( p1offs != 0 ) { + printf("Partial delete\n"); + printf("contents '%s'\n", sc_block_contents(p1scblock)); + printf("from offs %li\n", (long int)p1offs); + scblock_delete_text(p1scblock, p1offs, -1); + } else { + printf("Deleting the whole text SCBlock\n"); + sc_block_delete(&fr->scblocks, p1scblock); + delete_run(fr->paras[p1.para], p1run); + } + + } else { + printf("Deleting the whole non-text SCBlock\n"); + sc_block_delete(&fr->scblocks, p1scblock); + } + + /* Delete all the complete SCBlocks in the middle of the range */ + if ( !block_is_under(p2scblock, scblock) ) { + do { + + SCBlock *next; + + /* For each SC block in middle of range: */ + printf("Deleting %p\n", scblock); + if ( scblock == NULL ) { + fprintf(stderr, "nothing?\n"); + break; + } + printf("name is '%s'\n", sc_block_name(scblock)); + + if ( (sc_block_name(scblock) != NULL) + && (strcmp(sc_block_name(scblock), "newpara") == 0) ) + { + /* Deleting newpara block, merge the paragraphs */ + merge_paragraphs_by_newpara(fr, scblock); + p2.para--; + } + + next = sc_block_next(scblock); + delete_run_for_scblock(fr, fr->paras[p1.para], + fr->paras[p2.para], scblock, + &p2.para); + sc_block_delete(&fr->scblocks, scblock); + + scblock = next; + + } while ( !block_is_under(p2scblock, scblock) ); + } + + /* Last SCBlock in range: delete whole thing or first half */ + printf("Last block %p (%s)\n", p2scblock, sc_block_name(p2scblock)); + if ( type2 == PARA_TYPE_TEXT ) { + size_t len; + printf(" offs %li\n", (long int)p2offs); + if ( sc_block_contents(p2scblock) != NULL ) { + len = strlen(sc_block_contents(p2scblock)); + } else { + len = 0; + } + printf(" len %li\n", (long int)len); + if ( (len > 0) && (p2offs == len) ) { + printf("Deleting the whole text SCBlock\n"); + printf("deleting block %p\n", p2scblock); + show_sc_block(p2scblock, ""); + sc_block_delete(&fr->scblocks, p2scblock); + delete_run_for_scblock(fr, fr->paras[p1.para], + fr->paras[p2.para], p2scblock, + NULL); + } else if ( p2offs > 0 ) { + printf("Partial delete\n"); + printf("contents '%s'\n", sc_block_contents(p2scblock)); + printf("up to offs %li\n", (long int)p2offs); + scblock_delete_text(p2scblock, 0, p2offs); + } /* else do nothing */ + } else { + printf("Deleting the whole non-text SCBlock\n"); + sc_block_delete(&fr->scblocks, p2scblock); + } + + /* If any paragraphs have been deleted, this will wrap too many + * paragraphs, but it doesn't matter as long as we don't wrap + * past the end of the frame's contents. */ + for ( i=p1.para; i<=wrap_end; i++ ) { + if ( i >= fr->n_paras ) break; + printf("Wrapping para %i (%p)\n", i, fr->paras[i]); + wrap_paragraph(fr->paras[i], NULL, wrapw, 0, 0); + } + printf("All done.\n"); +} + + +void show_para(Paragraph *p) +{ + printf("Paragraph %p\n", p); + + if ( p->type == PARA_TYPE_TEXT ) { + + int i; + + printf("%i runs:\n", p->n_runs); + for ( i=0; i<p->n_runs; i++ ) { + printf(" Run %2i: SCBlock %p %s '%s'\n", + i, p->runs[i].scblock, + pango_font_description_to_string(p->runs[i].fontdesc), + sc_block_contents(p->runs[i].scblock)); + } + + } else if ( p->type == PARA_TYPE_IMAGE ) { + printf(" Image: %s\n", p->filename); + } else { + printf(" Other paragraph type\n"); + } +} + + +static SCBlock *split_text_paragraph(struct frame *fr, int pn, size_t pos, + PangoContext *pc) +{ + Paragraph *pnew; + int i; + SCBlock *nnp; + size_t run_offs; + int run; + Paragraph *para = fr->paras[pn]; + struct text_run *rr; + + pnew = insert_paragraph(fr, pn); + if ( pnew == NULL ) { + fprintf(stderr, "Failed to insert paragraph\n"); + return NULL; + } + + /* Determine which run the cursor is in */ + run = which_run(para, pos); + + /* Create the new (second) paragraph */ + pnew->type = PARA_TYPE_TEXT; + pnew->n_runs = 0; + pnew->runs = NULL; + for ( i=0; i<4; i++ ) pnew->space[i] = para->space[i]; + + rr = ¶->runs[run]; + run_offs = pos - get_paragraph_offset(para, run); + printf("split at run %i\n", run); + + /* Easy case: splitting at a run boundary */ + if ( run_offs == run_text_len(rr) ) { + + /* Even easier case: splitting at the end of the paragraph */ + if ( run == para->n_runs-1 ) { + + SCBlock *np; + SCBlock *end; + + printf("Simple new para\n"); + + if ( get_newline_at_end(para) == NULL ) { + + /* The current paragraph doesn't have + * a \newpara yet */ + + np = sc_block_append(rr->scblock, + strdup("newpara"), NULL, + NULL, NULL); + set_newline_at_end(para, np); + + } else { + + SCBlock *op; + + /* If the current paragraph did have \newpara, + * then the new one needs one too */ + np = sc_block_append(rr->scblock, + strdup("newpara"), + NULL, NULL, NULL); + op = get_newline_at_end(para); + set_newline_at_end(para, np); + set_newline_at_end(pnew, op); + + + } + + /* Add an empty run + SCBlock to type into */ + end = sc_block_append(np, NULL, NULL, strdup(""), NULL); + + pnew->n_runs = 0; + add_run(pnew, end, rr->fontdesc, rr->col, NULL); + pnew->scblock = end; + + wrap_paragraph(pnew, pc, fr->w - fr->pad_l - fr->pad_r, 0, 0); + + return end; + + } + + } else { + + /* Split the run (and SCBlock) into two */ + double col[4] = {0,0,0,0}; + struct text_run *rn; + + printf("Splitting run %i. Before:\n", run); + show_para(para); + + add_run(para, NULL, NULL, col, NULL); + /* -2 here because add_run increased para->n_runs by 1 */ + memmove(¶->runs[run+2], ¶->runs[run+1], + (para->n_runs - run - 2)*sizeof(struct text_run)); + + rr = ¶->runs[run]; /* Because add_run realloced the runs */ + rn = ¶->runs[run+1]; + + rn->scblock = sc_block_split(rr->scblock, run_offs); + + rn->fontdesc = pango_font_description_copy(rr->fontdesc); + rn->col[0] = rr->col[0]; + rn->col[1] = rr->col[1]; + rn->col[2] = rr->col[2]; + rn->col[3] = rr->col[3]; + + printf("After:\n"); + show_para(para); + + } + + /* All later runs just get moved to the new paragraph */ + for ( i=run+1; i<para->n_runs; i++ ) { + double col[4] = {0,0,0,0}; + printf("Moving run %i to pos %i\n", i, pnew->n_runs); + add_run(pnew, NULL, NULL, col, NULL); + pnew->runs[pnew->n_runs-1] = para->runs[i]; + } + pnew->scblock = pnew->runs[0].scblock; + + /* Truncate the first paragraph at the appropriate position */ + para->n_runs = run+1; + + printf("Final paragraphs:\n"); + printf("First:\n"); + show_para(para); + printf("Second:\n"); + show_para(pnew); + + /* Add a \newpara after the end of the first paragraph's SC */ + nnp = sc_block_append(rr->scblock, strdup("newpara"), NULL, NULL, NULL); + set_newline_at_end(pnew, get_newline_at_end(para)); + set_newline_at_end(para, nnp); + + wrap_paragraph(para, pc, fr->w - fr->pad_l - fr->pad_r, 0, 0); + wrap_paragraph(pnew, pc, fr->w - fr->pad_l - fr->pad_r, 0, 0); + + return sc_block_next(nnp); +} + + +SCBlock *split_paragraph(struct frame *fr, int pn, size_t pos, PangoContext *pc) +{ + Paragraph *para = fr->paras[pn]; + + if ( para->type == PARA_TYPE_TEXT ) { + return split_text_paragraph(fr, pn, pos, pc); + } else { + /* Other types can't be split */ + return NULL; + } +} + + +SCBlock *block_at_cursor(struct frame *fr, int pn, size_t pos) +{ + Paragraph *para = fr->paras[pn]; + + if ( para->type != PARA_TYPE_CALLBACK ) return NULL; + + return para->scblock; +} + + +int get_sc_pos(struct frame *fr, int pn, size_t pos, + SCBlock **bl, size_t *ppos) +{ + Paragraph *para = fr->paras[pn]; + int nrun; + struct text_run *run; + + nrun = which_run(para, pos); + if ( nrun == para->n_runs ) { + fprintf(stderr, "Couldn't find run to insert into.\n"); + return 1; + } + run = ¶->runs[nrun]; + + *ppos = pos - get_paragraph_offset(para, nrun); + *bl = run->scblock; + + return 0; +} + + +void set_para_spacing(Paragraph *para, float space[4]) +{ + if ( para == NULL ) return; + para->space[0] = space[0]; + para->space[1] = space[1]; + para->space[2] = space[2]; + para->space[3] = space[3]; +} + + +void set_para_alignment(Paragraph *para, PangoAlignment align) +{ + if ( para == NULL ) return; + para->alignment = align; +} + + +void *get_para_bvp(Paragraph *para) +{ + if ( para->type != PARA_TYPE_CALLBACK ) return NULL; + return para->bvp; +} + + +SCBlock *para_scblock(Paragraph *para) +{ + return para->scblock; +} + + +enum para_type para_type(Paragraph *para) +{ + return para->type; +} + + +int para_debug_num_runs(Paragraph *para) +{ + if ( para->type != PARA_TYPE_TEXT ) return 0; + return para->n_runs; +} + + +int para_debug_run_info(Paragraph *para, int i, SCBlock **scblock) +{ + if ( para->type != PARA_TYPE_TEXT ) return 1; + if ( i >= para->n_runs ) return 1; + + *scblock = para->runs[i].scblock; + return 0; +} |