/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* This file is part of the GtkHTML library. * * Copyright (C) 1997 Martin Jones (mjones@kde.org) * Copyright (C) 1997 Torben Weis (weis@kde.org) * Copyright (C) 1999 Helix Code, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include "htmltextslave.h" #include "htmlclue.h" #include "htmlclueflow.h" #include "htmlcursor.h" #include "htmlcolor.h" #include "htmlcolorset.h" #include "htmlpainter.h" #include "htmlobject.h" #include "htmlprinter.h" #include "htmlplainpainter.h" #include "htmlgdkpainter.h" #include "htmlsettings.h" #include "gtkhtml.h" /* #define HTML_TEXT_SLAVE_DEBUG */ HTMLTextSlaveClass html_text_slave_class; static HTMLObjectClass *parent_class = NULL; static void clear_glyph_items (HTMLTextSlave *slave); gchar * html_text_slave_get_text (HTMLTextSlave *slave) { if (!slave->charStart) slave->charStart = html_text_get_text (slave->owner, slave->posStart); return slave->charStart; } /* Split this TextSlave at the specified offset. */ static void split (HTMLTextSlave *slave, guint offset, gint skip, gchar *start_pointer) { HTMLObject *obj; HTMLObject *new; g_return_if_fail (offset < slave->posLen); obj = HTML_OBJECT (slave); new = html_text_slave_new (slave->owner, slave->posStart + offset + skip, slave->posLen - (offset + skip)); HTML_TEXT_SLAVE (new)->charStart = start_pointer; html_clue_append_after (HTML_CLUE (obj->parent), new, obj); slave->posLen = offset; } /* HTMLObject methods. */ static void copy (HTMLObject *self, HTMLObject *dest) { (* HTML_OBJECT_CLASS (parent_class)->copy) (self, dest); /* FIXME it does not make much sense to me to share the owner. */ HTML_TEXT_SLAVE (dest)->owner = HTML_TEXT_SLAVE (self)->owner; HTML_TEXT_SLAVE (dest)->posStart = HTML_TEXT_SLAVE (self)->posStart; HTML_TEXT_SLAVE (dest)->posLen = HTML_TEXT_SLAVE (self)->posLen; HTML_TEXT_SLAVE (dest)->glyph_items = NULL; } static inline gint html_text_slave_get_start_byte_offset (HTMLTextSlave *slave) { return html_text_slave_get_text (slave) - slave->owner->text; } static void hts_update_asc_dsc (HTMLPainter *painter, PangoItem *item, gint *asc, gint *dsc) { PangoFontMetrics *pfm; pfm = pango_font_get_metrics (item->analysis.font, item->analysis.language); if (asc) *asc = MAX (*asc, pango_font_metrics_get_ascent (pfm)); if (dsc) *dsc = MAX (*dsc, pango_font_metrics_get_descent (pfm)); pango_font_metrics_unref (pfm); } static gint hts_calc_width (HTMLTextSlave *slave, HTMLPainter *painter, gint *asc, gint *dsc) { /*HTMLText *text = slave->owner; gint line_offset, tabs = 0, width = 0; * line_offset = html_text_slave_get_line_offset (slave, 0, painter); if (line_offset != -1) width += (html_text_text_line_length (html_text_slave_get_text (slave), &line_offset, slave->posLen, &tabs) - slave->posLen)* html_painter_get_space_width (painter, html_text_get_font_style (text), text->face); * width += html_text_calc_part_width (text, painter, html_text_slave_get_text (slave), slave->posStart, slave->posLen, asc, dsc); */ GSList *cur, *gilist = html_text_slave_get_glyph_items (slave, painter); PangoLanguage *language = NULL; PangoFont *font = NULL; gint width = 0; if (asc) *asc = html_painter_engine_to_pango (painter, html_painter_get_space_asc (painter, html_text_get_font_style (slave->owner), slave->owner->face)); if (dsc) *dsc = html_painter_engine_to_pango (painter, html_painter_get_space_dsc (painter, html_text_get_font_style (slave->owner), slave->owner->face)); for (cur = gilist; cur; cur = cur->next) { HTMLTextSlaveGlyphItem *sgi = (HTMLTextSlaveGlyphItem *) cur->data; PangoRectangle log_rect; PangoItem *item = sgi->glyph_item.item; pango_glyph_string_extents (sgi->glyph_item.glyphs, sgi->glyph_item.item->analysis.font, NULL, &log_rect); width += log_rect.width; if (item->analysis.font != font || item->analysis.language != language) hts_update_asc_dsc (painter, item, asc, dsc); } if (asc) *asc = html_painter_pango_to_engine (painter, *asc); if (dsc) *dsc = html_painter_pango_to_engine (painter, *dsc); width = html_painter_pango_to_engine (painter, width); /* printf ("hts width: %d\n", width); */ return width; } inline static void glyphs_destroy (GList *glyphs) { GList *l; for (l = glyphs; l; l = l->next->next) pango_glyph_string_free ((PangoGlyphString *) l->data); g_list_free (glyphs); } inline static void glyph_items_destroy (GSList *glyph_items) { GSList *l; for (l = glyph_items; l; l = l->next) { HTMLTextSlaveGlyphItem *gi = (HTMLTextSlaveGlyphItem *) l->data; if (gi->type == HTML_TEXT_SLAVE_GLYPH_ITEM_CREATED) { if (gi->glyph_item.item) pango_item_free (gi->glyph_item.item); if (gi->glyph_item.glyphs) pango_glyph_string_free (gi->glyph_item.glyphs); g_free (gi->widths); } g_free (gi); } g_slist_free (glyph_items); } static gboolean html_text_slave_real_calc_size (HTMLObject *self, HTMLPainter *painter, GList **changed_objs) { HTMLText *owner; HTMLTextSlave *slave; GtkHTMLFontStyle font_style; gint new_ascent, new_descent, new_width; gboolean changed; slave = HTML_TEXT_SLAVE (self); owner = HTML_TEXT (slave->owner); font_style = html_text_get_font_style (owner); new_width = MAX (1, hts_calc_width (slave, painter, &new_ascent, &new_descent)); /* handle sub & super script */ if (font_style & GTK_HTML_FONT_STYLE_SUBSCRIPT || font_style & GTK_HTML_FONT_STYLE_SUPERSCRIPT) { gint shift = (new_ascent + new_descent) >> 1; if (font_style & GTK_HTML_FONT_STYLE_SUBSCRIPT) { new_descent += shift; new_ascent -= shift; } else { new_descent -= shift; new_ascent += shift; } } changed = FALSE; if (new_ascent != self->ascent) { self->ascent = new_ascent; changed = TRUE; } if (new_descent != self->descent) { self->descent = new_descent; changed = TRUE; } if (new_width != self->width) { self->width = new_width; changed = TRUE; } return changed; } #ifdef HTML_TEXT_SLAVE_DEBUG static void debug_print (HTMLFitType rv, gchar *text, gint len) { gint i; printf ("Split text"); switch (rv) { case HTML_FIT_PARTIAL: printf (" (Partial): `"); break; case HTML_FIT_NONE: printf (" (NoFit): `"); break; case HTML_FIT_COMPLETE: printf (" (Complete): `"); break; } for (i = 0; i < len; i++) putchar (text[i]); printf ("'\n"); } #endif gint html_text_slave_get_line_offset (HTMLTextSlave *slave, gint offset, HTMLPainter *p) { HTMLObject *head = HTML_OBJECT (slave->owner)->next; g_assert (HTML_IS_TEXT_SLAVE (head)); if (!html_clueflow_tabs (HTML_CLUEFLOW (HTML_OBJECT (slave)->parent), p)) return -1; if (head->y + head->descent - 1 < HTML_OBJECT (slave)->y - HTML_OBJECT (slave)->ascent) { HTMLObject *prev; HTMLTextSlave *bol; gint line_offset = 0; prev = html_object_prev (HTML_OBJECT (slave)->parent, HTML_OBJECT (slave)); while (prev->y + prev->descent - 1 >= HTML_OBJECT (slave)->y - HTML_OBJECT (slave)->ascent) prev = html_object_prev (HTML_OBJECT (slave)->parent, HTML_OBJECT (prev)); bol = HTML_TEXT_SLAVE (prev->next); return html_text_text_line_length (html_text_slave_get_text (bol), &line_offset, slave->posStart + offset - bol->posStart, NULL); } else return html_text_get_line_offset (slave->owner, p, slave->posStart + offset); } static gboolean could_remove_leading_space (HTMLTextSlave *slave, gboolean lineBegin) { HTMLObject *o = HTML_OBJECT (slave->owner); if (lineBegin && (HTML_OBJECT (slave)->prev != o || o->prev)) return TRUE; if (!o->prev) return FALSE; while (o->prev && HTML_OBJECT_TYPE (o->prev) == HTML_TYPE_CLUEALIGNED) o = o->prev; return o->prev ? FALSE : TRUE; } gchar * html_text_slave_remove_leading_space (HTMLTextSlave *slave, HTMLPainter *painter, gboolean lineBegin) { gchar *begin; begin = html_text_slave_get_text (slave); if (*begin == ' ' && could_remove_leading_space (slave, lineBegin)) { begin = g_utf8_next_char (begin); slave->charStart = begin; slave->posStart++; slave->posLen--; } return begin; } gint html_text_slave_get_nb_width (HTMLTextSlave *slave, HTMLPainter *painter, gboolean lineBegin) { html_text_slave_remove_leading_space (slave, painter, lineBegin); return html_object_calc_min_width (HTML_OBJECT (slave), painter); } /* * offset: current position - gchar offset * s: current position - string pointer * ii: current position - index of item * io: current position - offset within item * line_offset: * w: width for current position * lwl: last-whitespacing-length (in characters) * lbw: last-break-width * lbo: last-break-offset (into text) * lbsp: last-break-string-pointer * * Checks to see if breaking the text at the given position would result in it fitting * into the remaining width. If so, updates the last-break information (lwl,lbw,lbo,lbsp). * We'll actually break the item at the last break position that still fits. */ static gboolean update_lb (HTMLTextSlave *slave, HTMLPainter *painter, gint widthLeft, gint offset, gchar *s, gint ii, gint io, gint line_offset, gint w, gint *lwl, gint *lbw, gint *lbo, gchar **lbsp, gboolean *force_fit) { gint new_ltw, new_lwl, aw; new_ltw = html_text_tail_white_space (slave->owner, painter, offset, ii, io, &new_lwl, line_offset, s); aw = w - new_ltw; if (aw <= widthLeft || *force_fit) { *lwl = new_lwl; *lbw = aw; *lbo = offset; *lbsp = s; if (*force_fit && *lbw >= widthLeft) return TRUE; *force_fit = FALSE; } else return TRUE; return FALSE; } static HTMLFitType hts_fit_line (HTMLObject *o, HTMLPainter *painter, gboolean lineBegin, gboolean firstRun, gboolean next_to_floating, gint widthLeft) { HTMLTextSlave *slave = HTML_TEXT_SLAVE (o); gint lbw, w, lbo, lwl, offset; gint ii, io, line_offset; gchar *s, *lbsp; HTMLFitType rv = HTML_FIT_NONE; HTMLTextPangoInfo *pi = html_text_get_pango_info (slave->owner, painter); gboolean force_fit = lineBegin; if (slave->posLen == 0) return HTML_FIT_COMPLETE; widthLeft = html_painter_engine_to_pango (painter, widthLeft); lbw = lwl = w = 0; offset = lbo = slave->posStart; ii = html_text_get_item_index (slave->owner, painter, offset, &io); line_offset = html_text_get_line_offset (slave->owner, painter, offset); lbsp = s = html_text_slave_get_text (slave); while ((force_fit || widthLeft > lbw) && offset < slave->posStart + slave->posLen) { if (offset > slave->posStart && offset > lbo && html_text_is_line_break (pi->attrs[offset])) if (update_lb (slave, painter, widthLeft, offset, s, ii, io, line_offset, w, &lwl, &lbw, &lbo, &lbsp, &force_fit)) break; if (io == 0 && slave->owner->text[pi->entries[ii].glyph_item.item->offset] == '\t') { GtkHTMLFontStyle font_style; gchar *face; gint skip = 8 - (line_offset % 8); if (HTML_IS_PLAIN_PAINTER (painter)) { font_style = GTK_HTML_FONT_STYLE_FIXED; face = NULL; } else { font_style = html_text_get_font_style (slave->owner); face = slave->owner->face; } pi->entries[ii].glyph_item.glyphs->glyphs[0].geometry.width = pi->entries[ii].widths[io] = skip * html_painter_get_space_width (painter, font_style, face) * PANGO_SCALE; line_offset += skip; } else { line_offset++; } w += pi->entries[ii].widths[io]; html_text_pi_forward (pi, &ii, &io); s = g_utf8_next_char (s); offset++; } if (offset == slave->posStart + slave->posLen && (widthLeft >= w || force_fit)) { rv = HTML_FIT_COMPLETE; if (slave->posLen) o->width = html_painter_pango_to_engine (painter, w); } else if (lbo > slave->posStart) { split (slave, lbo - slave->posStart - lwl, lwl, lbsp); rv = HTML_FIT_PARTIAL; o->width = html_painter_pango_to_engine (painter, lbw); o->change |= HTML_CHANGE_RECALC_PI; } return rv; } static gboolean select_range (HTMLObject *self, HTMLEngine *engine, guint start, gint length, gboolean queue_draw) { return FALSE; } static guint get_length (HTMLObject *self) { return 0; } /* HTMLObject::draw() implementation. */ static gint get_ys (HTMLText *text, HTMLPainter *p) { if (text->font_style & GTK_HTML_FONT_STYLE_SUBSCRIPT || text->font_style & GTK_HTML_FONT_STYLE_SUPERSCRIPT) { gint height2; height2 = (HTML_OBJECT (text)->ascent + HTML_OBJECT (text)->descent) / 2; /* FIX2? (html_painter_calc_ascent (p, text->font_style, text->face) * + html_painter_calc_descent (p, text->font_style, text->face)) >> 1; */ return (text->font_style & GTK_HTML_FONT_STYLE_SUBSCRIPT) ? height2 : -height2; } else return 0; } static inline GList * get_glyphs_base_text (GList *glyphs, PangoItem *item, gint ii, const gchar *text, gint bytes) { PangoGlyphString *str; str = pango_glyph_string_new (); pango_shape (text, bytes, &item->analysis, str); glyphs = g_list_prepend (glyphs, str); glyphs = g_list_prepend (glyphs, GINT_TO_POINTER (ii)); return glyphs; } GList * html_get_glyphs_non_tab (GList *glyphs, PangoItem *item, gint ii, const gchar *text, gint bytes, gint len) { gchar *tab; while ((tab = memchr (text, (guchar) '\t', bytes))) { gint c_bytes = tab - text; if (c_bytes > 0) glyphs = get_glyphs_base_text (glyphs, item, ii, text, c_bytes); text += c_bytes + 1; bytes -= c_bytes + 1; } if (bytes > 0) glyphs = get_glyphs_base_text (glyphs, item, ii, text, bytes); return glyphs; } /* * NB: This implement the exact same algorithm as * reorder-items.c:pango_reorder_items(). */ static GSList * reorder_glyph_items (GSList *glyph_items, gint n_items) { GSList *tmp_list, *level_start_node; gint i, level_start_i; gint min_level = G_MAXINT; GSList *result = NULL; if (n_items == 0) return NULL; tmp_list = glyph_items; for (i = 0; i < n_items; i++) { HTMLTextSlaveGlyphItem *gi = tmp_list->data; min_level = MIN (min_level, gi->glyph_item.item->analysis.level); tmp_list = tmp_list->next; } level_start_i = 0; level_start_node = glyph_items; tmp_list = glyph_items; for (i = 0; i < n_items; i++) { HTMLTextSlaveGlyphItem *gi= tmp_list->data; if (gi->glyph_item.item->analysis.level == min_level) { if (min_level % 2) { if (i > level_start_i) result = g_slist_concat (reorder_glyph_items (level_start_node, i - level_start_i), result); result = g_slist_prepend (result, gi); } else { if (i > level_start_i) result = g_slist_concat (result, reorder_glyph_items (level_start_node, i - level_start_i)); result = g_slist_append (result, gi); } level_start_i = i + 1; level_start_node = tmp_list->next; } tmp_list = tmp_list->next; } if (min_level % 2) { if (i > level_start_i) result = g_slist_concat (reorder_glyph_items (level_start_node, i - level_start_i), result); } else { if (i > level_start_i) result = g_slist_concat (result, reorder_glyph_items (level_start_node, i - level_start_i)); } return result; } static GSList * get_glyph_items_in_range (HTMLTextSlave *slave, HTMLPainter *painter, gint start_offset, gint len) { HTMLTextPangoInfo *pi = html_text_get_pango_info (slave->owner, painter); gint i, offset, end_offset, n_items = 0; GSList *glyph_items = NULL; start_offset += slave->posStart; end_offset = start_offset + len; for (offset = 0, i = 0; i < pi->n; i++) { PangoItem *item = pi->entries[i].glyph_item.item; /* do item and slave overlap? */ if (MAX (offset, start_offset) < MIN (offset + item->num_chars, end_offset)) { HTMLTextSlaveGlyphItem *glyph_item = g_new (HTMLTextSlaveGlyphItem, 1); /* use text glyph item */ glyph_item->type = HTML_TEXT_SLAVE_GLYPH_ITEM_PARENTAL; glyph_item->glyph_item = pi->entries[i].glyph_item; glyph_item->widths = pi->entries[i].widths; if (start_offset > offset) { /* need to cut the beginning of current glyph item */ PangoGlyphItem *tmp_gi; gint split_index; glyph_item->type = HTML_TEXT_SLAVE_GLYPH_ITEM_CREATED; glyph_item->widths = NULL; glyph_item->glyph_item.item = pango_item_copy (glyph_item->glyph_item.item); glyph_item->glyph_item.glyphs = pango_glyph_string_copy (glyph_item->glyph_item.glyphs); split_index = g_utf8_offset_to_pointer (slave->owner->text + item->offset, start_offset - offset) - (slave->owner->text + item->offset); tmp_gi = pango_glyph_item_split (&glyph_item->glyph_item, slave->owner->text, split_index); /* free the beginning we don't need */ pango_glyph_item_free (tmp_gi); } if (offset + item->num_chars > end_offset) { /* need to cut the ending of current glyph item */ PangoGlyphItem tmp_gi1, *tmp_gi2; gint split_index; if (glyph_item->type == HTML_TEXT_SLAVE_GLYPH_ITEM_PARENTAL) { tmp_gi1.item = pango_item_copy (glyph_item->glyph_item.item); tmp_gi1.glyphs = pango_glyph_string_copy (glyph_item->glyph_item.glyphs); } else tmp_gi1 = glyph_item->glyph_item; split_index = g_utf8_offset_to_pointer (slave->owner->text + tmp_gi1.item->offset, end_offset - MAX (start_offset, offset)) - (slave->owner->text + tmp_gi1.item->offset); tmp_gi2 = pango_glyph_item_split (&tmp_gi1, slave->owner->text, split_index); glyph_item->glyph_item = *tmp_gi2; tmp_gi2->item = NULL; tmp_gi2->glyphs = NULL; /* free the tmp1 content and tmp2 container, but not the content */ pango_item_free (tmp_gi1.item); pango_glyph_string_free (tmp_gi1.glyphs); pango_glyph_item_free (tmp_gi2); glyph_item->type = HTML_TEXT_SLAVE_GLYPH_ITEM_CREATED; glyph_item->widths = NULL; } glyph_items = g_slist_prepend (glyph_items, glyph_item); n_items++; } if (offset + item->num_chars >= end_offset) break; offset += item->num_chars; } if (glyph_items) { GSList *reversed; reversed = g_slist_reverse (glyph_items); glyph_items = reorder_glyph_items (reversed, n_items); g_slist_free (reversed); } return glyph_items; } GSList * html_text_slave_get_glyph_items (HTMLTextSlave *slave, HTMLPainter *painter) { if (painter && (!slave->glyph_items || (HTML_OBJECT (slave)->change & HTML_CHANGE_RECALC_PI))) { clear_glyph_items (slave); HTML_OBJECT (slave)->change &= ~HTML_CHANGE_RECALC_PI; slave->glyph_items = get_glyph_items_in_range (slave, painter, 0, slave->posLen); } return slave->glyph_items; } static gboolean calc_glyph_range_size (HTMLText *text, PangoGlyphItem *glyph_item, gint start_index, gint end_index, gint *x_offset, gint *width, gint *asc, gint *height) { gint isect_start, isect_end; isect_start = MAX (glyph_item->item->offset, start_index); isect_end = MIN (glyph_item->item->offset + glyph_item->item->length, end_index); isect_start -= glyph_item->item->offset; isect_end -= glyph_item->item->offset; /* printf ("calc_glyph_range_size isect start %d end %d (end_index %d)\n", isect_start, isect_end, end_index); */ if (isect_start <= isect_end) { PangoRectangle log_rect; gint start_x, end_x; pango_glyph_string_index_to_x (glyph_item->glyphs, text->text + glyph_item->item->offset, glyph_item->item->length, &glyph_item->item->analysis, isect_start, FALSE, &start_x); if (isect_start < isect_end) pango_glyph_string_index_to_x (glyph_item->glyphs, text->text + glyph_item->item->offset, glyph_item->item->length, &glyph_item->item->analysis, isect_end, FALSE, &end_x); else end_x = start_x; if (asc || height) /* this call is used only to get ascent and height */ pango_glyph_string_extents (glyph_item->glyphs, glyph_item->item->analysis.font, NULL, &log_rect); /* printf ("selection_start_index %d selection_end_index %d isect_start %d isect_end %d start_x %d end_x %d cwidth %d width %d\n", * selection_start_index, selection_end_index, isect_start, isect_end, * html_painter_pango_to_engine (p, start_x), html_painter_pango_to_engine (p, end_x), * html_painter_pango_to_engine (p, start_x < end_x ? (end_x - start_x) : (start_x - end_x)), * html_painter_pango_to_engine (p, log_rect.width)); */ if (x_offset) *x_offset = MIN (start_x, end_x); if (width) *width = start_x < end_x ? (end_x - start_x) : (start_x - end_x); if (asc) *asc = PANGO_ASCENT (log_rect); if (height) *height = log_rect.height; return TRUE; } return FALSE; } static void draw_text (HTMLTextSlave *self, HTMLPainter *p, GtkHTMLFontStyle font_style, gint x, gint y, gint width, gint height, gint tx, gint ty) { HTMLObject *obj; HTMLText *text = self->owner; GSList *cur; gint run_width; gint selection_start_index = 0; gint selection_end_index = 0; gint isect_start, isect_end; gboolean selection; GdkColor selection_fg, selection_bg; HTMLEngine *e = NULL; obj = HTML_OBJECT (self); isect_start = MAX (text->select_start, self->posStart); isect_end = MIN (text->select_start + text->select_length, self->posStart + self->posLen); selection = isect_start < isect_end; if (p->widget && GTK_IS_HTML (p->widget)) e = html_object_engine (HTML_OBJECT (self->owner), GTK_HTML (p->widget)->engine); if (selection) { gchar *end; gchar *start; start = html_text_get_text (text, isect_start); end = g_utf8_offset_to_pointer (start, isect_end - isect_start); selection_start_index = start - text->text; selection_end_index = end - text->text; if (e) { selection_fg = html_colorset_get_color_allocated (e->settings->color_set, p, p->focus ? HTMLHighlightTextColor : HTMLHighlightTextNFColor)->color; selection_bg = html_colorset_get_color_allocated (e->settings->color_set, p, p->focus ? HTMLHighlightColor : HTMLHighlightNFColor)->color; } } /* printf ("draw_text %d %d %d\n", selection_bg.red, selection_bg.green, selection_bg.blue); */ run_width = 0; for (cur = html_text_slave_get_glyph_items (self, p); cur; cur = cur->next) { HTMLTextSlaveGlyphItem *gi = (HTMLTextSlaveGlyphItem *) cur->data; GList *cur_se; gint cur_width; if (e) html_painter_set_pen (p, &html_colorset_get_color_allocated (e->settings->color_set, e->painter, HTMLTextColor)->color); cur_width = html_painter_draw_glyphs (p, obj->x + tx + html_painter_pango_to_engine (p, run_width), obj->y + ty + get_ys (text, p), gi->glyph_item.item, gi->glyph_item.glyphs, NULL, NULL); if (selection) { gint start_x, width, asc, height; gint cx, cy, cw, ch; if (calc_glyph_range_size (text, &gi->glyph_item, selection_start_index, selection_end_index, &start_x, &width, &asc, &height) && width > 0) { html_painter_get_clip_rectangle (p, &cx, &cy, &cw, &ch); /* printf ("run_width; %d start_x %d index %d\n", run_width, start_x, selection_start_index); */ html_painter_set_clip_rectangle (p, obj->x + tx + html_painter_pango_to_engine (p, run_width + start_x), obj->y + ty + get_ys (text, p) - html_painter_pango_to_engine (p, asc), html_painter_pango_to_engine (p, width), html_painter_pango_to_engine (p, height)); /* printf ("draw selection %d %d %d at %d, %d\n", selection_bg.red, selection_bg.green, selection_bg.blue, * obj->x + tx + run_width, obj->y + ty + get_ys (text, p)); */ html_painter_draw_glyphs (p, obj->x + tx + html_painter_pango_to_engine (p, run_width), obj->y + ty + get_ys (text, p), gi->glyph_item.item, gi->glyph_item.glyphs, &selection_fg, &selection_bg); html_painter_set_clip_rectangle (p, cx, cy, cw, ch); } } for (cur_se = text->spell_errors; e && cur_se; cur_se = cur_se->next) { SpellError *se; guint ma, mi; se = (SpellError *) cur_se->data; ma = MAX (se->off, self->posStart); mi = MIN (se->off + se->len, self->posStart + self->posLen); if (ma < mi) { gint width, height, asc, start_x; gchar *end; gchar *start; gint se_start_index, se_end_index; start = html_text_get_text (text, ma); end = g_utf8_offset_to_pointer (start, mi - ma); se_start_index = start - text->text; se_end_index = end - text->text; if (calc_glyph_range_size (text, &gi->glyph_item, se_start_index, se_end_index, &start_x, &width, &asc, &height)) { html_painter_set_pen (p, &html_colorset_get_color_allocated (e->settings->color_set, p, HTMLSpellErrorColor)->color); /* printf ("spell error: %s\n", html_text_get_text (slave->owner, off)); */ html_painter_draw_spell_error (p, obj->x + tx + html_painter_pango_to_engine (p, run_width + start_x), obj->y + ty + get_ys (self->owner, p), html_painter_pango_to_engine (p, width)); } } if (se->off > self->posStart + self->posLen) break; } run_width += cur_width; } } static void draw_focus_rectangle (HTMLTextSlave *slave, HTMLPainter *painter, GdkRectangle *box) { HTMLGdkPainter *p; const double dashes[] = { 1, 2 }; gint ndash = G_N_ELEMENTS (dashes); HTMLEngine *e; if (painter->widget && GTK_IS_HTML (painter->widget)) e = html_object_engine (HTML_OBJECT (slave->owner), GTK_HTML (painter->widget)->engine); else return; if (HTML_IS_PRINTER (painter)) return; p = HTML_GDK_PAINTER (painter); /* printf ("draw_text_focus\n"); */ cairo_save (p->cr); gdk_cairo_set_source_color (p->cr, &html_colorset_get_color_allocated (e->settings->color_set, painter, HTMLTextColor)->color); cairo_set_line_cap (p->cr, CAIRO_LINE_CAP_ROUND); cairo_set_line_width (p->cr, 1); cairo_set_dash (p->cr, dashes, ndash, 2); cairo_rectangle ( p->cr, box->x - p->x1 - 0.5, box->y - p->y1 + 0.5, box->width + 1, box->height); cairo_stroke (p->cr); cairo_restore (p->cr); } static void draw_focus (HTMLTextSlave *slave, HTMLPainter *p, gint tx, gint ty) { GdkRectangle rect; Link *link = html_text_get_link_at_offset (slave->owner, slave->owner->focused_link_offset); if (link && MAX (link->start_offset, slave->posStart) < MIN (link->end_offset, slave->posStart + slave->posLen)) { gint bw = 0; html_object_get_bounds (HTML_OBJECT (slave), &rect); if (slave->posStart < link->start_offset) bw = html_text_calc_part_width (slave->owner, p, html_text_slave_get_text (slave), slave->posStart, link->start_offset - slave->posStart, NULL, NULL); rect.x += tx + bw; rect.width -= bw; if (slave->posStart + slave->posLen > link->end_offset) rect.width -= html_text_calc_part_width (slave->owner, p, slave->owner->text + link->end_index, link->end_offset, slave->posStart + slave->posLen - link->end_offset, NULL, NULL); rect.y += ty; draw_focus_rectangle (slave, p, &rect); } } static void draw (HTMLObject *o, HTMLPainter *p, gint x, gint y, gint width, gint height, gint tx, gint ty) { HTMLTextSlave *slave; HTMLText *owner; GtkHTMLFontStyle font_style; GdkRectangle paint; /* printf ("slave draw %p\n", o); */ slave = HTML_TEXT_SLAVE (o); if (!html_object_intersect (o, &paint, x, y, width, height) || slave->posLen == 0) return; owner = slave->owner; font_style = html_text_get_font_style (owner); draw_text (slave, p, font_style, x, y, width, height, tx, ty); if (HTML_OBJECT (owner)->draw_focused) draw_focus (slave, p, tx, ty); } static gint calc_min_width (HTMLObject *o, HTMLPainter *painter) { return 0; } static gint calc_preferred_width (HTMLObject *o, HTMLPainter *painter) { return 0; } static const gchar * get_url (HTMLObject *o, gint offset) { HTMLTextSlave *slave; slave = HTML_TEXT_SLAVE (o); return html_object_get_url (HTML_OBJECT (slave->owner), offset); } static gint calc_offset (HTMLTextSlave *slave, HTMLPainter *painter, gint x) { GSList *cur, *glyphs = html_text_slave_get_glyph_items (slave, painter); gint width = 0, offset = html_object_get_direction (HTML_OBJECT (slave->owner)) == HTML_DIRECTION_RTL ? slave->posLen : 0; PangoItem *item = NULL; if (glyphs) { gint i = 0; for (cur = glyphs; cur; cur = cur->next) { HTMLTextSlaveGlyphItem *gi = (HTMLTextSlaveGlyphItem *) cur->data; item = gi->glyph_item.item; if (gi->widths == NULL) { gi->widths = g_new (PangoGlyphUnit, item->num_chars); html_tmp_fix_pango_glyph_string_get_logical_widths (gi->glyph_item.glyphs, slave->owner->text + item->offset, item->length, item->analysis.level, gi->widths); } if (item->analysis.level % 2 == 0) { /* LTR */ for (i = 0; i < item->num_chars; i++) { if (x < html_painter_pango_to_engine (painter, width + gi->widths[i] / 2)) goto done; width += gi->widths[i]; } } else { /* RTL */ for (i = item->num_chars - 1; i >= 0; i--) { if (x < html_painter_pango_to_engine (painter, width + gi->widths[i] / 2)) { i++; goto done; } width += gi->widths[i]; } } } done: if (cur) offset = g_utf8_pointer_to_offset (html_text_slave_get_text (slave), slave->owner->text + item->offset) + i; else { offset = html_object_get_direction (HTML_OBJECT (slave->owner)) == HTML_DIRECTION_RTL ? 0 : slave->posLen; } } /* printf ("offset %d\n", offset); */ return offset; } static guint get_offset_for_pointer (HTMLTextSlave *slave, HTMLPainter *painter, gint x, gint y) { g_return_val_if_fail (slave != NULL, 0); x -= HTML_OBJECT (slave)->x; if (x <= 0) return 0; if (x >= HTML_OBJECT (slave)->width - 1) return slave->posLen; if (slave->posLen > 1) return calc_offset (slave, painter, x); else return x > HTML_OBJECT (slave)->width / 2 ? 1 : 0; } static HTMLObject * check_point (HTMLObject *self, HTMLPainter *painter, gint x, gint y, guint *offset_return, gboolean for_cursor) { if (x >= self->x && x < self->x + MAX (1, self->width) && y >= self->y - self->ascent && y < self->y + self->descent) { HTMLTextSlave *slave = HTML_TEXT_SLAVE (self); if (offset_return != NULL) *offset_return = slave->posStart + get_offset_for_pointer (slave, painter, x, y); return HTML_OBJECT (slave->owner); } return NULL; } static void clear_glyph_items (HTMLTextSlave *slave) { if (slave->glyph_items) { glyph_items_destroy (slave->glyph_items); slave->glyph_items = NULL; } } static void destroy (HTMLObject *obj) { HTMLTextSlave *slave = HTML_TEXT_SLAVE (obj); clear_glyph_items (slave); HTML_OBJECT_CLASS (parent_class)->destroy (obj); } void html_text_slave_type_init (void) { html_text_slave_class_init (&html_text_slave_class, HTML_TYPE_TEXTSLAVE, sizeof (HTMLTextSlave)); } void html_text_slave_class_init (HTMLTextSlaveClass *klass, HTMLType type, guint object_size) { HTMLObjectClass *object_class; object_class = HTML_OBJECT_CLASS (klass); html_object_class_init (object_class, type, object_size); object_class->select_range = select_range; object_class->copy = copy; object_class->destroy = destroy; object_class->draw = draw; object_class->calc_size = html_text_slave_real_calc_size; object_class->fit_line = hts_fit_line; object_class->calc_min_width = calc_min_width; object_class->calc_preferred_width = calc_preferred_width; object_class->get_url = get_url; object_class->get_length = get_length; object_class->check_point = check_point; parent_class = &html_object_class; } void html_text_slave_init (HTMLTextSlave *slave, HTMLTextSlaveClass *klass, HTMLText *owner, guint posStart, guint posLen) { HTMLObject *object; object = HTML_OBJECT (slave); html_object_init (object, HTML_OBJECT_CLASS (klass)); object->ascent = HTML_OBJECT (owner)->ascent; object->descent = HTML_OBJECT (owner)->descent; slave->posStart = posStart; slave->posLen = posLen; slave->owner = owner; slave->charStart = NULL; slave->pi = NULL; slave->glyph_items = NULL; /* text slaves have always min_width 0 */ object->min_width = 0; object->change &= ~HTML_CHANGE_MIN_WIDTH; } HTMLObject * html_text_slave_new (HTMLText *owner, guint posStart, guint posLen) { HTMLTextSlave *slave; slave = g_new (HTMLTextSlave, 1); html_text_slave_init (slave, &html_text_slave_class, owner, posStart, posLen); return HTML_OBJECT (slave); } static gboolean html_text_slave_is_index_in_glyph (HTMLTextSlave *slave, HTMLTextSlave *next_slave, GSList *cur, gint index, PangoItem *item) { if (item->analysis.level % 2 == 0) { /* LTR */ return item->offset <= index && (index < item->offset + item->length || (index == item->offset + item->length && (!cur->next || (!next_slave && slave->owner->text_bytes == item->offset + item->length) || (next_slave && html_text_slave_get_text (next_slave) - next_slave->owner->text == item->offset + item->length)))); } else { /* RTL */ return index <= item->offset + item->length && (item->offset < index || (index == item->offset && (!cur->next || (!next_slave && slave->owner->text_bytes == item->offset + item->length) || (next_slave && html_text_slave_get_text (next_slave) - next_slave->owner->text == item->offset)))); } } static HTMLTextSlaveGlyphItem * html_text_slave_get_glyph_item_at_offset (HTMLTextSlave *slave, HTMLPainter *painter, gint offset, HTMLTextSlaveGlyphItem **prev, HTMLTextSlaveGlyphItem **next, gint *start_width, gint *index_out) { HTMLTextSlaveGlyphItem *rv = NULL; HTMLTextSlaveGlyphItem *prev_gi, *next_gi; HTMLTextSlave *next_slave = HTML_OBJECT (slave)->next && HTML_IS_TEXT_SLAVE (HTML_OBJECT (slave)->next) ? HTML_TEXT_SLAVE (HTML_OBJECT (slave)->next) : NULL; GSList *cur; gint index; next_gi = NULL; index = g_utf8_offset_to_pointer (html_text_slave_get_text (slave), offset) - slave->owner->text; if (index_out) *index_out = index; if (start_width) *start_width = 0; cur = html_text_slave_get_glyph_items (slave, painter); if (cur) { for (prev_gi = NULL; cur; cur = cur->next) { HTMLTextSlaveGlyphItem *gi = (HTMLTextSlaveGlyphItem *) cur->data; if (html_text_slave_is_index_in_glyph (slave, next_slave, cur, index, gi->glyph_item.item)) { next_gi = cur->next ? (HTMLTextSlaveGlyphItem *) cur->next->data : NULL; rv = gi; break; } prev_gi = gi; if (start_width) { PangoRectangle log_rect; pango_glyph_string_extents (gi->glyph_item.glyphs, gi->glyph_item.item->analysis.font, NULL, &log_rect); (*start_width) += log_rect.width; } } } else { prev_gi = next_gi = NULL; } if (prev) *prev = prev_gi; if (next) *next = next_gi; return rv; } static gboolean html_text_slave_gi_left_edge (HTMLTextSlave *slave, HTMLCursor *cursor, HTMLTextSlaveGlyphItem *gi) { gint old_offset = cursor->offset; if (gi->glyph_item.item->analysis.level % 2 == 0) { /* LTR */ cursor->offset = slave->posStart + g_utf8_pointer_to_offset (html_text_slave_get_text (slave), slave->owner->text + gi->glyph_item.item->offset); cursor->position += cursor->offset - old_offset; } else { /* RTL */ cursor->offset = slave->posStart + g_utf8_pointer_to_offset (html_text_slave_get_text (slave), slave->owner->text + gi->glyph_item.item->offset + gi->glyph_item.item->length); cursor->position += cursor->offset - old_offset; } return TRUE; } static gboolean html_text_slave_gi_right_edge (HTMLTextSlave *slave, HTMLCursor *cursor, HTMLTextSlaveGlyphItem *gi) { gint old_offset = cursor->offset; if (gi->glyph_item.item->analysis.level % 2 == 0) { /* LTR */ cursor->offset = slave->posStart + g_utf8_pointer_to_offset (html_text_slave_get_text (slave), slave->owner->text + gi->glyph_item.item->offset + gi->glyph_item.item->length); cursor->position += cursor->offset - old_offset; } else { /* RTL */ cursor->offset = slave->posStart + g_utf8_pointer_to_offset (html_text_slave_get_text (slave), slave->owner->text + gi->glyph_item.item->offset); cursor->position += cursor->offset - old_offset; } return TRUE; } static gboolean html_text_slave_cursor_right_one (HTMLTextSlave *slave, HTMLPainter *painter, HTMLCursor *cursor) { HTMLTextSlaveGlyphItem *prev, *next; gint index; HTMLTextSlaveGlyphItem *gi = html_text_slave_get_glyph_item_at_offset (slave, painter, cursor->offset - slave->posStart, &prev, &next, NULL, &index); if (!gi) return FALSE; if (gi->glyph_item.item->analysis.level % 2 == 0) { /* LTR */ if (index < gi->glyph_item.item->offset + gi->glyph_item.item->length) { cursor->offset++; cursor->position++; return TRUE; } } else { /* RTL */ if (index > gi->glyph_item.item->offset && index <= gi->glyph_item.item->offset + gi->glyph_item.item->length) { cursor->offset--; cursor->position--; return TRUE; } } if (next) { if (html_text_slave_gi_left_edge (slave, cursor, next)) { if (next->glyph_item.item->analysis.level % 2 == 0) { /* LTR */ cursor->offset++; cursor->position++; } else { /* RTL */ cursor->offset--; cursor->position--; } return TRUE; } } return FALSE; } gboolean html_text_slave_cursor_right (HTMLTextSlave *slave, HTMLPainter *painter, HTMLCursor *cursor) { HTMLTextPangoInfo *pi = html_text_get_pango_info (slave->owner, painter); gboolean step_success; do step_success = html_text_slave_cursor_right_one (slave, painter, cursor); while (step_success && !pi->attrs[cursor->offset].is_cursor_position); return step_success; } static gboolean html_text_slave_cursor_left_one (HTMLTextSlave *slave, HTMLPainter *painter, HTMLCursor *cursor) { HTMLTextSlaveGlyphItem *prev, *next; gint index; HTMLObject *prev_obj = HTML_OBJECT (slave->owner)->prev; HTMLTextSlaveGlyphItem *gi = html_text_slave_get_glyph_item_at_offset (slave, painter, cursor->offset - slave->posStart, &prev, &next, NULL, &index); /* printf ("gi: %p item num chars: %d\n", gi, gi ? gi->glyph_item.item->num_chars : -1); */ if (!gi) return FALSE; if (gi->glyph_item.item->analysis.level % 2 == 0) { /* LTR */ if (index - gi->glyph_item.item->offset > 1 || (!prev && !prev_obj && index - gi->glyph_item.item->offset > 0)) { cursor->offset--; cursor->position--; return TRUE; } } else { /* RTL */ if (index < gi->glyph_item.item->offset + gi->glyph_item.item->length) { cursor->offset++; cursor->position++; return TRUE; } } if (prev) { if (html_text_slave_gi_right_edge (slave, cursor, prev)) { if (prev->glyph_item.item->analysis.level % 2 == 0) { /* LTR */ if (index - gi->glyph_item.item->offset == 0) { cursor->offset--; cursor->position--; } } else { /* RTL */ cursor->offset++; cursor->position++; } return TRUE; } } return FALSE; } gboolean html_text_slave_cursor_left (HTMLTextSlave *slave, HTMLPainter *painter, HTMLCursor *cursor) { HTMLTextPangoInfo *pi = html_text_get_pango_info (slave->owner, painter); gboolean step_success; do step_success = html_text_slave_cursor_left_one (slave, painter, cursor); while (step_success && !pi->attrs[cursor->offset].is_cursor_position); return step_success; } static gboolean html_text_slave_get_left_edge (HTMLTextSlave *slave, HTMLPainter *painter, HTMLCursor *cursor) { HTMLTextPangoInfo *pi = html_text_get_pango_info (slave->owner, painter); gint old_offset = cursor->offset; gint old_position = cursor->position; cursor->offset = html_text_slave_get_left_edge_offset (slave, painter); if (pi->attrs[cursor->offset].is_cursor_position && old_offset != cursor->offset) return TRUE; else { if (html_text_slave_cursor_right (slave, painter, cursor)) { /* we should preserve position here as caller function correct position themselves */ cursor->position = old_position; return TRUE; } else return FALSE; } } static gboolean html_text_slave_get_right_edge (HTMLTextSlave *slave, HTMLPainter *painter, HTMLCursor *cursor) { HTMLTextPangoInfo *pi = html_text_get_pango_info (slave->owner, painter); gint old_offset = cursor->offset; gint old_position = cursor->position; cursor->offset = html_text_slave_get_right_edge_offset (slave, painter); if (pi->attrs[cursor->offset].is_cursor_position && old_offset != cursor->offset) return TRUE; else { if (html_text_slave_cursor_left (slave, painter, cursor)) { /* we should preserve position here as caller function correct position themselves */ cursor->position = old_position; return TRUE; } else return FALSE; } } gboolean html_text_slave_cursor_head (HTMLTextSlave *slave, HTMLCursor *cursor, HTMLPainter *painter) { if (html_text_slave_get_glyph_items (slave, painter)) { cursor->object = HTML_OBJECT (slave->owner); if (html_text_get_pango_direction (slave->owner) != PANGO_DIRECTION_RTL) { /* LTR */ return html_text_slave_get_left_edge (slave, painter, cursor); } else { /* RTL */ return html_text_slave_get_right_edge (slave, painter, cursor); } } return FALSE; } gboolean html_text_slave_cursor_tail (HTMLTextSlave *slave, HTMLCursor *cursor, HTMLPainter *painter) { if (html_text_slave_get_glyph_items (slave, painter)) { cursor->object = HTML_OBJECT (slave->owner); if (html_text_get_pango_direction (slave->owner) != PANGO_DIRECTION_RTL) { /* LTR */ return html_text_slave_get_right_edge (slave, painter, cursor); } else { /* RTL */ return html_text_slave_get_left_edge (slave, painter, cursor); } } return FALSE; } void html_text_slave_get_cursor_base (HTMLTextSlave *slave, HTMLPainter *painter, guint offset, gint *x, gint *y) { HTMLTextSlaveGlyphItem *gi; gint index, start_width; html_object_calc_abs_position (HTML_OBJECT (slave), x, y); gi = html_text_slave_get_glyph_item_at_offset (slave, painter, (gint) offset, NULL, NULL, &start_width, &index); /* printf ("gi: %p index: %d start_width: %d item indexes %d %d\n", */ /* gi, index, start_width, gi ? gi->glyph_item.item->offset : -1, */ /* gi ? gi->glyph_item.item->offset + gi->glyph_item.item->length : -1); */ if (gi) { gint start_x; if (calc_glyph_range_size (slave->owner, &gi->glyph_item, index, index, &start_x, NULL, NULL, NULL) && x) { /* printf ("start_width: %d start_x: %d\n", start_width, start_x); */ *x += html_painter_pango_to_engine (painter, start_width + start_x); } } } gint html_text_slave_get_left_edge_offset (HTMLTextSlave *slave, HTMLPainter *painter) { GSList *gis = html_text_slave_get_glyph_items (slave, painter); if (gis) { HTMLTextSlaveGlyphItem *gi = (HTMLTextSlaveGlyphItem *) gis->data; if (gi->glyph_item.item->analysis.level % 2 == 0) { /* LTR */ return slave->posStart + g_utf8_pointer_to_offset (html_text_slave_get_text (slave), slave->owner->text + gi->glyph_item.item->offset); } else { /* RTL */ return slave->posStart + MIN (slave->posLen, g_utf8_pointer_to_offset (html_text_slave_get_text (slave), slave->owner->text + gi->glyph_item.item->offset + gi->glyph_item.item->length)); } } else { if (slave->owner->text_len > 0) g_warning ("html_text_slave_get_left_edge_offset failed"); return 0; } } gint html_text_slave_get_right_edge_offset (HTMLTextSlave *slave, HTMLPainter *painter) { GSList *gis = html_text_slave_get_glyph_items (slave, painter); if (gis) { HTMLTextSlaveGlyphItem *gi = (HTMLTextSlaveGlyphItem *) g_slist_last (gis)->data; if (gi->glyph_item.item->analysis.level % 2 == 0) { /* LTR */ return slave->posStart + MIN (slave->posLen, g_utf8_pointer_to_offset (html_text_slave_get_text (slave), slave->owner->text + gi->glyph_item.item->offset + gi->glyph_item.item->length)); } else { /* RTL */ return slave->posStart + g_utf8_pointer_to_offset (html_text_slave_get_text (slave), slave->owner->text + gi->glyph_item.item->offset); } } else { if (slave->owner->text_len > 0) g_warning ("html_text_slave_get_left_edge_offset failed"); return 0; } }