/* * frame.c * * Copyright © 2013-2018 Thomas White * * 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 . * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "sc_parse.h" #include "frame.h" #include "imagestore.h" #include "utils.h" struct text_run { SCBlock *scblock; /* If macro, this is \macro */ 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; }; PangoLayout *paragraph_layout(Paragraph *para) { return para->layout; } /* 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; in_runs; i++ ) { pango_font_description_free(para->runs[i].fontdesc); } 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; in_paras; i++ ) { free_paragraph(fr->paras[i]); } free(fr->paras); } /* Free all children */ for ( i=0; inum_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; inum_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; inum_children; i++ ) { struct frame *tt; tt = find_frame_with_scblocks(fr->children[i], scblocks); if ( tt != NULL ) return tt; } return NULL; } 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 ( 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; in_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; in_runs; i++ ) { PangoAttribute *attr; const char *run_text; size_t run_len; guint16 r, g, b; run_text = sc_block_contents(para->runs[i].scblock); 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]) { 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; 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 get size for %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; in_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; iparas[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; } } void ensure_run(struct frame *fr, struct edit_pos cpos) { SCBlock *bl; Paragraph *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; add_run(para, bl, fr->fontdesc, fr->col); 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 = fr->pad_t; int i; if ( fr == NULL ) { fprintf(stderr, _("Cursor frame is NULL.\n")); return 1; } for ( i=0; in_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; iparas[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; iparas[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; iruns[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; in_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; } if ( sc_block_contents(run->scblock) == 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; } /* Get the text for the run */ run_text = sc_block_contents(run->scblock); /* 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; } 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; jparas[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; in_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; in_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; in_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; in_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; jn_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) { int i; printf(_("Paragraph %p\n"), p); if ( p->type == PARA_TYPE_TEXT ) { printf(_("%i runs:\n"), p->n_runs); for ( i=0; in_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, fr->fontdesc, fr->col); 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); /* -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; in_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); 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; }