diff options
Diffstat (limited to 'src/frame.c')
-rw-r--r-- | src/frame.c | 1820 |
1 files changed, 0 insertions, 1820 deletions
diff --git a/src/frame.c b/src/frame.c deleted file mode 100644 index 08b8fd9..0000000 --- a/src/frame.c +++ /dev/null @@ -1,1820 +0,0 @@ -/* - * 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; -} |