PageRenderTime 101ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 1ms

/gtkhtml-4.5.3/gtkhtml/htmltext.c

#
C | 4308 lines | 3399 code | 682 blank | 227 comment | 686 complexity | 022039b99966830c5f01726ab7c42ec2 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.0
  1. /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
  2. /* This file is part of the GtkHTML library.
  3. *
  4. * Copyright (C) 1997 Martin Jones (mjones@kde.org)
  5. * Copyright (C) 1997 Torben Weis (weis@kde.org)
  6. * Copyright (C) 1999, 2000 Helix Code, Inc.
  7. *
  8. * This library is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU Library General Public
  10. * License as published by the Free Software Foundation; either
  11. * version 2 of the License, or (at your option) any later version.
  12. *
  13. * This library is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  16. * Library General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Library General Public License
  19. * along with this library; see the file COPYING.LIB. If not, write to
  20. * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  21. * Boston, MA 02110-1301, USA.
  22. */
  23. #include <config.h>
  24. #include <stdio.h>
  25. #include <string.h>
  26. #include <sys/types.h>
  27. #include <regex.h>
  28. #include <math.h>
  29. #define PANGO_ENABLE_BACKEND /* Required to get PANGO_GLYPH_EMPTY */
  30. #include <pango/pango.h>
  31. #include "htmltext.h"
  32. #include "htmlcolor.h"
  33. #include "htmlcolorset.h"
  34. #include "htmlcluealigned.h"
  35. #include "htmlclueflow.h"
  36. #include "htmlcursor.h"
  37. #include "htmlgdkpainter.h"
  38. #include "htmlplainpainter.h"
  39. #include "htmlprinter.h"
  40. #include "htmlengine.h"
  41. #include "htmlengine-edit.h"
  42. #include "htmlengine-edit-cut-and-paste.h"
  43. #include "htmlengine-save.h"
  44. #include "htmlentity.h"
  45. #include "htmlsettings.h"
  46. #include "htmltextslave.h"
  47. #include "htmlundo.h"
  48. HTMLTextClass html_text_class;
  49. static HTMLObjectClass *parent_class = NULL;
  50. static const PangoAttrClass html_pango_attr_font_size_klass;
  51. #define HT_CLASS(x) HTML_TEXT_CLASS (HTML_OBJECT (x)->klass)
  52. #ifdef PANGO_GLYPH_EMPTY
  53. #define EMPTY_GLYPH PANGO_GLYPH_EMPTY
  54. #else
  55. #define EMPTY_GLYPH 0
  56. #endif
  57. static SpellError * spell_error_new (guint off, guint len);
  58. static void spell_error_destroy (SpellError *se);
  59. static void move_spell_errors (GList *spell_errors, guint offset, gint delta);
  60. static GList * remove_spell_errors (GList *spell_errors, guint offset, guint len);
  61. static GList * merge_spell_errors (GList *se1, GList *se2);
  62. static void remove_text_slaves (HTMLObject *self);
  63. /* void
  64. debug_spell_errors (GList *se)
  65. {
  66. for (; se; se = se->next)
  67. printf ("SE: %4d, %4d\n", ((SpellError *) se->data)->off, ((SpellError *) se->data)->len);
  68. } */
  69. static inline gboolean
  70. is_in_the_save_cluev (HTMLObject *text,
  71. HTMLObject *o)
  72. {
  73. return html_object_nth_parent (o, 2) == html_object_nth_parent (text, 2);
  74. }
  75. /* HTMLObject methods. */
  76. HTMLTextPangoInfo *
  77. html_text_pango_info_new (gint n)
  78. {
  79. HTMLTextPangoInfo *pi;
  80. pi = g_new (HTMLTextPangoInfo, 1);
  81. pi->n = n;
  82. pi->entries = g_new0 (HTMLTextPangoInfoEntry, n);
  83. pi->attrs = NULL;
  84. pi->have_font = FALSE;
  85. pi->font_style = GTK_HTML_FONT_STYLE_DEFAULT;
  86. pi->face = NULL;
  87. return pi;
  88. }
  89. void
  90. html_text_pango_info_destroy (HTMLTextPangoInfo *pi)
  91. {
  92. gint i;
  93. for (i = 0; i < pi->n; i++) {
  94. pango_item_free (pi->entries[i].glyph_item.item);
  95. if (pi->entries[i].glyph_item.glyphs)
  96. pango_glyph_string_free (pi->entries[i].glyph_item.glyphs);
  97. g_free (pi->entries[i].widths);
  98. }
  99. g_free (pi->entries);
  100. g_free (pi->attrs);
  101. g_free (pi->face);
  102. g_free (pi);
  103. }
  104. static void
  105. pango_info_destroy (HTMLText *text)
  106. {
  107. if (text->pi) {
  108. html_text_pango_info_destroy (text->pi);
  109. text->pi = NULL;
  110. }
  111. }
  112. static void
  113. free_links (GSList *list)
  114. {
  115. if (list) {
  116. GSList *l;
  117. for (l = list; l; l = l->next)
  118. html_link_free ((Link *) l->data);
  119. g_slist_free (list);
  120. }
  121. }
  122. void
  123. html_text_free_attrs (GSList *attrs)
  124. {
  125. if (attrs) {
  126. GSList *l;
  127. for (l = attrs; l; l = l->next)
  128. pango_attribute_destroy ((PangoAttribute *) l->data);
  129. g_slist_free (attrs);
  130. }
  131. }
  132. static void
  133. copy (HTMLObject *s,
  134. HTMLObject *d)
  135. {
  136. HTMLText *src = HTML_TEXT (s);
  137. HTMLText *dest = HTML_TEXT (d);
  138. GList *cur;
  139. GSList *csl;
  140. (* HTML_OBJECT_CLASS (parent_class)->copy) (s, d);
  141. dest->text = g_strdup (src->text);
  142. dest->text_len = src->text_len;
  143. dest->text_bytes = src->text_bytes;
  144. dest->font_style = src->font_style;
  145. dest->face = g_strdup (src->face);
  146. dest->color = src->color;
  147. dest->select_start = 0;
  148. dest->select_length = 0;
  149. dest->attr_list = pango_attr_list_copy (src->attr_list);
  150. dest->extra_attr_list = src->extra_attr_list ? pango_attr_list_copy (src->extra_attr_list) : NULL;
  151. html_color_ref (dest->color);
  152. dest->spell_errors = g_list_copy (src->spell_errors);
  153. cur = dest->spell_errors;
  154. while (cur) {
  155. SpellError *se = (SpellError *) cur->data;
  156. cur->data = spell_error_new (se->off, se->len);
  157. cur = cur->next;
  158. }
  159. dest->links = g_slist_copy (src->links);
  160. for (csl = dest->links; csl; csl = csl->next)
  161. csl->data = html_link_dup ((Link *) csl->data);
  162. dest->pi = NULL;
  163. dest->direction = src->direction;
  164. }
  165. /* static void
  166. debug_word_width (HTMLText *t)
  167. {
  168. guint i;
  169. *
  170. printf ("words: %d | ", t->words);
  171. for (i = 0; i < t->words; i++)
  172. printf ("%d ", t->word_width [i]);
  173. printf ("\n");
  174. }
  175. *
  176. static void
  177. word_get_position (HTMLText *text,
  178. * guint off,
  179. * guint *word_out,
  180. * guint *left_out,
  181. * guint *right_out)
  182. {
  183. const gchar *s, *ls;
  184. guint coff, loff;
  185. *
  186. coff = 0;
  187. *word_out = 0;
  188. s = text->text;
  189. do {
  190. ls = s;
  191. loff = coff;
  192. s = strchr (s, ' ');
  193. coff += s ? g_utf8_pointer_to_offset (ls, s) : g_utf8_strlen (ls, -1);
  194. (*word_out) ++;
  195. if (s)
  196. s++;
  197. } while (s && coff < off);
  198. *
  199. *left_out = off - loff;
  200. *right_out = coff - off;
  201. *
  202. printf ("get position w: %d l: %d r: %d\n", *word_out, *left_out, *right_out);
  203. } */
  204. static gboolean
  205. cut_attr_list_filter (PangoAttribute *attr,
  206. gpointer data)
  207. {
  208. PangoAttribute *range = (PangoAttribute *) data;
  209. gint delta;
  210. if (attr->start_index >= range->start_index && attr->end_index <= range->end_index)
  211. return TRUE;
  212. delta = range->end_index - range->start_index;
  213. if (attr->start_index > range->end_index) {
  214. attr->start_index -= delta;
  215. attr->end_index -= delta;
  216. } else if (attr->start_index > range->start_index) {
  217. attr->start_index = range->start_index;
  218. attr->end_index -= delta;
  219. if (attr->end_index <= attr->start_index)
  220. return TRUE;
  221. } else if (attr->end_index >= range->end_index)
  222. attr->end_index -= delta;
  223. else if (attr->end_index >= range->start_index)
  224. attr->end_index = range->start_index;
  225. return FALSE;
  226. }
  227. static void
  228. cut_attr_list_list (PangoAttrList *attr_list,
  229. gint begin_index,
  230. gint end_index)
  231. {
  232. PangoAttrList *removed;
  233. PangoAttribute range;
  234. range.start_index = begin_index;
  235. range.end_index = end_index;
  236. removed = pango_attr_list_filter (attr_list, cut_attr_list_filter, &range);
  237. if (removed)
  238. pango_attr_list_unref (removed);
  239. }
  240. static void
  241. cut_attr_list (HTMLText *text,
  242. gint begin_index,
  243. gint end_index)
  244. {
  245. cut_attr_list_list (text->attr_list, begin_index, end_index);
  246. if (text->extra_attr_list)
  247. cut_attr_list_list (text->extra_attr_list, begin_index, end_index);
  248. }
  249. static void
  250. cut_links_full (HTMLText *text,
  251. gint start_offset,
  252. gint end_offset,
  253. gint start_index,
  254. gint end_index,
  255. gint shift_offset,
  256. gint shift_index)
  257. {
  258. GSList *l, *next;
  259. Link *link;
  260. for (l = text->links; l; l = next) {
  261. next = l->next;
  262. link = (Link *) l->data;
  263. if (start_offset <= link->start_offset && link->end_offset <= end_offset) {
  264. html_link_free (link);
  265. text->links = g_slist_delete_link (text->links, l);
  266. } else if (end_offset <= link->start_offset) {
  267. link->start_offset -= shift_offset;
  268. link->start_index -= shift_index;
  269. link->end_offset -= shift_offset;
  270. link->end_index -= shift_index;
  271. } else if (start_offset <= link->start_offset) {
  272. link->start_offset = end_offset - shift_offset;
  273. link->end_offset -= shift_offset;
  274. link->start_index = end_index - shift_index;
  275. link->end_index -= shift_index;
  276. } else if (end_offset <= link->end_offset) {
  277. if (shift_offset > 0) {
  278. link->end_offset -= shift_offset;
  279. link->end_index -= shift_index;
  280. } else {
  281. if (link->end_offset == end_offset) {
  282. link->end_offset = start_offset;
  283. link->end_index = start_index;
  284. } else if (link->start_offset == start_offset) {
  285. link->start_offset = end_offset;
  286. link->start_index = end_index;
  287. } else {
  288. Link *dup = html_link_dup (link);
  289. link->start_offset = end_offset;
  290. link->start_index = end_index;
  291. dup->end_offset = start_offset;
  292. dup->end_index = start_index;
  293. l = g_slist_prepend (l, dup);
  294. next = l->next->next;
  295. }
  296. }
  297. } else if (start_offset < link->end_offset) {
  298. link->end_offset = start_offset;
  299. link->end_index = start_index;
  300. }
  301. }
  302. }
  303. static void
  304. cut_links (HTMLText *text,
  305. gint start_offset,
  306. gint end_offset,
  307. gint start_index,
  308. gint end_index)
  309. {
  310. cut_links_full (text, start_offset, end_offset, start_index, end_index, end_offset - start_offset, end_index - start_index);
  311. }
  312. HTMLObject *
  313. html_text_op_copy_helper (HTMLText *text,
  314. GList *from,
  315. GList *to,
  316. guint *len)
  317. {
  318. HTMLObject *rv;
  319. HTMLText *rvt;
  320. gchar *tail, *nt;
  321. gint begin, end, begin_index, end_index;
  322. begin = (from) ? GPOINTER_TO_INT (from->data) : 0;
  323. end = (to) ? GPOINTER_TO_INT (to->data) : text->text_len;
  324. tail = html_text_get_text (text, end);
  325. begin_index = html_text_get_index (text, begin);
  326. end_index = tail - text->text;
  327. *len += end - begin;
  328. rv = html_object_dup (HTML_OBJECT (text));
  329. rvt = HTML_TEXT (rv);
  330. rvt->text_len = end - begin;
  331. rvt->text_bytes = end_index - begin_index;
  332. nt = g_strndup (rvt->text + begin_index, rvt->text_bytes);
  333. g_free (rvt->text);
  334. rvt->text = nt;
  335. rvt->spell_errors = remove_spell_errors (rvt->spell_errors, 0, begin);
  336. rvt->spell_errors = remove_spell_errors (rvt->spell_errors, end, text->text_len - end);
  337. if (end_index < text->text_bytes)
  338. cut_attr_list (rvt, end_index, text->text_bytes);
  339. if (begin_index > 0)
  340. cut_attr_list (rvt, 0, begin_index);
  341. if (end < text->text_len)
  342. cut_links (rvt, end, text->text_len, end_index, text->text_bytes);
  343. if (begin > 0)
  344. cut_links (rvt, 0, begin, 0, begin_index);
  345. return rv;
  346. }
  347. HTMLObject *
  348. html_text_op_cut_helper (HTMLText *text,
  349. HTMLEngine *e,
  350. GList *from,
  351. GList *to,
  352. GList *left,
  353. GList *right,
  354. guint *len)
  355. {
  356. HTMLObject *rv;
  357. HTMLText *rvt;
  358. gint begin, end;
  359. begin = (from) ? GPOINTER_TO_INT (from->data) : 0;
  360. end = (to) ? GPOINTER_TO_INT (to->data) : text->text_len;
  361. g_assert (begin <= end);
  362. g_assert (end <= text->text_len);
  363. /* printf ("before cut '%s'\n", text->text);
  364. * debug_word_width (text); */
  365. remove_text_slaves (HTML_OBJECT (text));
  366. if (!html_object_could_remove_whole (HTML_OBJECT (text), from, to, left, right) || begin || end < text->text_len) {
  367. gchar *nt, *tail;
  368. gint begin_index, end_index;
  369. if (begin == end)
  370. return HTML_OBJECT (html_text_new_with_len ("", 0, text->font_style, text->color));
  371. rv = html_object_dup (HTML_OBJECT (text));
  372. rvt = HTML_TEXT (rv);
  373. tail = html_text_get_text (text, end);
  374. begin_index = html_text_get_index (text, begin);
  375. end_index = tail - text->text;
  376. text->text_bytes -= tail - (text->text + begin_index);
  377. text->text[begin_index] = 0;
  378. cut_attr_list (text, begin_index, end_index);
  379. if (end_index < rvt->text_bytes)
  380. cut_attr_list (rvt, end_index, rvt->text_bytes);
  381. if (begin_index > 0)
  382. cut_attr_list (rvt, 0, begin_index);
  383. cut_links (text, begin, end, begin_index, end_index);
  384. if (end < rvt->text_len)
  385. cut_links (rvt, end, rvt->text_len, end_index, rvt->text_bytes);
  386. if (begin > 0)
  387. cut_links (rvt, 0, begin, 0, begin_index);
  388. nt = g_strconcat (text->text, tail, NULL);
  389. g_free (text->text);
  390. rvt->spell_errors = remove_spell_errors (rvt->spell_errors, 0, begin);
  391. rvt->spell_errors = remove_spell_errors (rvt->spell_errors, end, text->text_len - end);
  392. move_spell_errors (rvt->spell_errors, begin, -begin);
  393. text->text = nt;
  394. text->text_len -= end - begin;
  395. *len += end - begin;
  396. nt = g_strndup (rvt->text + begin_index, end_index - begin_index);
  397. g_free (rvt->text);
  398. rvt->text = nt;
  399. rvt->text_len = end - begin;
  400. rvt->text_bytes = end_index - begin_index;
  401. text->spell_errors = remove_spell_errors (text->spell_errors, begin, end - begin);
  402. move_spell_errors (text->spell_errors, end, - (end - begin));
  403. html_text_convert_nbsp (text, TRUE);
  404. html_text_convert_nbsp (rvt, TRUE);
  405. pango_info_destroy (text);
  406. } else {
  407. text->spell_errors = remove_spell_errors (text->spell_errors, 0, text->text_len);
  408. html_object_move_cursor_before_remove (HTML_OBJECT (text), e);
  409. html_object_change_set (HTML_OBJECT (text)->parent, HTML_CHANGE_ALL_CALC);
  410. /* force parent redraw */
  411. HTML_OBJECT (text)->parent->width = 0;
  412. html_object_remove_child (HTML_OBJECT (text)->parent, HTML_OBJECT (text));
  413. rv = HTML_OBJECT (text);
  414. *len += text->text_len;
  415. }
  416. html_object_change_set (HTML_OBJECT (text), HTML_CHANGE_ALL_CALC);
  417. /* printf ("after cut '%s'\n", text->text);
  418. * debug_word_width (text); */
  419. return rv;
  420. }
  421. static HTMLObject *
  422. op_copy (HTMLObject *self,
  423. HTMLObject *parent,
  424. HTMLEngine *e,
  425. GList *from,
  426. GList *to,
  427. guint *len)
  428. {
  429. return html_text_op_copy_helper (HTML_TEXT (self), from, to, len);
  430. }
  431. static HTMLObject *
  432. op_cut (HTMLObject *self,
  433. HTMLEngine *e,
  434. GList *from,
  435. GList *to,
  436. GList *left,
  437. GList *right,
  438. guint *len)
  439. {
  440. return html_text_op_cut_helper (HTML_TEXT (self), e, from, to, left, right, len);
  441. }
  442. static void
  443. merge_links (HTMLText *t1,
  444. HTMLText *t2)
  445. {
  446. Link *tail, *head;
  447. GSList *l;
  448. if (t2->links) {
  449. for (l = t2->links; l; l = l->next) {
  450. Link *link = (Link *) l->data;
  451. link->start_offset += t1->text_len;
  452. link->start_index += t1->text_bytes;
  453. link->end_offset += t1->text_len;
  454. link->end_index += t1->text_bytes;
  455. }
  456. if (t1->links) {
  457. head = (Link *) t1->links->data;
  458. tail = (Link *) g_slist_last (t2->links)->data;
  459. if (tail->start_offset == head->end_offset && html_link_equal (head, tail)) {
  460. tail->start_offset = head->start_offset;
  461. tail->start_index = head->start_index;
  462. html_link_free (head);
  463. t1->links = g_slist_delete_link (t1->links, t1->links);
  464. }
  465. }
  466. t1->links = g_slist_concat (t2->links, t1->links);
  467. t2->links = NULL;
  468. }
  469. }
  470. static gboolean
  471. object_merge (HTMLObject *self,
  472. HTMLObject *with,
  473. HTMLEngine *e,
  474. GList **left,
  475. GList **right,
  476. HTMLCursor *cursor)
  477. {
  478. HTMLText *t1, *t2;
  479. gchar *to_free;
  480. t1 = HTML_TEXT (self);
  481. t2 = HTML_TEXT (with);
  482. /* printf ("merge '%s' '%s'\n", t1->text, t2->text); */
  483. /* merge_word_width (t1, t2, e->painter); */
  484. if (e->cursor->object == with) {
  485. e->cursor->object = self;
  486. e->cursor->offset += t1->text_len;
  487. }
  488. /* printf ("--- before merge\n");
  489. * debug_spell_errors (t1->spell_errors);
  490. * printf ("---\n");
  491. * debug_spell_errors (t2->spell_errors);
  492. * printf ("---\n");
  493. */
  494. move_spell_errors (t2->spell_errors, 0, t1->text_len);
  495. t1->spell_errors = merge_spell_errors (t1->spell_errors, t2->spell_errors);
  496. t2->spell_errors = NULL;
  497. pango_attr_list_splice (t1->attr_list, t2->attr_list, t1->text_bytes, t2->text_bytes);
  498. if (t2->extra_attr_list) {
  499. if (!t1->extra_attr_list)
  500. t1->extra_attr_list = pango_attr_list_new ();
  501. pango_attr_list_splice (t1->extra_attr_list, t2->extra_attr_list, t1->text_bytes, t2->text_bytes);
  502. }
  503. merge_links (t1, t2);
  504. to_free = t1->text;
  505. t1->text = g_strconcat (t1->text, t2->text, NULL);
  506. t1->text_len += t2->text_len;
  507. t1->text_bytes += t2->text_bytes;
  508. g_free (to_free);
  509. html_text_convert_nbsp (t1, TRUE);
  510. html_object_change_set (self, HTML_CHANGE_ALL_CALC);
  511. pango_info_destroy (t1);
  512. pango_info_destroy (t2);
  513. /* html_text_request_word_width (t1, e->painter); */
  514. /* printf ("merged '%s'\n", t1->text);
  515. * printf ("--- after merge\n");
  516. * debug_spell_errors (t1->spell_errors);
  517. * printf ("---\n"); */
  518. return TRUE;
  519. }
  520. static gboolean
  521. split_attrs_filter_head (PangoAttribute *attr,
  522. gpointer data)
  523. {
  524. gint index = GPOINTER_TO_INT (data);
  525. if (attr->start_index >= index)
  526. return TRUE;
  527. else if (attr->end_index > index)
  528. attr->end_index = index;
  529. return FALSE;
  530. }
  531. static gboolean
  532. split_attrs_filter_tail (PangoAttribute *attr,
  533. gpointer data)
  534. {
  535. gint index = GPOINTER_TO_INT (data);
  536. if (attr->end_index <= index)
  537. return TRUE;
  538. if (attr->start_index > index)
  539. attr->start_index -= index;
  540. else
  541. attr->start_index = 0;
  542. attr->end_index -= index;
  543. return FALSE;
  544. }
  545. static void
  546. split_attrs (HTMLText *t1,
  547. HTMLText *t2,
  548. gint index)
  549. {
  550. PangoAttrList *delete;
  551. delete = pango_attr_list_filter (t1->attr_list, split_attrs_filter_head, GINT_TO_POINTER (index));
  552. if (delete)
  553. pango_attr_list_unref (delete);
  554. if (t1->extra_attr_list) {
  555. delete = pango_attr_list_filter (t1->extra_attr_list, split_attrs_filter_head, GINT_TO_POINTER (index));
  556. if (delete)
  557. pango_attr_list_unref (delete);
  558. }
  559. delete = pango_attr_list_filter (t2->attr_list, split_attrs_filter_tail, GINT_TO_POINTER (index));
  560. if (delete)
  561. pango_attr_list_unref (delete);
  562. if (t2->extra_attr_list) {
  563. delete = pango_attr_list_filter (t2->extra_attr_list, split_attrs_filter_tail, GINT_TO_POINTER (index));
  564. if (delete)
  565. pango_attr_list_unref (delete);
  566. }
  567. }
  568. static void
  569. split_links (HTMLText *t1,
  570. HTMLText *t2,
  571. gint offset,
  572. gint index)
  573. {
  574. GSList *l, *prev = NULL;
  575. for (l = t1->links; l; l = l->next) {
  576. Link *link = (Link *) l->data;
  577. if (link->start_offset < offset) {
  578. if (link->end_offset > offset) {
  579. link->end_offset = offset;
  580. link->end_index = index;
  581. }
  582. if (prev) {
  583. prev->next = NULL;
  584. free_links (t1->links);
  585. }
  586. t1->links = l;
  587. break;
  588. }
  589. prev = l;
  590. if (!l->next) {
  591. free_links (t1->links);
  592. t1->links = NULL;
  593. break;
  594. }
  595. }
  596. prev = NULL;
  597. for (l = t2->links; l; l = l->next) {
  598. Link *link = (Link *) l->data;
  599. if (link->start_offset < offset) {
  600. if (link->end_offset > offset) {
  601. link->start_offset = offset;
  602. link->start_index = index;
  603. prev = l;
  604. l = l->next;
  605. }
  606. if (prev) {
  607. prev->next = NULL;
  608. free_links (l);
  609. } else {
  610. free_links (t2->links);
  611. t2->links = NULL;
  612. }
  613. break;
  614. }
  615. prev = l;
  616. }
  617. for (l = t2->links; l; l = l->next) {
  618. Link *link = (Link *) l->data;
  619. link->start_offset -= offset;
  620. link->start_index -= index;
  621. link->end_offset -= offset;
  622. link->end_index -= index;
  623. }
  624. }
  625. static void
  626. object_split (HTMLObject *self,
  627. HTMLEngine *e,
  628. HTMLObject *child,
  629. gint offset,
  630. gint level,
  631. GList **left,
  632. GList **right)
  633. {
  634. HTMLObject *dup, *prev;
  635. HTMLText *t1, *t2;
  636. gchar *tt;
  637. gint split_index;
  638. g_assert (self->parent);
  639. html_clue_remove_text_slaves (HTML_CLUE (self->parent));
  640. t1 = HTML_TEXT (self);
  641. dup = html_object_dup (self);
  642. tt = t1->text;
  643. split_index = html_text_get_index (t1, offset);
  644. t1->text = g_strndup (tt, split_index);
  645. t1->text_len = offset;
  646. t1->text_bytes = split_index;
  647. g_free (tt);
  648. html_text_convert_nbsp (t1, TRUE);
  649. t2 = HTML_TEXT (dup);
  650. tt = t2->text;
  651. t2->text = html_text_get_text (t2, offset);
  652. t2->text_len -= offset;
  653. t2->text_bytes -= split_index;
  654. split_attrs (t1, t2, split_index);
  655. split_links (t1, t2, offset, split_index);
  656. if (!html_text_convert_nbsp (t2, FALSE))
  657. t2->text = g_strdup (t2->text);
  658. g_free (tt);
  659. html_clue_append_after (HTML_CLUE (self->parent), dup, self);
  660. prev = self->prev;
  661. if (t1->text_len == 0 && prev && html_object_merge (prev, self, e, NULL, NULL, NULL))
  662. self = prev;
  663. if (t2->text_len == 0 && dup->next)
  664. html_object_merge (dup, dup->next, e, NULL, NULL, NULL);
  665. /* printf ("--- before split offset %d dup len %d\n", offset, HTML_TEXT (dup)->text_len);
  666. * debug_spell_errors (HTML_TEXT (self)->spell_errors); */
  667. HTML_TEXT (self)->spell_errors = remove_spell_errors (HTML_TEXT (self)->spell_errors,
  668. offset, HTML_TEXT (dup)->text_len);
  669. HTML_TEXT (dup)->spell_errors = remove_spell_errors (HTML_TEXT (dup)->spell_errors,
  670. 0, HTML_TEXT (self)->text_len);
  671. move_spell_errors (HTML_TEXT (dup)->spell_errors, 0, - HTML_TEXT (self)->text_len);
  672. /* printf ("--- after split\n");
  673. * printf ("left\n");
  674. * debug_spell_errors (HTML_TEXT (self)->spell_errors);
  675. * printf ("right\n");
  676. * debug_spell_errors (HTML_TEXT (dup)->spell_errors);
  677. * printf ("---\n");
  678. */
  679. *left = g_list_prepend (*left, self);
  680. *right = g_list_prepend (*right, dup);
  681. html_object_change_set (self, HTML_CHANGE_ALL_CALC);
  682. html_object_change_set (dup, HTML_CHANGE_ALL_CALC);
  683. pango_info_destroy (HTML_TEXT (self));
  684. level--;
  685. if (level)
  686. html_object_split (self->parent, e, dup, 0, level, left, right);
  687. }
  688. static gboolean
  689. html_text_real_calc_size (HTMLObject *self,
  690. HTMLPainter *painter,
  691. GList **changed_objs)
  692. {
  693. self->width = 0;
  694. html_object_calc_preferred_width (self, painter);
  695. return FALSE;
  696. }
  697. static const gchar *
  698. html_utf8_strnchr (const gchar *s,
  699. gchar c,
  700. gint len,
  701. gint *offset)
  702. {
  703. const gchar *res = NULL;
  704. *offset = 0;
  705. while (s && *s && *offset < len) {
  706. if (*s == c) {
  707. res = s;
  708. break;
  709. }
  710. s = g_utf8_next_char (s);
  711. (*offset) ++;
  712. }
  713. return res;
  714. }
  715. gint
  716. html_text_text_line_length (const gchar *text,
  717. gint *line_offset,
  718. guint len,
  719. gint *tabs)
  720. {
  721. const gchar *tab, *found_tab;
  722. gint cl, l, skip, sum_skip;
  723. /* printf ("lo: %d len: %d t: '%s'\n", line_offset, len, text); */
  724. if (tabs)
  725. *tabs = 0;
  726. l = 0;
  727. sum_skip = skip = 0;
  728. tab = text;
  729. while (tab && (found_tab = html_utf8_strnchr (tab, '\t', len - l, &cl)) && l < len) {
  730. l += cl;
  731. if (l >= len)
  732. break;
  733. if (*line_offset != -1) {
  734. *line_offset += cl;
  735. skip = 8 - (*line_offset % 8);
  736. }
  737. tab = found_tab + 1;
  738. *line_offset += skip;
  739. if (*line_offset != -1)
  740. sum_skip += skip - 1;
  741. l++;
  742. if (tabs)
  743. (*tabs) ++;
  744. }
  745. if (*line_offset != -1)
  746. (*line_offset) += len - l;
  747. /* printf ("ll: %d\n", len + sum_skip); */
  748. return len + sum_skip;
  749. }
  750. static guint
  751. get_line_length (HTMLObject *self,
  752. HTMLPainter *p,
  753. gint line_offset)
  754. {
  755. return html_clueflow_tabs (HTML_CLUEFLOW (self->parent), p)
  756. ? html_text_text_line_length (HTML_TEXT (self)->text, &line_offset, HTML_TEXT (self)->text_len, NULL)
  757. : HTML_TEXT (self)->text_len;
  758. }
  759. gint
  760. html_text_get_line_offset (HTMLText *text,
  761. HTMLPainter *painter,
  762. gint offset)
  763. {
  764. gint line_offset = -1;
  765. if (html_clueflow_tabs (HTML_CLUEFLOW (HTML_OBJECT (text)->parent), painter)) {
  766. line_offset = html_clueflow_get_line_offset (HTML_CLUEFLOW (HTML_OBJECT (text)->parent),
  767. painter, HTML_OBJECT (text));
  768. if (offset) {
  769. gchar *s = text->text;
  770. while (offset > 0 && s && *s) {
  771. if (*s == '\t')
  772. line_offset += 8 - (line_offset % 8);
  773. else
  774. line_offset++;
  775. s = g_utf8_next_char (s);
  776. offset--;
  777. }
  778. }
  779. }
  780. return line_offset;
  781. }
  782. gint
  783. html_text_get_item_index (HTMLText *text,
  784. HTMLPainter *painter,
  785. gint offset,
  786. gint *item_offset)
  787. {
  788. HTMLTextPangoInfo *pi = html_text_get_pango_info (text, painter);
  789. gint idx = 0;
  790. if (pi->n > 0) {
  791. while (idx < pi->n - 1 && offset >= pi->entries[idx].glyph_item.item->num_chars) {
  792. offset -= pi->entries[idx].glyph_item.item->num_chars;
  793. idx++;
  794. }
  795. *item_offset = offset;
  796. }
  797. return idx;
  798. }
  799. static void
  800. update_asc_dsc (HTMLPainter *painter,
  801. PangoItem *item,
  802. gint *asc,
  803. gint *dsc)
  804. {
  805. PangoFontMetrics *pfm;
  806. pfm = pango_font_get_metrics (item->analysis.font, item->analysis.language);
  807. if (asc)
  808. *asc = MAX (*asc, pango_font_metrics_get_ascent (pfm));
  809. if (dsc)
  810. *dsc = MAX (*dsc, pango_font_metrics_get_descent (pfm));
  811. pango_font_metrics_unref (pfm);
  812. }
  813. static void
  814. html_text_get_attr_list_list (PangoAttrList *get_attrs,
  815. PangoAttrList *attr_list,
  816. gint start_index,
  817. gint end_index)
  818. {
  819. PangoAttrIterator *iter = pango_attr_list_get_iterator (attr_list);
  820. if (iter) {
  821. do {
  822. gint begin, end;
  823. pango_attr_iterator_range (iter, &begin, &end);
  824. if (MAX (begin, start_index) < MIN (end, end_index)) {
  825. GSList *c, *l = pango_attr_iterator_get_attrs (iter);
  826. for (c = l; c; c = c->next) {
  827. PangoAttribute *attr = (PangoAttribute *) c->data;
  828. if (attr->start_index < start_index)
  829. attr->start_index = 0;
  830. else
  831. attr->start_index -= start_index;
  832. if (attr->end_index > end_index)
  833. attr->end_index = end_index - start_index;
  834. else
  835. attr->end_index -= start_index;
  836. c->data = NULL;
  837. pango_attr_list_insert (get_attrs, attr);
  838. }
  839. g_slist_free (l);
  840. }
  841. } while (pango_attr_iterator_next (iter));
  842. pango_attr_iterator_destroy (iter);
  843. }
  844. }
  845. PangoAttrList *
  846. html_text_get_attr_list (HTMLText *text,
  847. gint start_index,
  848. gint end_index)
  849. {
  850. PangoAttrList *attrs = pango_attr_list_new ();
  851. html_text_get_attr_list_list (attrs, text->attr_list, start_index, end_index);
  852. if (text->extra_attr_list)
  853. html_text_get_attr_list_list (attrs, text->extra_attr_list, start_index, end_index);
  854. return attrs;
  855. }
  856. void
  857. html_text_calc_text_size (HTMLText *t,
  858. HTMLPainter *painter,
  859. gint start_byte_offset,
  860. guint len,
  861. HTMLTextPangoInfo *pi,
  862. GList *glyphs,
  863. gint *line_offset,
  864. gint *width,
  865. gint *asc,
  866. gint *dsc)
  867. {
  868. gchar *text = t->text + start_byte_offset;
  869. html_painter_calc_entries_size (painter, text, len, pi, glyphs,
  870. line_offset, width, asc, dsc);
  871. }
  872. gint
  873. html_text_calc_part_width (HTMLText *text,
  874. HTMLPainter *painter,
  875. gchar *start,
  876. gint offset,
  877. gint len,
  878. gint *asc,
  879. gint *dsc)
  880. {
  881. gint idx, width = 0, line_offset;
  882. gint ascent = 0, descent = 0; /* Quiet GCC */
  883. gboolean need_ascent_descent = asc || dsc;
  884. HTMLTextPangoInfo *pi;
  885. PangoLanguage *language = NULL;
  886. PangoFont *font = NULL;
  887. gchar *s;
  888. if (offset < 0)
  889. return 0;
  890. if (offset + len > text->text_len)
  891. return 0;
  892. if (need_ascent_descent) {
  893. ascent = html_painter_engine_to_pango (painter,
  894. html_painter_get_space_asc (painter, html_text_get_font_style (text), text->face));
  895. descent = html_painter_engine_to_pango (painter,
  896. html_painter_get_space_dsc (painter, html_text_get_font_style (text), text->face));
  897. }
  898. if (text->text_len == 0 || len == 0)
  899. goto out;
  900. line_offset = html_text_get_line_offset (text, painter, offset);
  901. if (start == NULL)
  902. start = html_text_get_text (text, offset);
  903. s = start;
  904. pi = html_text_get_pango_info (text, painter);
  905. idx = html_text_get_item_index (text, painter, offset, &offset);
  906. if (need_ascent_descent) {
  907. update_asc_dsc (painter, pi->entries[idx].glyph_item.item, &ascent, &descent);
  908. font = pi->entries[idx].glyph_item.item->analysis.font;
  909. language = pi->entries[idx].glyph_item.item->analysis.language;
  910. }
  911. while (len > 0) {
  912. gint old_idx;
  913. if (*s == '\t') {
  914. gint skip = 8 - (line_offset % 8);
  915. width += skip * pi->entries[idx].widths[offset];
  916. line_offset += skip;
  917. } else {
  918. width += pi->entries[idx].widths[offset];
  919. line_offset++;
  920. }
  921. len--;
  922. old_idx = idx;
  923. if (html_text_pi_forward (pi, &idx, &offset) && idx != old_idx)
  924. if (len > 0 && (need_ascent_descent) && (pi->entries[idx].glyph_item.item->analysis.font != font
  925. || pi->entries[idx].glyph_item.item->analysis.language != language)) {
  926. update_asc_dsc (painter, pi->entries[idx].glyph_item.item, &ascent, &descent);
  927. }
  928. s = g_utf8_next_char (s);
  929. }
  930. out:
  931. if (asc)
  932. *asc = html_painter_pango_to_engine (painter, ascent);
  933. if (dsc)
  934. *dsc = html_painter_pango_to_engine (painter, descent);
  935. return html_painter_pango_to_engine (painter, width);
  936. }
  937. static gint
  938. calc_preferred_width (HTMLObject *self,
  939. HTMLPainter *painter)
  940. {
  941. HTMLText *text;
  942. gint width;
  943. text = HTML_TEXT (self);
  944. width = html_text_calc_part_width (text, painter, text->text, 0, text->text_len, &self->ascent, &self->descent);
  945. self->y = self->ascent;
  946. if (html_clueflow_tabs (HTML_CLUEFLOW (self->parent), painter)) {
  947. gint line_offset;
  948. gint tabs;
  949. line_offset = html_text_get_line_offset (text, painter, 0);
  950. width += (html_text_text_line_length (text->text, &line_offset, text->text_len, &tabs) - text->text_len)*
  951. html_painter_get_space_width (painter, html_text_get_font_style (text), text->face);
  952. }
  953. return MAX (1, width);
  954. }
  955. static void
  956. remove_text_slaves (HTMLObject *self)
  957. {
  958. HTMLObject *next_obj;
  959. /* Remove existing slaves */
  960. next_obj = self->next;
  961. while (next_obj != NULL
  962. && (HTML_OBJECT_TYPE (next_obj) == HTML_TYPE_TEXTSLAVE)) {
  963. self->next = next_obj->next;
  964. html_clue_remove (HTML_CLUE (next_obj->parent), next_obj);
  965. html_object_destroy (next_obj);
  966. next_obj = self->next;
  967. }
  968. }
  969. static HTMLFitType
  970. ht_fit_line (HTMLObject *o,
  971. HTMLPainter *painter,
  972. gboolean startOfLine,
  973. gboolean firstRun,
  974. gboolean next_to_floating,
  975. gint widthLeft)
  976. {
  977. HTMLText *text;
  978. HTMLObject *text_slave;
  979. text = HTML_TEXT (o);
  980. remove_text_slaves (o);
  981. /* Turn all text over to our slaves */
  982. text_slave = html_text_slave_new (text, 0, HTML_TEXT (text)->text_len);
  983. html_clue_append_after (HTML_CLUE (o->parent), text_slave, o);
  984. return HTML_FIT_COMPLETE;
  985. }
  986. #if 0 /* No longer used? */
  987. static gint
  988. min_word_width_calc_tabs (HTMLText *text,
  989. HTMLPainter *p,
  990. gint idx,
  991. gint *len)
  992. {
  993. gchar *str, *end;
  994. gint rv = 0, line_offset, wt, wl, i;
  995. gint epos;
  996. gboolean tab = FALSE;
  997. if (!html_clueflow_tabs (HTML_CLUEFLOW (HTML_OBJECT (text)->parent), p))
  998. return 0;
  999. /* printf ("tabs %d\n", idx); */
  1000. str = text->text;
  1001. i = idx;
  1002. while (i > 0 && *str) {
  1003. if (*str == ' ')
  1004. i--;
  1005. str = g_utf8_next_char (str);
  1006. }
  1007. if (!*str)
  1008. return 0;
  1009. epos = 0;
  1010. end = str;
  1011. while (*end && *end != ' ') {
  1012. tab |= *end == '\t';
  1013. end = g_utf8_next_char (end);
  1014. epos++;
  1015. }
  1016. if (tab) {
  1017. line_offset = 0;
  1018. if (idx == 0) {
  1019. HTMLObject *prev;
  1020. prev = html_object_prev_not_slave (HTML_OBJECT (text));
  1021. if (prev && html_object_is_text (prev) /* FIXME-words && HTML_TEXT (prev)->words > 0 */) {
  1022. min_word_width_calc_tabs (HTML_TEXT (prev), p, /* FIXME-words HTML_TEXT (prev)->words - 1 */ HTML_TEXT (prev)->text_len - 1, &line_offset);
  1023. /* printf ("lo: %d\n", line_offset); */
  1024. }
  1025. }
  1026. wl = html_text_text_line_length (str, &line_offset, epos, &wt);
  1027. } else {
  1028. wl = epos;
  1029. }
  1030. rv = wl - epos;
  1031. if (len)
  1032. *len = wl;
  1033. /* printf ("tabs delta %d\n", rv); */
  1034. return rv;
  1035. }
  1036. #endif
  1037. gint
  1038. html_text_pango_info_get_index (HTMLTextPangoInfo *pi,
  1039. gint byte_offset,
  1040. gint idx)
  1041. {
  1042. while (idx < pi->n && pi->entries[idx].glyph_item.item->offset + pi->entries[idx].glyph_item.item->length <= byte_offset)
  1043. idx++;
  1044. return idx;
  1045. }
  1046. static void
  1047. html_text_add_cite_color (PangoAttrList *attrs,
  1048. HTMLText *text,
  1049. HTMLClueFlow *flow,
  1050. HTMLEngine *e)
  1051. {
  1052. HTMLColor *cite_color = html_colorset_get_color (e->settings->color_set, HTMLCiteColor);
  1053. if (cite_color && flow->levels->len > 0 && flow->levels->data[0] == HTML_LIST_TYPE_BLOCKQUOTE_CITE) {
  1054. PangoAttribute *attr;
  1055. attr = pango_attr_foreground_new (cite_color->color.red, cite_color->color.green, cite_color->color.blue);
  1056. attr->start_index = 0;
  1057. attr->end_index = text->text_bytes;
  1058. pango_attr_list_change (attrs, attr);
  1059. }
  1060. }
  1061. void
  1062. html_text_remove_unwanted_line_breaks (gchar *s,
  1063. gint len,
  1064. PangoLogAttr *attrs)
  1065. {
  1066. gint i;
  1067. gunichar last_uc = 0;
  1068. for (i = 0; i < len; i++) {
  1069. gunichar uc = g_utf8_get_char (s);
  1070. if (attrs[i].is_line_break) {
  1071. if (last_uc == '.' || last_uc == '/' ||
  1072. last_uc == '-' || last_uc == '$' ||
  1073. last_uc == '+' || last_uc == '?' ||
  1074. last_uc == ')' ||
  1075. last_uc == '}' ||
  1076. last_uc == ']' ||
  1077. last_uc == '>')
  1078. attrs[i].is_line_break = 0;
  1079. else if ((uc == '(' ||
  1080. uc == '{' ||
  1081. uc == '[' ||
  1082. uc == '<'
  1083. )
  1084. && i > 0 && !attrs[i - 1].is_white)
  1085. attrs[i].is_line_break = 0;
  1086. }
  1087. s = g_utf8_next_char (s);
  1088. last_uc = uc;
  1089. }
  1090. }
  1091. PangoAttrList *
  1092. html_text_prepare_attrs (HTMLText *text,
  1093. HTMLPainter *painter)
  1094. {
  1095. PangoAttrList *attrs;
  1096. HTMLClueFlow *flow = NULL;
  1097. HTMLEngine *e = NULL;
  1098. PangoAttribute *attr;
  1099. attrs = pango_attr_list_new ();
  1100. if (HTML_OBJECT (text)->parent && HTML_IS_CLUEFLOW (HTML_OBJECT (text)->parent))
  1101. flow = HTML_CLUEFLOW (HTML_OBJECT (text)->parent);
  1102. if (painter->widget && GTK_IS_HTML (painter->widget))
  1103. e = html_object_engine (HTML_OBJECT (text), GTK_HTML (painter->widget)->engine);
  1104. if (flow && e) {
  1105. html_text_add_cite_color (attrs, text, flow, e);
  1106. }
  1107. if (HTML_IS_PLAIN_PAINTER (painter)) {
  1108. attr = pango_attr_family_new (painter->font_manager.fixed.face ? painter->font_manager.fixed.face : "Monospace");
  1109. attr->start_index = 0;
  1110. attr->end_index = text->text_bytes;
  1111. pango_attr_list_insert (attrs, attr);
  1112. if (painter->font_manager.fix_size != painter->font_manager.var_size || fabs (painter->font_manager.magnification - 1.0) > 0.001) {
  1113. attr = pango_attr_size_new (painter->font_manager.fix_size * painter->font_manager.magnification);
  1114. attr->start_index = 0;
  1115. attr->end_index = text->text_bytes;
  1116. pango_attr_list_insert (attrs, attr);
  1117. }
  1118. } else {
  1119. if (fabs (painter->font_manager.magnification - 1.0) > 0.001) {
  1120. attr = pango_attr_size_new (painter->font_manager.var_size * painter->font_manager.magnification);
  1121. attr->start_index = 0;
  1122. attr->end_index = text->text_bytes;
  1123. pango_attr_list_insert (attrs, attr);
  1124. }
  1125. pango_attr_list_splice (attrs, text->attr_list, 0, 0);
  1126. }
  1127. if (text->extra_attr_list)
  1128. pango_attr_list_splice (attrs, text->extra_attr_list, 0, 0);
  1129. if (!HTML_IS_PLAIN_PAINTER (painter)) {
  1130. if (flow && e)
  1131. html_text_change_attrs (attrs, html_clueflow_get_default_font_style (flow), e, 0, text->text_bytes, TRUE);
  1132. }
  1133. if (text->links && e) {
  1134. HTMLColor *link_color;
  1135. GSList *l;
  1136. for (l = text->links; l; l = l->next) {
  1137. Link *link;
  1138. link = (Link *) l->data;
  1139. if (link->is_visited == FALSE)
  1140. link_color = html_colorset_get_color (e->settings->color_set, HTMLLinkColor);
  1141. else
  1142. link_color = html_colorset_get_color (e->settings->color_set, HTMLVLinkColor);
  1143. attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
  1144. attr->start_index = link->start_index;
  1145. attr->end_index = link->end_index;
  1146. pango_attr_list_change (attrs, attr);
  1147. attr = pango_attr_foreground_new (link_color->color.red, link_color->color.green, link_color->color.blue);
  1148. attr->start_index = link->start_index;
  1149. attr->end_index = link->end_index;
  1150. pango_attr_list_change (attrs, attr);
  1151. }
  1152. }
  1153. return attrs;
  1154. }
  1155. PangoDirection
  1156. html_text_get_pango_direction (HTMLText *text)
  1157. {
  1158. if (HTML_OBJECT (text)->change & HTML_CHANGE_RECALC_PI)
  1159. return pango_find_base_dir (text->text, text->text_bytes);
  1160. else
  1161. return text->direction;
  1162. }
  1163. static PangoDirection
  1164. get_pango_base_direction (HTMLText *text)
  1165. {
  1166. switch (html_object_get_direction (HTML_OBJECT (text))) {
  1167. case HTML_DIRECTION_RTL:
  1168. return PANGO_DIRECTION_RTL;
  1169. case HTML_DIRECTION_LTR:
  1170. return PANGO_DIRECTION_LTR;
  1171. case HTML_DIRECTION_DERIVED:
  1172. default:
  1173. if (text->text)
  1174. return html_text_get_pango_direction (text);
  1175. else
  1176. return HTML_DIRECTION_LTR;
  1177. }
  1178. }
  1179. /**
  1180. * pango_glyph_string_get_logical_widths:
  1181. * @glyphs: a #PangoGlyphString
  1182. * @text: the text corresponding to the glyphs
  1183. * @length: the length of @text, in bytes
  1184. * @embedding_level: the embedding level of the string
  1185. * @logical_widths: an array whose length is g_utf8_strlen (text, length)
  1186. * to be filled in with the resulting character widths.
  1187. *
  1188. * Given a #PangoGlyphString resulting from pango_shape() and the corresponding
  1189. * text, determine the screen width corresponding to each character. When
  1190. * multiple characters compose a single cluster, the width of the entire
  1191. * cluster is divided equally among the characters.
  1192. **/
  1193. void
  1194. html_tmp_fix_pango_glyph_string_get_logical_widths (PangoGlyphString *glyphs,
  1195. const gchar *text,
  1196. gint length,
  1197. gint embedding_level,
  1198. gint *logical_widths)
  1199. {
  1200. gint i, j;
  1201. gint last_cluster = 0;
  1202. gint width = 0;
  1203. gint last_cluster_width = 0;
  1204. const gchar *p = text; /* Points to start of current cluster */
  1205. /* printf ("html_tmp_fix_pango_glyph_string_get_logical_widths"); */
  1206. for (i = 0; i <= glyphs->num_glyphs; i++)
  1207. {
  1208. gint glyph_index = (embedding_level % 2 == 0) ? i : glyphs->num_glyphs - i - 1;
  1209. /* If this glyph belongs to a new cluster, or we're at the end, find
  1210. * the start of the next cluster, and assign the widths for this cluster.
  1211. */
  1212. if (i == glyphs->num_glyphs || p != text + glyphs->log_clusters[glyph_index])
  1213. {
  1214. gint next_cluster = last_cluster;
  1215. if (i < glyphs->num_glyphs)
  1216. {
  1217. while (p < text + glyphs->log_clusters[glyph_index])
  1218. {
  1219. next_cluster++;
  1220. p = g_utf8_next_char (p);
  1221. }
  1222. }
  1223. else
  1224. {
  1225. while (p < text + length)
  1226. {
  1227. next_cluster++;
  1228. p = g_utf8_next_char (p);
  1229. }
  1230. }
  1231. for (j = last_cluster; j < next_cluster; j++) {
  1232. logical_widths[j] = (width - last_cluster_width) / (next_cluster - last_cluster);
  1233. /* printf (" %d", logical_widths [j]); */
  1234. }
  1235. if (last_cluster != next_cluster) {
  1236. last_cluster = next_cluster;
  1237. last_cluster_width = width;
  1238. }
  1239. }
  1240. if (i < glyphs->num_glyphs)
  1241. width += glyphs->glyphs[glyph_index].geometry.width;
  1242. }
  1243. /* printf ("\n"); */
  1244. }
  1245. static void
  1246. html_text_shape_tab (HTMLText *text,
  1247. PangoGlyphString *glyphs)
  1248. {
  1249. /* copied from pango sources */
  1250. pango_glyph_string_set_size (glyphs, 1);
  1251. glyphs->glyphs[0].glyph = EMPTY_GLYPH;
  1252. glyphs->glyphs[0].geometry.x_offset = 0;
  1253. glyphs->glyphs[0].geometry.y_offset = 0;
  1254. glyphs->glyphs[0].attr.is_cluster_start = 1;
  1255. glyphs->log_clusters[0] = 0;
  1256. glyphs->glyphs[0].geometry.width = 48 * PANGO_SCALE;
  1257. }
  1258. HTMLTextPangoInfo *
  1259. html_text_get_pango_info (HTMLText *text,
  1260. HTMLPainter *painter)
  1261. {
  1262. if (HTML_OBJECT (text)->change & HTML_CHANGE_RECALC_PI) {
  1263. pango_info_destroy (text);
  1264. HTML_OBJECT (text)->change &= ~HTML_CHANGE_RECALC_PI;
  1265. text->direction = pango_find_base_dir (text->text, text->text_bytes);
  1266. }
  1267. if (!text->pi) {
  1268. GList *items, *cur;
  1269. PangoAttrList *attrs;
  1270. gint i, offset;
  1271. attrs = html_text_prepare_attrs (text, painter);
  1272. items = pango_itemize_with_base_dir (painter->pango_context, get_pango_base_direction (text), text->text, 0, text->text_bytes, attrs, NULL);
  1273. pango_attr_list_unref (attrs);
  1274. /* create pango info */
  1275. text->pi = html_text_pango_info_new (g_list_length (items));
  1276. text->pi->have_font = TRUE;
  1277. text->pi->font_style = html_text_get_font_style (text);
  1278. text->pi->face = g_strdup (text->face);
  1279. text->pi->attrs = g_new (PangoLogAttr, text->text_len + 1);
  1280. /* get line breaks */
  1281. offset = 0;
  1282. for (cur = items; cur; cur = cur->next) {
  1283. PangoItem tmp_item;
  1284. PangoItem *item;
  1285. gint start_offset;
  1286. start_offset = offset;
  1287. item = (PangoItem *) cur->data;
  1288. offset += item->num_chars;
  1289. tmp_item = *item;
  1290. while (cur->next) {
  1291. PangoItem *next_item = (PangoItem *) cur->next->data;
  1292. if (tmp_item.analysis.lang_engine == next_item->analysis.lang_engine) {
  1293. tmp_item.length += next_item->length;
  1294. tmp_item.num_chars += next_item->num_chars;
  1295. offset += next_item->num_chars;
  1296. cur = cur->next;
  1297. } else
  1298. break;
  1299. }
  1300. pango_break (text->text + tmp_item.offset, tmp_item.length, &tmp_item.analysis, text->pi->attrs + start_offset, tmp_item.num_chars + 1);
  1301. }
  1302. if (text->pi && text->pi->attrs)
  1303. html_text_remove_unwanted_line_breaks (text->text, text->text_len, text->pi->attrs);
  1304. for (i = 0, cur = items; i < text->pi->n; i++, cur = cur->next)
  1305. text->pi->entries[i].glyph_item.item = (PangoItem *) cur->data;
  1306. for (i = 0; i < text->pi->n; i++) {
  1307. PangoItem *item;
  1308. PangoGlyphString *glyphs;
  1309. item = text->pi->entries[i].glyph_item.item;
  1310. glyphs = text->pi->entries[i].glyph_item.glyphs = pango_glyph_string_new ();
  1311. /* printf ("item pos %d len %d\n", item->offset, item->length); */
  1312. text->pi->entries[i].widths = g_new (PangoGlyphUnit, item->num_chars);
  1313. if (text->text[item->offset] == '\t')
  1314. html_text_shape_tab (text, glyphs);
  1315. else
  1316. pango_shape (text->text + item->offset, item->length, &item->analysis, glyphs);
  1317. html_tmp_fix_pango_glyph_string_get_logical_widths (glyphs, text->text + item->offset, item->length,
  1318. item->analysis.level, text->pi->entries[i].widths);
  1319. }
  1320. g_list_free (items);
  1321. }
  1322. return text->pi;
  1323. }
  1324. gboolean
  1325. html_text_pi_backward (HTMLTextPangoInfo *pi,
  1326. gint *ii,
  1327. gint *io)
  1328. {
  1329. if (*io <= 0) {
  1330. if (*ii <= 0)
  1331. return FALSE;
  1332. (*ii) --;
  1333. *io = pi->entries [*ii].glyph_item.item->num_chars - 1;
  1334. } else
  1335. (*io) --;
  1336. return TRUE;
  1337. }
  1338. gboolean
  1339. html_text_pi_forward (HTMLTextPangoInfo *pi,
  1340. gint *ii,
  1341. gint *io)
  1342. {
  1343. if (*io >= pi->entries[*ii].glyph_item.item->num_chars - 1) {
  1344. if (*ii >= pi->n -1)
  1345. return FALSE;
  1346. (*ii) ++;
  1347. *io = 0;
  1348. } else
  1349. (*io) ++;
  1350. return TRUE;
  1351. }
  1352. /**
  1353. * html_text_tail_white_space:
  1354. * @text: a #HTMLText object
  1355. * @painter: a #HTMLPainter object
  1356. * @offset: offset into the text of @text, in characters
  1357. * @ii: index of current item
  1358. * @io: offset within current item, in characters
  1359. * @white_len: length of found trailing white space, in characters
  1360. * @line_offset:
  1361. * @s: pointer into the text of @text corresponding to @offset
  1362. *
  1363. * Used to chop off one character worth of whitespace at a particular position.
  1364. *
  1365. * Return value: width of found trailing white space, in Pango units
  1366. **/
  1367. gint
  1368. html_text_tail_white_space (HTMLText *text,
  1369. HTMLPainter *painter,
  1370. gint offset,
  1371. gint ii,
  1372. gint io,
  1373. gint *white_len,
  1374. gint line_offset,
  1375. gchar *s)
  1376. {
  1377. HTMLTextPangoInfo *pi = html_text_get_pango_info (text, painter);
  1378. gint wl = 0;
  1379. gint ww = 0;
  1380. if (html_text_pi_backward (pi, &ii, &io)) {
  1381. s = g_utf8_prev_char (s);
  1382. offset--;
  1383. if (pi->attrs[offset].is_white) {
  1384. if (*s == '\t' && offset > 1) {
  1385. gint skip = 8, co = offset - 1;
  1386. do {
  1387. s = g_utf8_prev_char (s);
  1388. co--;
  1389. if (*s != '\t')
  1390. skip--;
  1391. } while (s && co > 0 && *s != '\t');
  1392. ww += skip * pi->entries[ii].widths[io];
  1393. } else {
  1394. ww += pi->entries[ii].widths[io];
  1395. }
  1396. wl++;
  1397. }
  1398. }
  1399. if (white_len)
  1400. *white_len = wl;
  1401. return ww;
  1402. }
  1403. static void
  1404. update_mw (HTMLText *text,
  1405. HTMLPainter *painter,
  1406. gint offset,
  1407. gint *last_offset,
  1408. gint *ww,
  1409. gint *mw,
  1410. gint ii,
  1411. gint io,
  1412. gchar *s,
  1413. gint line_offset) {
  1414. *ww -= html_text_tail_white_space (text, painter, offset, ii, io, NULL, line_offset, s);
  1415. if (*ww > *mw)
  1416. *mw = *ww;
  1417. *ww = 0;
  1418. *last_offset = offset;
  1419. }
  1420. gboolean
  1421. html_text_is_line_break (PangoLogAttr attr)
  1422. {
  1423. return attr.is_line_break;
  1424. }
  1425. static gint
  1426. calc_min_width (HTMLObject *self,
  1427. HTMLPainter *painter)
  1428. {
  1429. HTMLText *text = HTML_TEXT (self);
  1430. HTMLTextPangoInfo *pi = html_text_get_pango_info (text, painter);
  1431. gint mw = 0, ww;
  1432. gint ii, io, offset, last_offset, line_offset;
  1433. gchar *s;
  1434. ww = 0;
  1435. last_offset = offset = 0;
  1436. ii = io = 0;
  1437. line_offset = html_text_get_line_offset (text, painter, 0);
  1438. s = text->text;
  1439. while (offset < text->text_len) {
  1440. if (offset > 0 && html_text_is_line_break (pi->attrs[offset]))
  1441. update_mw (text, painter, offset, &last_offset, &ww, &mw, ii, io, s, line_offset);
  1442. if (*s == '\t') {
  1443. gint skip = 8 - (line_offset % 8);
  1444. ww += skip * pi->entries[ii].widths[io];
  1445. line_offset += skip;
  1446. } else {
  1447. ww += pi->entries[ii].widths[io];
  1448. line_offset++;
  1449. }
  1450. s = g_utf8_next_char (s);
  1451. offset++;
  1452. html_text_pi_forward (pi, &ii, &io);
  1453. }
  1454. if (ww > mw)
  1455. mw = ww;
  1456. return MAX (1, html_painter_pango_to_engine (painter, mw));
  1457. }
  1458. static void
  1459. draw (HTMLObject *o,
  1460. HTMLPainter *p,
  1461. gint x,
  1462. gint y,
  1463. gint width,
  1464. gint height,
  1465. gint tx,
  1466. gint ty)
  1467. {
  1468. }
  1469. static gboolean
  1470. accepts_cursor (HTMLObject *object)
  1471. {
  1472. return TRUE;
  1473. }
  1474. static gboolean
  1475. save_open_attrs (HTMLEngineSaveState *state,
  1476. GSList *attrs)
  1477. {
  1478. gboolean rv = TRUE;
  1479. for (; attrs; attrs = attrs->next) {
  1480. PangoAttribute *attr = (PangoAttribute *) attrs->data;
  1481. HTMLEngine *e = state->engine;
  1482. const gchar *tag = NULL;
  1483. gboolean free_tag = FALSE;
  1484. switch (attr->klass->type) {
  1485. case PANGO_ATTR_WEIGHT:
  1486. tag = "<B>";
  1487. break;
  1488. case PANGO_ATTR_STYLE:
  1489. tag = "<I>";
  1490. break;
  1491. case PANGO_ATTR_UNDERLINE:
  1492. tag = "<U>";
  1493. break;
  1494. case PANGO_ATTR_STRIKETHROUGH:
  1495. tag = "<S>";
  1496. break;
  1497. case PANGO_ATTR_SIZE:
  1498. if (attr->klass == &html_pango_attr_font_size_klass) {
  1499. HTMLPangoAttrFontSize *size = (HTMLPangoAttrFontSize *) attr;
  1500. if ((size->style & GTK_HTML_FONT_STYLE_SIZE_MASK) != GTK_HTML_FONT_STYLE_SIZE_3 && (size->style & GTK_HTML_FONT_STYLE_SIZE_MASK) != 0) {
  1501. tag = g_strdup_printf ("<FONT SIZE=\"%d\">", size->style & GTK_HTML_FONT_STYLE_SIZE_MASK);
  1502. free_tag = TRUE;
  1503. }
  1504. }
  1505. break;
  1506. case PANGO_ATTR_FAMILY: {
  1507. PangoAttrString *family_attr = (PangoAttrString *) attr;
  1508. if (!g_ascii_strcasecmp (e->painter->font_manager.fixed.face
  1509. ? e->painter->font_manager.fixed.face : "Monospace",
  1510. family_attr->value))
  1511. tag = "<TT>";
  1512. }
  1513. break;
  1514. case PANGO_ATTR_FOREGROUND: {
  1515. PangoAttrColor *color = (PangoAttrColor *) attr;
  1516. tag = g_strdup_printf ("<FONT COLOR=\"#%02x%02x%02x\">",
  1517. (color->color.red >> 8) & 0xff, (color->color.green >> 8) & 0xff, (color->color.blue >> 8) & 0xff);
  1518. free_tag = TRUE;
  1519. }
  1520. break;
  1521. default:
  1522. break;
  1523. }
  1524. if (tag) {
  1525. if (!html_engine_save_output_string (state, "%s", tag))
  1526. rv = FALSE;
  1527. if (free_tag)
  1528. g_free ((gpointer) tag);
  1529. if (!rv)
  1530. break;
  1531. }
  1532. }
  1533. return TRUE;
  1534. }
  1535. static gboolean
  1536. save_close_attrs (HTMLEngineSaveState *state,
  1537. GSList *attrs)
  1538. {
  1539. for (; attrs; attrs = attrs->next) {
  1540. PangoAttribute *attr = (PangoAttribute *) attrs->data;
  1541. HTMLEngine *e = state->engine;
  1542. const gchar *tag = NULL;
  1543. switch (attr->klass->type) {
  1544. case PANGO_ATTR_WEIGHT:
  1545. tag = "</B>";
  1546. break;
  1547. case PANGO_ATTR_STYLE:
  1548. tag = "</I>";
  1549. break;
  1550. case PANGO_ATTR_UNDERLINE:
  1551. tag = "</U>";
  1552. break;
  1553. case PANGO_ATTR_STRIKETHROUGH:
  1554. tag = "</S>";
  1555. break;
  1556. case PANGO_ATTR_SIZE:
  1557. if (attr->klass == &html_pango_attr_font_size_klass) {
  1558. HTMLPangoAttrFontSize *size = (HTMLPangoAttrFontSize *) attr;
  1559. if ((size->style & GTK_HTML_FONT_STYLE_SIZE_MASK) != GTK_HTML_FONT_STYLE_SIZE_3 && (size->style & GTK_HTML_FONT_STYLE_SIZE_MASK) != 0)
  1560. tag = "</FONT>";
  1561. }
  1562. break;
  1563. case PANGO_ATTR_FOREGROUND:
  1564. tag = "</FONT>";
  1565. break;
  1566. case PANGO_ATTR_FAMILY: {
  1567. PangoAttrString *family_attr = (PangoAttrString *) attr;
  1568. if (!g_ascii_strcasecmp (e->painter->font_manager.fixed.face
  1569. ? e->painter->font_manager.fixed.face : "Monospace",
  1570. family_attr->value))
  1571. tag = "</TT>";
  1572. }
  1573. break;
  1574. default:
  1575. break;
  1576. }
  1577. if (tag)
  1578. if (!html_engine_save_output_string (state, "%s", tag))
  1579. return FALSE;
  1580. }
  1581. return TRUE;
  1582. }
  1583. static gboolean
  1584. save_text_part (HTMLText *text,
  1585. HTMLEngineSaveState *state,
  1586. guint start_index,
  1587. guint end_index)
  1588. {
  1589. gchar *str;
  1590. gint len;
  1591. gboolean rv;
  1592. str = g_strndup (text->text + start_index, end_index - start_index);
  1593. len = g_utf8_pointer_to_offset (text->text + start_index, text->text + end_index);
  1594. rv = html_engine_save_encode (state, str, len);
  1595. g_free (str);
  1596. return rv;
  1597. }
  1598. static gboolean
  1599. save_link_open (Link *link,
  1600. HTMLEngineSaveState *state)
  1601. {
  1602. return html_engine_save_delims_and_vals (state,
  1603. "<A HREF=\"", link->url,
  1604. "\">", NULL);
  1605. }
  1606. static gboolean
  1607. save_link_close (Link *link,
  1608. HTMLEngineSaveState *state)
  1609. {
  1610. return html_engine_save_output_string (state, "%s", "</A>");
  1611. }
  1612. static gboolean
  1613. save_text (HTMLText *text,
  1614. HTMLEngineSaveState *state,
  1615. guint start_index,
  1616. guint end_index,
  1617. GSList **l,
  1618. gboolean *link_started)
  1619. {
  1620. if (*l) {
  1621. Link *link;
  1622. link = (Link *) (*l)->data;
  1623. while (*l && ((!*link_started && start_index <= link->start_index && link->start_index < end_index)
  1624. || (*link_started && link->end_index <= end_index))) {
  1625. if (!*link_started && start_index <= link->start_index && link->start_index < end_index) {
  1626. if (!save_text_part (text, state, start_index, link->start_index))
  1627. return FALSE;
  1628. *link_started = TRUE;
  1629. save_link_open (link, state);
  1630. start_index = link->start_index;
  1631. }
  1632. if (*link_started && link->end_index <= end_index) {
  1633. if (!save_text_part (text, state, start_index, link->end_index))
  1634. return FALSE;
  1635. save_link_close (link, state);
  1636. *link_started = FALSE;
  1637. (*l) = (*l)->next;
  1638. start_index = link->end_index;
  1639. if (*l)
  1640. link = (Link *) (*l)->data;
  1641. }
  1642. }
  1643. }
  1644. if (start_index < end_index)
  1645. return save_text_part (text, state, start_index, end_index);
  1646. return TRUE;
  1647. }
  1648. static gboolean
  1649. save (HTMLObject *self,
  1650. HTMLEngineSaveState *state)
  1651. {
  1652. HTMLText *text = HTML_TEXT (self);
  1653. PangoAttrIterator *iter = pango_attr_list_get_iterator (text->attr_list);
  1654. if (iter) {
  1655. GSList *l, *links = g_slist_reverse (g_slist_copy (text->links));
  1656. gboolean link_started = FALSE;
  1657. l = links;
  1658. do {
  1659. GSList *attrs;
  1660. gint start_index, end_index;
  1661. attrs = pango_attr_iterator_get_attrs (iter);
  1662. pango_attr_iterator_range (iter,
  1663. &start_index,
  1664. &end_index);
  1665. if (end_index > text->text_bytes)
  1666. end_index = text->text_bytes;
  1667. if (attrs)
  1668. save_open_attrs (state, attrs);
  1669. save_text (text, state, start_index, end_index, &l, &link_started);
  1670. if (attrs) {
  1671. attrs = g_slist_reverse (attrs);
  1672. save_close_attrs (state, attrs);
  1673. html_text_free_attrs (attrs);
  1674. }
  1675. } while (pango_attr_iterator_next (iter));
  1676. pango_attr_iterator_destroy (iter);
  1677. g_slist_free (links);
  1678. }
  1679. return TRUE;
  1680. }
  1681. static gboolean
  1682. save_plain (HTMLObject *self,
  1683. HTMLEngineSaveState *state,
  1684. gint requested_width)
  1685. {
  1686. HTMLText *text;
  1687. text = HTML_TEXT (self);
  1688. return html_engine_save_output_string (state, "%s", text->text);
  1689. }
  1690. static guint
  1691. get_length (HTMLObject *self)
  1692. {
  1693. return HTML_TEXT (self)->text_len;
  1694. }
  1695. /* #define DEBUG_NBSP */
  1696. struct TmpDeltaRecord
  1697. {
  1698. gint index; /* Byte index within original string */
  1699. gint delta; /* New delta (character at index was modified,
  1700. * new delta applies to characters afterwards)
  1701. */
  1702. };
  1703. /* Called when current character is not white space or at end of string */
  1704. static gboolean
  1705. check_last_white (gint white_space,
  1706. gunichar last_white,
  1707. gint *delta_out)
  1708. {
  1709. if (white_space > 0 && last_white == ENTITY_NBSP) {
  1710. (*delta_out) --; /* &nbsp; => &sp; is one byte shorter in UTF-8 */
  1711. return TRUE;
  1712. }
  1713. return FALSE;
  1714. }
  1715. /* Called when current character is white space */
  1716. static gboolean
  1717. check_prev_white (gint white_space,
  1718. gunichar last_white,
  1719. gint *delta_out)
  1720. {
  1721. if (white_space > 0 && last_white == ' ') {
  1722. (*delta_out) ++; /* &sp; => &nbsp; is one byte longer in UTF-8 */
  1723. return TRUE;
  1724. }
  1725. return FALSE;
  1726. }
  1727. static GSList *
  1728. add_change (GSList *list,
  1729. gint index,
  1730. gint delta)
  1731. {
  1732. struct TmpDeltaRecord *rec = g_new (struct TmpDeltaRecord, 1);
  1733. rec->index = index;
  1734. rec->delta = delta;
  1735. return g_slist_prepend (list, rec);
  1736. }
  1737. /* This function does a pre-scan for the transformation in convert_nbsp,
  1738. * which converts a sequence of N white space characters (&sp; or &nbsp;)
  1739. * into N-1 &nbsp and 1 &sp;.
  1740. *
  1741. * delta_out: total change in byte length of string
  1742. * changes_out: location to store series of records for each change in offset
  1743. * between the original string and the new string.
  1744. * returns: %TRUE if any records were stored in changes_out
  1745. */
  1746. static gboolean
  1747. is_convert_nbsp_needed (const gchar *s,
  1748. gint *delta_out,
  1749. GSList **changes_out)
  1750. {
  1751. gunichar uc, last_white = 0;
  1752. gboolean change;
  1753. gint white_space;
  1754. const gchar *p, *last_p;
  1755. *delta_out = 0;
  1756. last_p = NULL; /* Quiet GCC */
  1757. white_space = 0;
  1758. for (p = s; *p; p = g_utf8_next_char (p)) {
  1759. uc = g_utf8_get_char (p);
  1760. if (uc == ENTITY_NBSP || uc == ' ') {
  1761. change = check_prev_white (white_space, last_white, delta_out);
  1762. white_space++;
  1763. last_white = uc;
  1764. } else {
  1765. change = check_last_white (white_space, last_white, delta_out);
  1766. white_space = 0;
  1767. }
  1768. if (change)
  1769. *changes_out = add_change (*changes_out, last_p - s, *delta_out);
  1770. last_p = p;
  1771. }
  1772. if (check_last_white (white_space, last_white, delta_out))
  1773. *changes_out = add_change (*changes_out, last_p - s, *delta_out);
  1774. *changes_out = g_slist_reverse (*changes_out);
  1775. return *changes_out != NULL;
  1776. }
  1777. /* Called when current character is white space */
  1778. static void
  1779. write_prev_white_space (gint white_space,
  1780. gchar **fill)
  1781. {
  1782. if (white_space > 0) {
  1783. #ifdef DEBUG_NBSP
  1784. printf ("&nbsp;");
  1785. #endif
  1786. **fill = 0xc2; (*fill) ++;
  1787. **fill = 0xa0; (*fill) ++;
  1788. }
  1789. }
  1790. /* Called when current character is not white space or at end of string */
  1791. static void
  1792. write_last_white_space (gint white_space,
  1793. gchar **fill)
  1794. {
  1795. if (white_space > 0) {
  1796. #ifdef DEBUG_NBSP
  1797. printf (" ");
  1798. #endif
  1799. **fill = ' '; (*fill) ++;
  1800. }
  1801. }
  1802. /* converts a sequence of N white space characters (&sp; or &nbsp;)
  1803. * into N-1 &nbsp and 1 &sp;.
  1804. */
  1805. static void
  1806. convert_nbsp (gchar *fill,
  1807. const gchar *text)
  1808. {
  1809. gint white_space;
  1810. gunichar uc;
  1811. const gchar *this_p, *p;
  1812. p = text;
  1813. white_space = 0;
  1814. #ifdef DEBUG_NBSP
  1815. printf ("convert_nbsp: %s --> \"", p);
  1816. #endif
  1817. while (*p) {
  1818. this_p = p;
  1819. uc = g_utf8_get_char (p);
  1820. p = g_utf8_next_char (p);
  1821. if (uc == ENTITY_NBSP || uc == ' ') {
  1822. write_prev_white_space (white_space, &fill);
  1823. white_space++;
  1824. } else {
  1825. write_last_white_space (white_space, &fill);
  1826. white_space = 0;
  1827. #ifdef DEBUG_NBSP
  1828. printf ("*");
  1829. #endif
  1830. strncpy (fill, this_p, p - this_p);
  1831. fill += p - this_p;
  1832. }
  1833. }
  1834. write_last_white_space (white_space, &fill);
  1835. *fill = 0;
  1836. #ifdef DEBUG_NBSP
  1837. printf ("\"\n");
  1838. #endif
  1839. }
  1840. static void
  1841. update_index_interval (guint *start_index,
  1842. guint *end_index,
  1843. GSList *changes)
  1844. {
  1845. GSList *c;
  1846. gint index, delta;
  1847. index = delta = 0;
  1848. for (c = changes; c && *start_index > index; c = c->next) {
  1849. struct TmpDeltaRecord *rec = c->data;
  1850. if (*start_index > index && *start_index <= rec->index) {
  1851. (*start_index) += delta;
  1852. break;
  1853. }
  1854. index = rec->index;
  1855. delta = rec->delta;
  1856. }
  1857. if (c == NULL && *start_index > index) {
  1858. (*start_index) += delta;
  1859. (*end_index) += delta;
  1860. return;
  1861. }
  1862. for (; c && *end_index > index; c = c->next) {
  1863. struct TmpDeltaRecord *rec = c->data;
  1864. if (*end_index > index && *end_index <= rec->index) {
  1865. (*end_index) += delta;
  1866. break;
  1867. }
  1868. index = rec->index;
  1869. delta = rec->delta;
  1870. }
  1871. if (c == NULL && *end_index > index)
  1872. (*end_index) += delta;
  1873. }
  1874. static gboolean
  1875. update_attributes_filter (PangoAttribute *attr,
  1876. gpointer data)
  1877. {
  1878. update_index_interval (&attr->start_index, &attr->end_index, (GSList *) data);
  1879. return FALSE;
  1880. }
  1881. static void
  1882. update_attributes (PangoAttrList *attrs,
  1883. GSList *changes)
  1884. {
  1885. pango_attr_list_filter (attrs, update_attributes_filter, changes);
  1886. }
  1887. static void
  1888. update_links (GSList *links,
  1889. GSList *changes)
  1890. {
  1891. GSList *cl;
  1892. for (cl = links; cl; cl = cl->next) {
  1893. Link *link = (Link *) cl->data;
  1894. update_index_interval (&link->start_index, &link->end_index, changes);
  1895. }
  1896. }
  1897. static void
  1898. free_changes (GSList *changes)
  1899. {
  1900. GSList *c;
  1901. for (c = changes; c; c = c->next)
  1902. g_free (c->data);
  1903. g_slist_free (changes);
  1904. }
  1905. gboolean
  1906. html_text_convert_nbsp (HTMLText *text,
  1907. gboolean free_text)
  1908. {
  1909. GSList *changes = NULL;
  1910. gint delta;
  1911. if (is_convert_nbsp_needed (text->text, &delta, &changes)) {
  1912. gchar *to_free;
  1913. to_free = text->text;
  1914. text->text = g_malloc (strlen (to_free) + delta + 1);
  1915. text->text_bytes += delta;
  1916. convert_nbsp (text->text, to_free);
  1917. if (free_text)
  1918. g_free (to_free);
  1919. if (changes) {
  1920. if (text->attr_list)
  1921. update_attributes (text->attr_list, changes);
  1922. if (text->extra_attr_list)
  1923. update_attributes (text->extra_attr_list, changes);
  1924. if (text->links)
  1925. update_links (text->links, changes);
  1926. free_changes (changes);
  1927. }
  1928. html_object_change_set (HTML_OBJECT (text), HTML_CHANGE_ALL);
  1929. return TRUE;
  1930. }
  1931. return FALSE;
  1932. }
  1933. static void
  1934. move_spell_errors (GList *spell_errors,
  1935. guint offset,
  1936. gint delta)
  1937. {
  1938. SpellError *se;
  1939. if (!delta)
  1940. return;
  1941. while (spell_errors) {
  1942. se = (SpellError *) spell_errors->data;
  1943. if (se->off >= offset)
  1944. se->off += delta;
  1945. spell_errors = spell_errors->next;
  1946. }
  1947. }
  1948. static GList *
  1949. remove_one (GList *list,
  1950. GList *link)
  1951. {
  1952. spell_error_destroy ((SpellError *) link->data);
  1953. list = g_list_remove_link (list, link);
  1954. g_list_free (link);
  1955. return list;
  1956. }
  1957. static GList *
  1958. remove_spell_errors (GList *spell_errors,
  1959. guint offset,
  1960. guint len)
  1961. {
  1962. SpellError *se;
  1963. GList *cur, *cnext;
  1964. cur = spell_errors;
  1965. while (cur) {
  1966. cnext = cur->next;
  1967. se = (SpellError *) cur->data;
  1968. if (se->off < offset) {
  1969. if (se->off + se->len > offset) {
  1970. if (se->off + se->len <= offset + len)
  1971. se->len = offset - se->off;
  1972. else
  1973. se->len -= len;
  1974. if (se->len < 2)
  1975. spell_errors = remove_one (spell_errors, cur);
  1976. }
  1977. } else if (se->off < offset + len) {
  1978. if (se->off + se->len <= offset + len)
  1979. spell_errors = remove_one (spell_errors, cur);
  1980. else {
  1981. se->len -= offset + len - se->off;
  1982. se->off = offset + len;
  1983. if (se->len < 2)
  1984. spell_errors = remove_one (spell_errors, cur);
  1985. }
  1986. }
  1987. cur = cnext;
  1988. }
  1989. return spell_errors;
  1990. }
  1991. static gint
  1992. se_cmp (SpellError *a,
  1993. SpellError *b)
  1994. {
  1995. guint o1 = a->off;
  1996. guint o2 = b->off;
  1997. return (o1 < o2) ? -1 : (o1 == o2) ? 0 : 1;
  1998. }
  1999. static GList *
  2000. merge_spell_errors (GList *se1,
  2001. GList *se2)
  2002. {
  2003. GList *merged = NULL;
  2004. GList *link;
  2005. /* Build the merge list in reverse order. */
  2006. while (se1 != NULL && se2 != NULL) {
  2007. /* Pop the lesser of the two list heads. */
  2008. if (se_cmp (se1->data, se2->data) < 0) {
  2009. link = se1;
  2010. se1 = g_list_remove_link (se1, link);
  2011. } else {
  2012. link = se2;
  2013. se2 = g_list_remove_link (se2, link);
  2014. }
  2015. /* Merge unique items, discard duplicates. */
  2016. if (merged == NULL || se_cmp (link->data, merged->data) != 0)
  2017. merged = g_list_concat (link, merged);
  2018. else {
  2019. spell_error_destroy (link->data);
  2020. g_list_free (link);
  2021. }
  2022. }
  2023. merged = g_list_reverse (merged);
  2024. /* At this point at least one of the two input lists are empty,
  2025. * so just append them both to the end of the merge list. */
  2026. merged = g_list_concat (merged, se1);
  2027. merged = g_list_concat (merged, se2);
  2028. return merged;
  2029. }
  2030. static HTMLObject *
  2031. check_point (HTMLObject *self,
  2032. HTMLPainter *painter,
  2033. gint x,
  2034. gint y,
  2035. guint *offset_return,
  2036. gboolean for_cursor)
  2037. {
  2038. return NULL;
  2039. }
  2040. static void
  2041. queue_draw (HTMLText *text,
  2042. HTMLEngine *engine,
  2043. guint offset,
  2044. guint len)
  2045. {
  2046. HTMLObject *obj;
  2047. for (obj = HTML_OBJECT (text)->next; obj != NULL; obj = obj->next) {
  2048. HTMLTextSlave *slave;
  2049. if (HTML_OBJECT_TYPE (obj) != HTML_TYPE_TEXTSLAVE)
  2050. continue;
  2051. slave = HTML_TEXT_SLAVE (obj);
  2052. if (offset < slave->posStart + slave->posLen
  2053. && (len == 0 || offset + len >= slave->posStart)) {
  2054. html_engine_queue_draw (engine, obj);
  2055. if (len != 0 && slave->posStart + slave->posLen > offset + len)
  2056. break;
  2057. }
  2058. }
  2059. }
  2060. /* This is necessary to merge the text-specified font style with that of the
  2061. * HTMLClueFlow parent. */
  2062. static GtkHTMLFontStyle
  2063. get_font_style (const HTMLText *text)
  2064. {
  2065. HTMLObject *parent;
  2066. GtkHTMLFontStyle font_style;
  2067. parent = HTML_OBJECT (text)->parent;
  2068. if (parent && HTML_OBJECT_TYPE (parent) == HTML_TYPE_CLUEFLOW) {
  2069. GtkHTMLFontStyle parent_style;
  2070. parent_style = html_clueflow_get_default_font_style (HTML_CLUEFLOW (parent));
  2071. font_style = gtk_html_font_style_merge (parent_style, text->font_style);
  2072. } else {
  2073. font_style = gtk_html_font_style_merge (GTK_HTML_FONT_STYLE_SIZE_3, text->font_style);
  2074. }
  2075. return font_style;
  2076. }
  2077. static void
  2078. set_font_style (HTMLText *text,
  2079. HTMLEngine *engine,
  2080. GtkHTMLFontStyle style)
  2081. {
  2082. if (text->font_style == style)
  2083. return;
  2084. text->font_style = style;
  2085. html_object_change_set (HTML_OBJECT (text), HTML_CHANGE_ALL_CALC);
  2086. if (engine != NULL) {
  2087. html_object_relayout (HTML_OBJECT (text)->parent, engine, HTML_OBJECT (text));
  2088. html_engine_queue_draw (engine, HTML_OBJECT (text));
  2089. }
  2090. }
  2091. static void
  2092. destroy (HTMLObject *obj)
  2093. {
  2094. HTMLText *text = HTML_TEXT (obj);
  2095. html_color_unref (text->color);
  2096. html_text_spell_errors_clear (text);
  2097. g_free (text->text);
  2098. g_free (text->face);
  2099. pango_info_destroy (text);
  2100. pango_attr_list_unref (text->attr_list);
  2101. text->attr_list = NULL;
  2102. if (text->extra_attr_list) {
  2103. pango_attr_list_unref (text->extra_attr_list);
  2104. text->extra_attr_list = NULL;
  2105. }
  2106. free_links (text->links);
  2107. text->links = NULL;
  2108. HTML_OBJECT_CLASS (parent_class)->destroy (obj);
  2109. }
  2110. static gboolean
  2111. select_range (HTMLObject *self,
  2112. HTMLEngine *engine,
  2113. guint offset,
  2114. gint length,
  2115. gboolean queue_draw)
  2116. {
  2117. HTMLText *text;
  2118. HTMLObject *p;
  2119. HTMLTextPangoInfo *pi = html_text_get_pango_info (HTML_TEXT (self), engine->painter);
  2120. gboolean changed;
  2121. text = HTML_TEXT (self);
  2122. if (length < 0 || length + offset > HTML_TEXT (self)->text_len)
  2123. length = HTML_TEXT (self)->text_len - offset;
  2124. /* extend to cursor positions */
  2125. while (offset > 0 && !pi->attrs[offset].is_cursor_position) {
  2126. offset--;
  2127. length++;
  2128. }
  2129. while (offset + length < text->text_len && !pi->attrs[offset + length].is_cursor_position)
  2130. length++;
  2131. /* printf ("updated offset: %d length: %d (end offset %d)\n", offset, length, offset + length); */
  2132. if (offset != text->select_start || length != text->select_length)
  2133. changed = TRUE;
  2134. else
  2135. changed = FALSE;
  2136. /* printf ("select range %d, %d\n", offset, length); */
  2137. if (queue_draw) {
  2138. for (p = self->next;
  2139. p != NULL && HTML_OBJECT_TYPE (p) == HTML_TYPE_TEXTSLAVE;
  2140. p = p->next) {
  2141. HTMLTextSlave *slave;
  2142. gboolean was_selected, is_selected;
  2143. guint max;
  2144. slave = HTML_TEXT_SLAVE (p);
  2145. max = slave->posStart + slave->posLen;
  2146. if (text->select_start + text->select_length > slave->posStart
  2147. && text->select_start < max)
  2148. was_selected = TRUE;
  2149. else
  2150. was_selected = FALSE;
  2151. if (offset + length > slave->posStart && offset < max)
  2152. is_selected = TRUE;
  2153. else
  2154. is_selected = FALSE;
  2155. if (was_selected && is_selected) {
  2156. gint diff1, diff2;
  2157. diff1 = offset - slave->posStart;
  2158. diff2 = text->select_start - slave->posStart;
  2159. /* printf ("offsets diff 1: %d 2: %d\n", diff1, diff2); */
  2160. if (diff1 != diff2) {
  2161. html_engine_queue_draw (engine, p);
  2162. } else {
  2163. diff1 = offset + length - slave->posStart;
  2164. diff2 = (text->select_start + text->select_length
  2165. - slave->posStart);
  2166. /* printf ("lens diff 1: %d 2: %d\n", diff1, diff2); */
  2167. if (diff1 != diff2)
  2168. html_engine_queue_draw (engine, p);
  2169. }
  2170. } else {
  2171. if ((!was_selected && is_selected) || (was_selected && !is_selected))
  2172. html_engine_queue_draw (engine, p);
  2173. }
  2174. }
  2175. }
  2176. text->select_start = offset;
  2177. text->select_length = length;
  2178. if (length == 0)
  2179. self->selected = FALSE;
  2180. else
  2181. self->selected = TRUE;
  2182. return changed;
  2183. }
  2184. static HTMLObject *
  2185. set_link (HTMLObject *self,
  2186. HTMLColor *color,
  2187. const gchar *url,
  2188. const gchar *target)
  2189. {
  2190. /* HTMLText *text = HTML_TEXT (self); */
  2191. /* FIXME-link return url ? html_link_text_new_with_len (text->text, text->text_len, text->font_style, color, url, target) : NULL; */
  2192. return NULL;
  2193. }
  2194. static void
  2195. append_selection_string (HTMLObject *self,
  2196. GString *buffer)
  2197. {
  2198. HTMLText *text;
  2199. const gchar *p, *last;
  2200. text = HTML_TEXT (self);
  2201. if (text->select_length == 0)
  2202. return;
  2203. p = html_text_get_text (text, text->select_start);
  2204. last = g_utf8_offset_to_pointer (p, text->select_length);
  2205. /* OPTIMIZED
  2206. last = html_text_get_text (text,
  2207. * text->select_start + text->select_length);
  2208. */
  2209. html_engine_save_string_append_nonbsp (buffer,
  2210. (guchar *) p,
  2211. last - p);
  2212. }
  2213. static void
  2214. get_cursor (HTMLObject *self,
  2215. HTMLPainter *painter,
  2216. guint offset,
  2217. gint *x1,
  2218. gint *y1,
  2219. gint *x2,
  2220. gint *y2)
  2221. {
  2222. HTMLObject *slave;
  2223. guint ascent, descent;
  2224. html_object_get_cursor_base (self, painter, offset, x2, y2);
  2225. slave = self->next;
  2226. if (slave == NULL || HTML_OBJECT_TYPE (slave) != HTML_TYPE_TEXTSLAVE) {
  2227. ascent = self->ascent;
  2228. descent = self->descent;
  2229. } else {
  2230. ascent = slave->ascent;
  2231. descent = slave->descent;
  2232. }
  2233. *x1 = *x2;
  2234. *y1 = *y2 - ascent;
  2235. *y2 += descent - 1;
  2236. }
  2237. static void
  2238. html_text_get_cursor_base (HTMLObject *self,
  2239. HTMLPainter *painter,
  2240. guint offset,
  2241. gint *x,
  2242. gint *y)
  2243. {
  2244. HTMLTextSlave *slave = html_text_get_slave_at_offset (HTML_TEXT (self), NULL, offset);
  2245. /* printf ("slave: %p\n", slave); */
  2246. if (slave)
  2247. html_text_slave_get_cursor_base (slave, painter, offset - slave->posStart, x, y);
  2248. else {
  2249. g_warning ("Getting cursor base for an HTMLText with no slaves -- %p\n", (gpointer) self);
  2250. html_object_calc_abs_position (self, x, y);
  2251. }
  2252. }
  2253. Link *
  2254. html_text_get_link_at_offset (HTMLText *text,
  2255. gint offset)
  2256. {
  2257. GSList *l;
  2258. for (l = text->links; l; l = l->next) {
  2259. Link *link = (Link *) l->data;
  2260. if (link->start_offset <= offset && offset <= link->end_offset)
  2261. return link;
  2262. }
  2263. return NULL;
  2264. }
  2265. static const gchar *
  2266. get_url (HTMLObject *object,
  2267. gint offset)
  2268. {
  2269. Link *link = html_text_get_link_at_offset (HTML_TEXT (object), offset);
  2270. return link ? link->url : NULL;
  2271. }
  2272. static const gchar *
  2273. get_target (HTMLObject *object,
  2274. gint offset)
  2275. {
  2276. Link *link = html_text_get_link_at_offset (HTML_TEXT (object), offset);
  2277. return link ? link->target : NULL;
  2278. }
  2279. HTMLTextSlave *
  2280. html_text_get_slave_at_offset (HTMLText *text,
  2281. HTMLTextSlave *start,
  2282. gint offset)
  2283. {
  2284. HTMLObject *obj = start ? HTML_OBJECT (start) : HTML_OBJECT (text)->next;
  2285. while (obj && HTML_IS_TEXT_SLAVE (obj) && HTML_TEXT_SLAVE (obj)->posStart + HTML_TEXT_SLAVE (obj)->posLen < offset)
  2286. obj = obj->next;
  2287. if (obj && HTML_IS_TEXT_SLAVE (obj) && HTML_TEXT_SLAVE (obj)->posStart + HTML_TEXT_SLAVE (obj)->posLen >= offset)
  2288. return HTML_TEXT_SLAVE (obj);
  2289. return NULL;
  2290. }
  2291. static gboolean
  2292. html_text_cursor_prev_slave (HTMLObject *slave,
  2293. HTMLPainter *painter,
  2294. HTMLCursor *cursor)
  2295. {
  2296. gint offset = cursor->offset;
  2297. while (slave->prev && HTML_IS_TEXT_SLAVE (slave->prev)) {
  2298. if (HTML_TEXT_SLAVE (slave->prev)->posLen) {
  2299. if (html_text_slave_cursor_tail (HTML_TEXT_SLAVE (slave->prev), cursor, painter)) {
  2300. cursor->position += cursor->offset - offset;
  2301. return TRUE;
  2302. } else
  2303. break;
  2304. }
  2305. slave = slave->prev;
  2306. }
  2307. return FALSE;
  2308. }
  2309. static gboolean
  2310. html_text_cursor_next_slave (HTMLObject *slave,
  2311. HTMLPainter *painter,
  2312. HTMLCursor *cursor)
  2313. {
  2314. gint offset = cursor->offset;
  2315. while (slave->next && HTML_IS_TEXT_SLAVE (slave->next)) {
  2316. if (HTML_TEXT_SLAVE (slave->next)->posLen) {
  2317. if (html_text_slave_cursor_head (HTML_TEXT_SLAVE (slave->next), cursor, painter)) {
  2318. cursor->position += cursor->offset - offset;
  2319. return TRUE;
  2320. } else
  2321. break;
  2322. }
  2323. slave = slave->next;
  2324. }
  2325. return FALSE;
  2326. }
  2327. static gboolean
  2328. html_text_cursor_forward (HTMLObject *self,
  2329. HTMLCursor *cursor,
  2330. HTMLEngine *engine)
  2331. {
  2332. HTMLText *text;
  2333. HTMLTextPangoInfo *pi = NULL;
  2334. gint len, attrpos = 0;
  2335. gboolean retval = FALSE;
  2336. g_assert (self);
  2337. g_assert (cursor->object == self);
  2338. if (html_object_is_container (self))
  2339. return FALSE;
  2340. text = HTML_TEXT (self);
  2341. pi = html_text_get_pango_info (text, engine->painter);
  2342. len = html_object_get_length (self);
  2343. do {
  2344. attrpos = cursor->offset;
  2345. if (attrpos < len) {
  2346. cursor->offset++;
  2347. cursor->position++;
  2348. retval = TRUE;
  2349. } else {
  2350. retval = FALSE;
  2351. break;
  2352. }
  2353. } while (attrpos < len &&
  2354. !pi->attrs[attrpos].is_sentence_end &&
  2355. !pi->attrs[attrpos + 1].is_cursor_position);
  2356. return retval;
  2357. }
  2358. static gboolean
  2359. html_cursor_allow_zero_offset (HTMLCursor *cursor,
  2360. HTMLObject *o)
  2361. {
  2362. if (cursor->offset == 1) {
  2363. HTMLObject *prev;
  2364. prev = html_object_prev_not_slave (o);
  2365. if (!prev || HTML_IS_CLUEALIGNED (prev))
  2366. return TRUE;
  2367. else {
  2368. while (prev && !html_object_accepts_cursor (prev))
  2369. prev = html_object_prev_not_slave (prev);
  2370. if (!prev)
  2371. return TRUE;
  2372. }
  2373. }
  2374. return FALSE;
  2375. }
  2376. static gboolean
  2377. html_text_cursor_backward (HTMLObject *self,
  2378. HTMLCursor *cursor,
  2379. HTMLEngine *engine)
  2380. {
  2381. HTMLText *text;
  2382. HTMLTextPangoInfo *pi = NULL;
  2383. gint attrpos = 0;
  2384. gboolean retval = FALSE;
  2385. g_assert (self);
  2386. g_assert (cursor->object == self);
  2387. if (html_object_is_container (self))
  2388. return FALSE;
  2389. text = HTML_TEXT (self);
  2390. pi = html_text_get_pango_info (text, engine->painter);
  2391. do {
  2392. attrpos = cursor->offset;
  2393. if (cursor->offset > 1 ||
  2394. html_cursor_allow_zero_offset (cursor, self)) {
  2395. cursor->offset--;
  2396. cursor->position--;
  2397. retval = TRUE;
  2398. } else {
  2399. retval = FALSE;
  2400. break;
  2401. }
  2402. } while (attrpos > 0 &&
  2403. !pi->attrs[attrpos].is_sentence_start &&
  2404. !pi->attrs[attrpos - 1].is_cursor_position);
  2405. return retval;
  2406. }
  2407. static gboolean
  2408. html_text_cursor_right (HTMLObject *self,
  2409. HTMLPainter *painter,
  2410. HTMLCursor *cursor)
  2411. {
  2412. HTMLTextSlave *slave;
  2413. g_assert (self);
  2414. g_assert (cursor->object == self);
  2415. slave = html_text_get_slave_at_offset (HTML_TEXT (self), NULL, cursor->offset);
  2416. if (slave) {
  2417. if (html_text_slave_cursor_right (slave, painter, cursor))
  2418. return TRUE;
  2419. else {
  2420. if (self->parent) {
  2421. if (html_object_get_direction (self->parent) == HTML_DIRECTION_RTL)
  2422. return html_text_cursor_prev_slave (HTML_OBJECT (slave), painter, cursor);
  2423. else
  2424. return html_text_cursor_next_slave (HTML_OBJECT (slave), painter, cursor);
  2425. }
  2426. }
  2427. }
  2428. return FALSE;
  2429. }
  2430. static gboolean
  2431. html_text_cursor_left (HTMLObject *self,
  2432. HTMLPainter *painter,
  2433. HTMLCursor *cursor)
  2434. {
  2435. HTMLTextSlave *slave;
  2436. g_assert (self);
  2437. g_assert (cursor->object == self);
  2438. slave = html_text_get_slave_at_offset (HTML_TEXT (self), NULL, cursor->offset);
  2439. if (slave) {
  2440. if (html_text_slave_cursor_left (slave, painter, cursor))
  2441. return TRUE;
  2442. else {
  2443. if (self->parent) {
  2444. if (html_object_get_direction (self->parent) == HTML_DIRECTION_RTL)
  2445. return html_text_cursor_next_slave (HTML_OBJECT (slave), painter, cursor);
  2446. else
  2447. return html_text_cursor_prev_slave (HTML_OBJECT (slave), painter, cursor);
  2448. }
  2449. }
  2450. }
  2451. return FALSE;
  2452. }
  2453. static gboolean
  2454. html_text_backspace (HTMLObject *self,
  2455. HTMLCursor *cursor,
  2456. HTMLEngine *engine)
  2457. {
  2458. HTMLText *text;
  2459. HTMLTextPangoInfo *pi = NULL;
  2460. guint attrpos = 0, prevpos;
  2461. gboolean retval = FALSE;
  2462. g_assert (self);
  2463. g_assert (cursor->object == self);
  2464. text = HTML_TEXT (self);
  2465. pi = html_text_get_pango_info (text, engine->painter);
  2466. prevpos = cursor->offset;
  2467. do {
  2468. attrpos = cursor->offset;
  2469. if (cursor->offset > 1 ||
  2470. html_cursor_allow_zero_offset (cursor, self)) {
  2471. cursor->offset--;
  2472. cursor->position--;
  2473. retval = TRUE;
  2474. } else {
  2475. if (cursor->offset == prevpos)
  2476. retval = FALSE;
  2477. break;
  2478. }
  2479. } while (attrpos > 0 && !pi->attrs[attrpos].is_cursor_position);
  2480. if (!retval) {
  2481. HTMLObject *prev;
  2482. gint offset = cursor->offset;
  2483. /* maybe no characters in this line. */
  2484. prev = html_object_prev_cursor (cursor->object, &offset);
  2485. cursor->offset = offset;
  2486. if (prev) {
  2487. if (!html_object_is_container (prev))
  2488. cursor->offset = html_object_get_length (prev);
  2489. cursor->object = prev;
  2490. cursor->position--;
  2491. retval = TRUE;
  2492. }
  2493. }
  2494. if (retval) {
  2495. if (pi->attrs[attrpos].backspace_deletes_character) {
  2496. gchar *cluster_text = &text->text[prevpos];
  2497. gchar *normalized_text = NULL;
  2498. glong len;
  2499. gint offset = cursor->offset, pos = cursor->position;
  2500. normalized_text = g_utf8_normalize (cluster_text,
  2501. prevpos - attrpos,
  2502. G_NORMALIZE_NFD);
  2503. len = g_utf8_strlen (normalized_text, -1);
  2504. html_engine_delete (engine);
  2505. if (len > 1) {
  2506. html_engine_insert_text (engine, normalized_text,
  2507. g_utf8_offset_to_pointer (normalized_text, len - 1) - normalized_text);
  2508. html_cursor_jump_to (cursor, engine, self, offset);
  2509. }
  2510. if (normalized_text)
  2511. g_free (normalized_text);
  2512. /* restore a cursor position and offset for a split cursor */
  2513. engine->cursor->offset = offset;
  2514. engine->cursor->position = pos;
  2515. } else {
  2516. html_engine_delete (engine);
  2517. }
  2518. }
  2519. return retval;
  2520. }
  2521. static gint
  2522. html_text_get_right_edge_offset (HTMLObject *o,
  2523. HTMLPainter *painter,
  2524. gint offset)
  2525. {
  2526. HTMLTextSlave *slave = html_text_get_slave_at_offset (HTML_TEXT (o), NULL, offset);
  2527. if (slave) {
  2528. return html_text_slave_get_right_edge_offset (slave, painter);
  2529. } else {
  2530. g_warning ("getting right edge offset from text object without slave(s)");
  2531. return HTML_TEXT (o)->text_len;
  2532. }
  2533. }
  2534. static gint
  2535. html_text_get_left_edge_offset (HTMLObject *o,
  2536. HTMLPainter *painter,
  2537. gint offset)
  2538. {
  2539. HTMLTextSlave *slave = html_text_get_slave_at_offset (HTML_TEXT (o), NULL, offset);
  2540. if (slave) {
  2541. return html_text_slave_get_left_edge_offset (slave, painter);
  2542. } else {
  2543. g_warning ("getting left edge offset from text object without slave(s)");
  2544. return 0;
  2545. }
  2546. }
  2547. void
  2548. html_text_type_init (void)
  2549. {
  2550. html_text_class_init (&html_text_class, HTML_TYPE_TEXT, sizeof (HTMLText));
  2551. }
  2552. void
  2553. html_text_class_init (HTMLTextClass *klass,
  2554. HTMLType type,
  2555. guint object_size)
  2556. {
  2557. HTMLObjectClass *object_class;
  2558. object_class = HTML_OBJECT_CLASS (klass);
  2559. html_object_class_init (object_class, type, object_size);
  2560. object_class->destroy = destroy;
  2561. object_class->copy = copy;
  2562. object_class->op_copy = op_copy;
  2563. object_class->op_cut = op_cut;
  2564. object_class->merge = object_merge;
  2565. object_class->split = object_split;
  2566. object_class->draw = draw;
  2567. object_class->accepts_cursor = accepts_cursor;
  2568. object_class->calc_size = html_text_real_calc_size;
  2569. object_class->calc_preferred_width = calc_preferred_width;
  2570. object_class->calc_min_width = calc_min_width;
  2571. object_class->fit_line = ht_fit_line;
  2572. object_class->get_cursor = get_cursor;
  2573. object_class->get_cursor_base = html_text_get_cursor_base;
  2574. object_class->save = save;
  2575. object_class->save_plain = save_plain;
  2576. object_class->check_point = check_point;
  2577. object_class->select_range = select_range;
  2578. object_class->get_length = get_length;
  2579. object_class->get_line_length = get_line_length;
  2580. object_class->set_link = set_link;
  2581. object_class->append_selection_string = append_selection_string;
  2582. object_class->get_url = get_url;
  2583. object_class->get_target = get_target;
  2584. object_class->cursor_forward = html_text_cursor_forward;
  2585. object_class->cursor_backward = html_text_cursor_backward;
  2586. object_class->cursor_right = html_text_cursor_right;
  2587. object_class->cursor_left = html_text_cursor_left;
  2588. object_class->backspace = html_text_backspace;
  2589. object_class->get_right_edge_offset = html_text_get_right_edge_offset;
  2590. object_class->get_left_edge_offset = html_text_get_left_edge_offset;
  2591. /* HTMLText methods. */
  2592. klass->queue_draw = queue_draw;
  2593. klass->get_font_style = get_font_style;
  2594. klass->set_font_style = set_font_style;
  2595. parent_class = &html_object_class;
  2596. }
  2597. /* almost identical copy of glib's _g_utf8_make_valid() */
  2598. static gchar *
  2599. _html_text_utf8_make_valid (const gchar *name,
  2600. gint len)
  2601. {
  2602. GString *string;
  2603. const gchar *remainder, *invalid;
  2604. gint remaining_bytes, valid_bytes, total_bytes;
  2605. g_return_val_if_fail (name != NULL, NULL);
  2606. string = NULL;
  2607. remainder = name;
  2608. if (len == -1) {
  2609. remaining_bytes = strlen (name);
  2610. } else {
  2611. const gchar *start = name, *end = name;
  2612. while (len > 0) {
  2613. gunichar uc = g_utf8_get_char_validated (end, -1);
  2614. if (uc == (gunichar) -2 || uc == (gunichar) -1) {
  2615. end++;
  2616. } else if (uc == 0) {
  2617. break;
  2618. } else {
  2619. end = g_utf8_next_char (end);
  2620. }
  2621. len--;
  2622. }
  2623. remaining_bytes = end - start;
  2624. }
  2625. total_bytes = remaining_bytes;
  2626. while (remaining_bytes != 0) {
  2627. if (g_utf8_validate (remainder, remaining_bytes, &invalid))
  2628. break;
  2629. valid_bytes = invalid - remainder;
  2630. if (string == NULL)
  2631. string = g_string_sized_new (remaining_bytes);
  2632. g_string_append_len (string, remainder, valid_bytes);
  2633. /* append U+FFFD REPLACEMENT CHARACTER */
  2634. g_string_append (string, "\357\277\275");
  2635. remaining_bytes -= valid_bytes + 1;
  2636. remainder = invalid + 1;
  2637. }
  2638. if (string == NULL)
  2639. return g_strndup (name, total_bytes);
  2640. g_string_append (string, remainder);
  2641. g_assert (g_utf8_validate (string->str, -1, NULL));
  2642. return g_string_free (string, FALSE);
  2643. }
  2644. /**
  2645. * html_text_sanitize:
  2646. * @str_in: text string to sanitize (in)
  2647. * @str_out: newly allocated text string sanitized (out)
  2648. * @len: length of text, in characters (in/out). (A value of
  2649. * -1 on input means to use all characters in @str)
  2650. *
  2651. * Validates a UTF-8 string up to the given number of characters.
  2652. *
  2653. * Return value: number of bytes in the output value of @str
  2654. **/
  2655. gsize
  2656. html_text_sanitize (const gchar *str_in,
  2657. gchar **str_out,
  2658. gint *len)
  2659. {
  2660. g_return_val_if_fail (str_in != NULL, 0);
  2661. g_return_val_if_fail (str_out != NULL, 0);
  2662. g_return_val_if_fail (len != NULL, 0);
  2663. *str_out = _html_text_utf8_make_valid (str_in, *len);
  2664. g_return_val_if_fail (*str_out != NULL, 0);
  2665. *len = g_utf8_strlen (*str_out, -1);
  2666. return strlen (*str_out);
  2667. }
  2668. void
  2669. html_text_init (HTMLText *text,
  2670. HTMLTextClass *klass,
  2671. const gchar *str,
  2672. gint len,
  2673. GtkHTMLFontStyle font_style,
  2674. HTMLColor *color)
  2675. {
  2676. g_assert (color);
  2677. html_object_init (HTML_OBJECT (text), HTML_OBJECT_CLASS (klass));
  2678. text->text_bytes = html_text_sanitize (str, &text->text, &len);
  2679. text->text_len = len;
  2680. text->font_style = font_style;
  2681. text->face = NULL;
  2682. text->color = color;
  2683. text->spell_errors = NULL;
  2684. text->select_start = 0;
  2685. text->select_length = 0;
  2686. text->pi = NULL;
  2687. text->attr_list = pango_attr_list_new ();
  2688. text->extra_attr_list = NULL;
  2689. text->links = NULL;
  2690. html_color_ref (color);
  2691. }
  2692. HTMLObject *
  2693. html_text_new_with_len (const gchar *str,
  2694. gint len,
  2695. GtkHTMLFontStyle font,
  2696. HTMLColor *color)
  2697. {
  2698. HTMLText *text;
  2699. text = g_new (HTMLText, 1);
  2700. html_text_init (text, &html_text_class, str, len, font, color);
  2701. return HTML_OBJECT (text);
  2702. }
  2703. HTMLObject *
  2704. html_text_new (const gchar *text,
  2705. GtkHTMLFontStyle font,
  2706. HTMLColor *color)
  2707. {
  2708. return html_text_new_with_len (text, -1, font, color);
  2709. }
  2710. void
  2711. html_text_queue_draw (HTMLText *text,
  2712. HTMLEngine *engine,
  2713. guint offset,
  2714. guint len)
  2715. {
  2716. g_return_if_fail (text != NULL);
  2717. g_return_if_fail (engine != NULL);
  2718. (* HT_CLASS (text)->queue_draw) (text, engine, offset, len);
  2719. }
  2720. GtkHTMLFontStyle
  2721. html_text_get_font_style (const HTMLText *text)
  2722. {
  2723. g_return_val_if_fail (text != NULL, GTK_HTML_FONT_STYLE_DEFAULT);
  2724. return (* HT_CLASS (text)->get_font_style) (text);
  2725. }
  2726. void
  2727. html_text_set_font_style (HTMLText *text,
  2728. HTMLEngine *engine,
  2729. GtkHTMLFontStyle style)
  2730. {
  2731. g_return_if_fail (text != NULL);
  2732. (* HT_CLASS (text)->set_font_style) (text, engine, style);
  2733. }
  2734. void
  2735. html_text_set_font_face (HTMLText *text,
  2736. HTMLFontFace *face)
  2737. {
  2738. if (text->face)
  2739. g_free (text->face);
  2740. text->face = g_strdup (face);
  2741. }
  2742. void
  2743. html_text_set_text (HTMLText *text,
  2744. const gchar *new_text)
  2745. {
  2746. g_free (text->text);
  2747. text->text = NULL;
  2748. text->text_len = -1;
  2749. text->text_bytes = html_text_sanitize (new_text, &text->text,
  2750. (gint *) &text->text_len);
  2751. html_object_change_set (HTML_OBJECT (text), HTML_CHANGE_ALL);
  2752. }
  2753. /* spell checking */
  2754. #include "htmlinterval.h"
  2755. static SpellError *
  2756. spell_error_new (guint off,
  2757. guint len)
  2758. {
  2759. SpellError *se = g_new (SpellError, 1);
  2760. se->off = off;
  2761. se->len = len;
  2762. return se;
  2763. }
  2764. static void
  2765. spell_error_destroy (SpellError *se)
  2766. {
  2767. g_free (se);
  2768. }
  2769. void
  2770. html_text_spell_errors_clear (HTMLText *text)
  2771. {
  2772. g_list_foreach (text->spell_errors, (GFunc) spell_error_destroy, NULL);
  2773. g_list_free (text->spell_errors);
  2774. text->spell_errors = NULL;
  2775. }
  2776. void
  2777. html_text_spell_errors_clear_interval (HTMLText *text,
  2778. HTMLInterval *i)
  2779. {
  2780. GList *cur, *cnext;
  2781. SpellError *se;
  2782. guint offset, len;
  2783. offset = html_interval_get_start (i, HTML_OBJECT (text));
  2784. len = html_interval_get_length (i, HTML_OBJECT (text));
  2785. cur = text->spell_errors;
  2786. /* printf ("html_text_spell_errors_clear_interval %s %d %d\n", text->text, offset, len); */
  2787. while (cur) {
  2788. cnext = cur->next;
  2789. se = (SpellError *) cur->data;
  2790. /* test overlap */
  2791. if (MAX (offset, se->off) <= MIN (se->off + se->len, offset + len)) {
  2792. text->spell_errors = g_list_remove_link (text->spell_errors, cur);
  2793. spell_error_destroy (se);
  2794. g_list_free (cur);
  2795. }
  2796. cur = cnext;
  2797. }
  2798. }
  2799. void
  2800. html_text_spell_errors_add (HTMLText *text,
  2801. guint off,
  2802. guint len)
  2803. {
  2804. text->spell_errors = merge_spell_errors (
  2805. text->spell_errors, g_list_prepend (
  2806. NULL, spell_error_new (off, len)));
  2807. }
  2808. guint
  2809. html_text_get_bytes (HTMLText *text)
  2810. {
  2811. return strlen (text->text);
  2812. }
  2813. gchar *
  2814. html_text_get_text (HTMLText *text,
  2815. guint offset)
  2816. {
  2817. gchar *s = text->text;
  2818. while (offset-- && s && *s)
  2819. s = g_utf8_next_char (s);
  2820. return s;
  2821. }
  2822. guint
  2823. html_text_get_index (HTMLText *text,
  2824. guint offset)
  2825. {
  2826. return html_text_get_text (text, offset) - text->text;
  2827. }
  2828. gunichar
  2829. html_text_get_char (HTMLText *text,
  2830. guint offset)
  2831. {
  2832. gunichar uc;
  2833. uc = g_utf8_get_char (html_text_get_text (text, offset));
  2834. return uc;
  2835. }
  2836. /* magic links */
  2837. struct _HTMLMagicInsertMatch
  2838. {
  2839. const gchar *regex;
  2840. regex_t *preg;
  2841. const gchar *prefix;
  2842. };
  2843. typedef struct _HTMLMagicInsertMatch HTMLMagicInsertMatch;
  2844. static HTMLMagicInsertMatch mim[] = {
  2845. /* prefixed expressions */
  2846. { "(news|telnet|nntp|file|http|ftp|sftp|https|webcal)://([-a-z0-9]+(:[-a-z0-9]+)?@)?[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(([.])?/[-a-z0-9_$.+!*(),;:@%&=?/~#']*[^]'.}>\\) ,?!;:\"]?)?", NULL, NULL },
  2847. { "(sip|h323|callto):([-_a-z0-9.'\\+]+(:[0-9]{1,5})?(/[-_a-z0-9.']+)?)(@([-_a-z0-9.%=?]+|([0-9]{1,3}.){3}[0-9]{1,3})?)?(:[0-9]{1,5})?", NULL, NULL },
  2848. { "mailto:[-_a-z0-9.'\\+]+@[-_a-z0-9.%=?]+", NULL, NULL },
  2849. /* not prefixed expression */
  2850. { "www\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(([.])?/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]'.}>\\) ,?!;:\"]?)?", NULL, "http://" },
  2851. { "ftp\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(([.])?/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]'.}>\\) ,?!;:\"]?)?", NULL, "ftp://" },
  2852. { "[-_a-z0-9.'\\+]+@[-_a-z0-9.%=?]+", NULL, "mailto:" }
  2853. };
  2854. void
  2855. html_engine_init_magic_links (void)
  2856. {
  2857. gint i;
  2858. for (i = 0; i < G_N_ELEMENTS (mim); i++) {
  2859. mim[i].preg = g_new0 (regex_t, 1);
  2860. if (regcomp (mim[i].preg, mim[i].regex, REG_EXTENDED | REG_ICASE)) {
  2861. /* error */
  2862. g_free (mim[i].preg);
  2863. mim[i].preg = 0;
  2864. }
  2865. }
  2866. }
  2867. static void
  2868. paste_link (HTMLEngine *engine,
  2869. HTMLText *text,
  2870. gint so,
  2871. gint eo,
  2872. const gchar *prefix)
  2873. {
  2874. gchar *href;
  2875. gchar *base;
  2876. base = g_strndup (html_text_get_text (text, so), html_text_get_index (text, eo) - html_text_get_index (text, so));
  2877. href = (prefix) ? g_strconcat (prefix, base, NULL) : g_strdup (base);
  2878. g_free (base);
  2879. html_text_add_link (text, engine, href, NULL, so, eo);
  2880. g_free (href);
  2881. }
  2882. gboolean
  2883. html_text_magic_link (HTMLText *text,
  2884. HTMLEngine *engine,
  2885. guint offset)
  2886. {
  2887. regmatch_t pmatch[2];
  2888. gint i;
  2889. gboolean rv = FALSE, exec = TRUE;
  2890. gint saved_position;
  2891. gunichar uc;
  2892. gchar *str, *cur;
  2893. if (!offset)
  2894. return FALSE;
  2895. offset--;
  2896. /* printf ("html_text_magic_link\n"); */
  2897. html_undo_level_begin (engine->undo, "Magic link", "Remove magic link");
  2898. saved_position = engine->cursor->position;
  2899. cur = str = html_text_get_text (text, offset);
  2900. /* check forward to ensure chars are < 0x80, could be removed once we have utf8 regex */
  2901. while (cur && *cur) {
  2902. cur = g_utf8_next_char (cur);
  2903. if (!*cur)
  2904. break;
  2905. uc = g_utf8_get_char (cur);
  2906. if (uc >= 0x80) {
  2907. exec = FALSE;
  2908. break;
  2909. } else if (uc == ' ' || uc == ENTITY_NBSP) {
  2910. break;
  2911. }
  2912. }
  2913. uc = g_utf8_get_char (str);
  2914. if (uc >= 0x80)
  2915. exec = FALSE;
  2916. while (exec && uc != ' ' && uc != ENTITY_NBSP && offset) {
  2917. str = g_utf8_prev_char (str);
  2918. uc = g_utf8_get_char (str);
  2919. if (uc >= 0x80)
  2920. exec = FALSE;
  2921. offset--;
  2922. }
  2923. if (uc == ' ' || uc == ENTITY_NBSP)
  2924. str = g_utf8_next_char (str);
  2925. if (exec) {
  2926. gboolean done = FALSE;
  2927. guint32 str_offset = 0, str_length = strlen (str);
  2928. while (!done) {
  2929. done = TRUE;
  2930. for (i = 0; i < G_N_ELEMENTS (mim); i++) {
  2931. if (mim[i].preg && !regexec (mim[i].preg, str + str_offset, 2, pmatch, 0)) {
  2932. paste_link (engine, text,
  2933. g_utf8_pointer_to_offset (text->text, str + str_offset + pmatch[0].rm_so),
  2934. g_utf8_pointer_to_offset (text->text, str + str_offset + pmatch[0].rm_eo), mim[i].prefix);
  2935. rv = TRUE;
  2936. str_offset += pmatch[0].rm_eo + 1;
  2937. done = str_offset >= str_length;
  2938. break;
  2939. }
  2940. }
  2941. }
  2942. }
  2943. html_undo_level_end (engine->undo, engine);
  2944. html_cursor_jump_to_position_no_spell (engine->cursor, engine, saved_position);
  2945. return rv;
  2946. }
  2947. /*
  2948. * magic links end
  2949. */
  2950. gint
  2951. html_text_trail_space_width (HTMLText *text,
  2952. HTMLPainter *painter)
  2953. {
  2954. return text->text_len > 0 && html_text_get_char (text, text->text_len - 1) == ' '
  2955. ? html_painter_get_space_width (painter, html_text_get_font_style (text), text->face) : 0;
  2956. }
  2957. void
  2958. html_text_append (HTMLText *text,
  2959. const gchar *pstr,
  2960. gint len)
  2961. {
  2962. gchar *to_delete, *str = NULL;
  2963. guint bytes;
  2964. to_delete = text->text;
  2965. bytes = html_text_sanitize (pstr, &str, &len);
  2966. text->text_len += len;
  2967. text->text = g_malloc (text->text_bytes + bytes + 1);
  2968. memcpy (text->text, to_delete, text->text_bytes);
  2969. memcpy (text->text + text->text_bytes, str, bytes);
  2970. text->text_bytes += bytes;
  2971. text->text[text->text_bytes] = '\0';
  2972. g_free (to_delete);
  2973. g_free (str);
  2974. html_object_change_set (HTML_OBJECT (text), HTML_CHANGE_ALL);
  2975. }
  2976. void
  2977. html_text_append_link_full (HTMLText *text,
  2978. gchar *url,
  2979. gchar *target,
  2980. gint start_index,
  2981. gint end_index,
  2982. gint start_offset,
  2983. gint end_offset)
  2984. {
  2985. text->links = g_slist_prepend (text->links, html_link_new (url, target, start_index, end_index, start_offset, end_offset, FALSE));
  2986. }
  2987. static void
  2988. html_text_offsets_to_indexes (HTMLText *text,
  2989. gint so,
  2990. gint eo,
  2991. gint *si,
  2992. gint *ei)
  2993. {
  2994. *si = html_text_get_index (text, so);
  2995. *ei = g_utf8_offset_to_pointer (text->text + *si, eo - so) - text->text;
  2996. }
  2997. void
  2998. html_text_append_link (HTMLText *text,
  2999. gchar *url,
  3000. gchar *target,
  3001. gint start_offset,
  3002. gint end_offset)
  3003. {
  3004. gint start_index, end_index;
  3005. html_text_offsets_to_indexes (text, start_offset, end_offset, &start_index, &end_index);
  3006. html_text_append_link_full (text, url, target, start_index, end_index, start_offset, end_offset);
  3007. }
  3008. void
  3009. html_text_add_link_full (HTMLText *text,
  3010. HTMLEngine *e,
  3011. gchar *url,
  3012. gchar *target,
  3013. gint start_index,
  3014. gint end_index,
  3015. gint start_offset,
  3016. gint end_offset)
  3017. {
  3018. GSList *l, *prev = NULL;
  3019. Link *link;
  3020. cut_links_full (text, start_offset, end_offset, start_index, end_index, 0, 0);
  3021. if (text->links == NULL)
  3022. html_text_append_link_full (text, url, target, start_index, end_index, start_offset, end_offset);
  3023. else {
  3024. Link *plink = NULL, *new_link = html_link_new (url, target, start_index, end_index, start_offset, end_offset, FALSE);
  3025. for (l = text->links; new_link && l; l = l->next) {
  3026. link = (Link *) l->data;
  3027. if (new_link->start_offset >= link->end_offset) {
  3028. if (new_link->start_offset == link->end_offset && html_link_equal (link, new_link)) {
  3029. link->end_offset = end_offset;
  3030. link->end_index = end_index;
  3031. html_link_free (new_link);
  3032. new_link = NULL;
  3033. } else {
  3034. l = g_slist_prepend (l, new_link);
  3035. if (prev)
  3036. prev->next = l;
  3037. else
  3038. text->links = l;
  3039. link = new_link;
  3040. new_link = NULL;
  3041. }
  3042. if (plink && html_link_equal (plink, link) && plink->start_offset == link->end_offset) {
  3043. plink->start_offset = link->start_offset;
  3044. plink->start_index = link->start_index;
  3045. prev->next = g_slist_remove (prev->next, link);
  3046. html_link_free (link);
  3047. link = plink;
  3048. }
  3049. plink = link;
  3050. prev = l;
  3051. }
  3052. }
  3053. if (new_link && prev)
  3054. prev->next = g_slist_prepend (NULL, new_link);
  3055. }
  3056. HTML_OBJECT (text)->change |= HTML_CHANGE_RECALC_PI;
  3057. }
  3058. void
  3059. html_text_add_link (HTMLText *text,
  3060. HTMLEngine *e,
  3061. gchar *url,
  3062. gchar *target,
  3063. gint start_offset,
  3064. gint end_offset)
  3065. {
  3066. gint start_index, end_index;
  3067. html_text_offsets_to_indexes (text, start_offset, end_offset, &start_index, &end_index);
  3068. html_text_add_link_full (text, e, url, target, start_index, end_index, start_offset, end_offset);
  3069. }
  3070. void
  3071. html_text_remove_links (HTMLText *text)
  3072. {
  3073. if (text->links) {
  3074. free_links (text->links);
  3075. text->links = NULL;
  3076. html_object_change_set (HTML_OBJECT (text), HTML_CHANGE_RECALC_PI);
  3077. }
  3078. }
  3079. /* HTMLTextSlave * */
  3080. /* html_text_get_slave_at_offset (HTMLObject *o, gint offset) */
  3081. /* { */
  3082. /* if (!o || (!HTML_IS_TEXT (o) && !HTML_IS_TEXT_SLAVE (o))) */
  3083. /* return NULL; */
  3084. /* if (HTML_IS_TEXT (o)) */
  3085. /* o = o->next; */
  3086. /* while (o && HTML_IS_TEXT_SLAVE (o)) { */
  3087. /* if (HTML_IS_TEXT_SLAVE (o) && HTML_TEXT_SLAVE (o)->posStart <= offset */
  3088. /* && (offset < HTML_TEXT_SLAVE (o)->posStart + HTML_TEXT_SLAVE (o)->posLen */
  3089. /* || (offset == HTML_TEXT_SLAVE (o)->posStart + HTML_TEXT_SLAVE (o)->posLen && HTML_TEXT_SLAVE (o)->owner->text_len == offset))) */
  3090. /* return HTML_TEXT_SLAVE (o); */
  3091. /* o = o->next; */
  3092. /* } */
  3093. /* return NULL; */
  3094. /* } */
  3095. Link *
  3096. html_text_get_link_slaves_at_offset (HTMLText *text,
  3097. gint offset,
  3098. HTMLTextSlave **start,
  3099. HTMLTextSlave **end)
  3100. {
  3101. Link *link = html_text_get_link_at_offset (text, offset);
  3102. if (link) {
  3103. *start = html_text_get_slave_at_offset (text, NULL, link->start_offset);
  3104. *end = html_text_get_slave_at_offset (text, *start, link->end_offset);
  3105. if (*start && *end)
  3106. return link;
  3107. }
  3108. return NULL;
  3109. }
  3110. gboolean
  3111. html_text_get_link_rectangle (HTMLText *text,
  3112. HTMLPainter *painter,
  3113. gint offset,
  3114. gint *x1,
  3115. gint *y1,
  3116. gint *x2,
  3117. gint *y2)
  3118. {
  3119. HTMLTextSlave *start;
  3120. HTMLTextSlave *end;
  3121. Link *link;
  3122. link = html_text_get_link_slaves_at_offset (text, offset, &start, &end);
  3123. if (link) {
  3124. gint xs, ys, xe, ye;
  3125. html_object_calc_abs_position (HTML_OBJECT (start), &xs, &ys);
  3126. xs += html_text_calc_part_width (text, painter, html_text_slave_get_text (start), start->posStart, link->start_offset - start->posStart, NULL, NULL);
  3127. ys -= HTML_OBJECT (start)->ascent;
  3128. html_object_calc_abs_position (HTML_OBJECT (end), &xe, &ye);
  3129. xe += HTML_OBJECT (end)->width;
  3130. xe -= html_text_calc_part_width (text, painter, text->text + link->end_index, link->end_offset, end->posStart + start->posLen - link->end_offset, NULL, NULL);
  3131. ye += HTML_OBJECT (end)->descent;
  3132. *x1 = MIN (xs, xe);
  3133. *y1 = MIN (ys, ye);
  3134. *x2 = MAX (xs, xe);
  3135. *y2 = MAX (ys, ye);
  3136. return TRUE;
  3137. }
  3138. return FALSE;
  3139. }
  3140. gboolean
  3141. html_text_prev_link_offset (HTMLText *text,
  3142. gint *offset)
  3143. {
  3144. GSList *l;
  3145. for (l = text->links; l; l = l->next) {
  3146. Link *link = (Link *) l->data;
  3147. if (link->start_offset <= *offset && *offset <= link->end_offset) {
  3148. if (l->next) {
  3149. *offset = ((Link *) l->next->data)->end_offset - 1;
  3150. return TRUE;
  3151. }
  3152. break;
  3153. }
  3154. }
  3155. return FALSE;
  3156. }
  3157. gboolean
  3158. html_text_next_link_offset (HTMLText *text,
  3159. gint *offset)
  3160. {
  3161. GSList *l, *prev = NULL;
  3162. for (l = text->links; l; l = l->next) {
  3163. Link *link = (Link *) l->data;
  3164. if (link->start_offset <= *offset && *offset <= link->end_offset) {
  3165. if (prev) {
  3166. *offset = ((Link *) prev->data)->start_offset + 1;
  3167. return TRUE;
  3168. }
  3169. break;
  3170. }
  3171. prev = l;
  3172. }
  3173. return FALSE;
  3174. }
  3175. gboolean
  3176. html_text_first_link_offset (HTMLText *text,
  3177. gint *offset)
  3178. {
  3179. if (text->links)
  3180. *offset = ((Link *) g_slist_last (text->links)->data)->start_offset + 1;
  3181. return text->links != NULL;
  3182. }
  3183. gboolean
  3184. html_text_last_link_offset (HTMLText *text,
  3185. gint *offset)
  3186. {
  3187. if (text->links)
  3188. *offset = ((Link *) text->links->data)->end_offset - 1;
  3189. return text->links != NULL;
  3190. }
  3191. gchar *
  3192. html_text_get_link_text (HTMLText *text,
  3193. gint offset)
  3194. {
  3195. Link *link = html_text_get_link_at_offset (text, offset);
  3196. gchar *start;
  3197. start = html_text_get_text (text, link->start_offset);
  3198. return g_strndup (start, g_utf8_offset_to_pointer (start, link->end_offset - link->start_offset) - start);
  3199. }
  3200. void
  3201. html_link_set_url_and_target (Link *link,
  3202. gchar *url,
  3203. gchar *target)
  3204. {
  3205. if (!link)
  3206. return;
  3207. g_free (link->url);
  3208. g_free (link->target);
  3209. link->url = g_strdup (url);
  3210. link->target = g_strdup (target);
  3211. }
  3212. Link *
  3213. html_link_dup (Link *l)
  3214. {
  3215. Link *nl = g_new (Link, 1);
  3216. nl->url = g_strdup (l->url);
  3217. nl->target = g_strdup (l->target);
  3218. nl->start_offset = l->start_offset;
  3219. nl->end_offset = l->end_offset;
  3220. nl->start_index = l->start_index;
  3221. nl->end_index = l->end_index;
  3222. nl->is_visited = l->is_visited;
  3223. return nl;
  3224. }
  3225. void
  3226. html_link_free (Link *link)
  3227. {
  3228. g_return_if_fail (link != NULL);
  3229. g_free (link->url);
  3230. g_free (link->target);
  3231. g_free (link);
  3232. }
  3233. gboolean
  3234. html_link_equal (Link *l1,
  3235. Link *l2)
  3236. {
  3237. return l1->url && l2->url && !g_ascii_strcasecmp (l1->url, l2->url)
  3238. && (l1->target == l2->target || (l1->target && l2->target && !g_ascii_strcasecmp (l1->target, l2->target)));
  3239. }
  3240. Link *
  3241. html_link_new (gchar *url,
  3242. gchar *target,
  3243. guint start_index,
  3244. guint end_index,
  3245. gint start_offset,
  3246. gint end_offset,
  3247. gboolean is_visited)
  3248. {
  3249. Link *link = g_new0 (Link, 1);
  3250. link->url = g_strdup (url);
  3251. link->target = g_strdup (target);
  3252. link->start_offset = start_offset;
  3253. link->end_offset = end_offset;
  3254. link->start_index = start_index;
  3255. link->end_index = end_index;
  3256. link->is_visited = is_visited;
  3257. return link;
  3258. }
  3259. /* extended pango attributes */
  3260. static PangoAttribute *
  3261. html_pango_attr_font_size_copy (const PangoAttribute *attr)
  3262. {
  3263. HTMLPangoAttrFontSize *font_size_attr = (HTMLPangoAttrFontSize *) attr, *new_attr;
  3264. new_attr = (HTMLPangoAttrFontSize *) html_pango_attr_font_size_new (font_size_attr->style);
  3265. new_attr->attr_int.value = font_size_attr->attr_int.value;
  3266. return (PangoAttribute *) new_attr;
  3267. }
  3268. static void
  3269. html_pango_attr_font_size_destroy (PangoAttribute *attr)
  3270. {
  3271. g_free (attr);
  3272. }
  3273. static gboolean
  3274. html_pango_attr_font_size_equal (const PangoAttribute *attr1,
  3275. const PangoAttribute *attr2)
  3276. {
  3277. const HTMLPangoAttrFontSize *font_size_attr1 = (const HTMLPangoAttrFontSize *) attr1;
  3278. const HTMLPangoAttrFontSize *font_size_attr2 = (const HTMLPangoAttrFontSize *) attr2;
  3279. return (font_size_attr1->style == font_size_attr2->style);
  3280. }
  3281. void
  3282. html_pango_attr_font_size_calc (HTMLPangoAttrFontSize *attr,
  3283. HTMLEngine *e)
  3284. {
  3285. gint size, base_size, real_size;
  3286. base_size = (attr->style & GTK_HTML_FONT_STYLE_FIXED) ? e->painter->font_manager.fix_size : e->painter->font_manager.var_size;
  3287. if ((attr->style & GTK_HTML_FONT_STYLE_SIZE_MASK) != 0)
  3288. size = (attr->style & GTK_HTML_FONT_STYLE_SIZE_MASK) - GTK_HTML_FONT_STYLE_SIZE_3;
  3289. else
  3290. size = 0;
  3291. real_size = e->painter->font_manager.magnification * ((gdouble) base_size + (size > 0 ? (1 << size) : size) * base_size / 8.0);
  3292. attr->attr_int.value = real_size;
  3293. }
  3294. static const PangoAttrClass html_pango_attr_font_size_klass = {
  3295. PANGO_ATTR_SIZE,
  3296. html_pango_attr_font_size_copy,
  3297. html_pango_attr_font_size_destroy,
  3298. html_pango_attr_font_size_equal
  3299. };
  3300. PangoAttribute *
  3301. html_pango_attr_font_size_new (GtkHTMLFontStyle style)
  3302. {
  3303. HTMLPangoAttrFontSize *result = g_new (HTMLPangoAttrFontSize, 1);
  3304. result->attr_int.attr.klass = &html_pango_attr_font_size_klass;
  3305. result->style = style;
  3306. return (PangoAttribute *) result;
  3307. }
  3308. static gboolean
  3309. calc_font_size_filter (PangoAttribute *attr,
  3310. gpointer data)
  3311. {
  3312. HTMLEngine *e = HTML_ENGINE (data);
  3313. if (attr->klass->type == PANGO_ATTR_SIZE)
  3314. html_pango_attr_font_size_calc ((HTMLPangoAttrFontSize *) attr, e);
  3315. else if (attr->klass->type == PANGO_ATTR_FAMILY) {
  3316. /* FIXME: this is not very nice. we set it here as it's only used when fonts changed.
  3317. * once family in style is used again, that code must be updated */
  3318. PangoAttrString *sa = (PangoAttrString *) attr;
  3319. g_free (sa->value);
  3320. sa->value = g_strdup (e->painter->font_manager.fixed.face ? e->painter->font_manager.fixed.face : "Monospace");
  3321. }
  3322. return FALSE;
  3323. }
  3324. void
  3325. html_text_calc_font_size (HTMLText *text,
  3326. HTMLEngine *e)
  3327. {
  3328. pango_attr_list_filter (text->attr_list, calc_font_size_filter, e);
  3329. }
  3330. static GtkHTMLFontStyle
  3331. style_from_attrs (PangoAttrIterator *iter)
  3332. {
  3333. GtkHTMLFontStyle style = GTK_HTML_FONT_STYLE_DEFAULT;
  3334. GSList *list, *l;
  3335. list = pango_attr_iterator_get_attrs (iter);
  3336. for (l = list; l; l = l->next) {
  3337. PangoAttribute *attr = (PangoAttribute *) l->data;
  3338. switch (attr->klass->type) {
  3339. case PANGO_ATTR_WEIGHT:
  3340. style |= GTK_HTML_FONT_STYLE_BOLD;
  3341. break;
  3342. case PANGO_ATTR_UNDERLINE:
  3343. style |= GTK_HTML_FONT_STYLE_UNDERLINE;
  3344. break;
  3345. case PANGO_ATTR_STRIKETHROUGH:
  3346. style |= GTK_HTML_FONT_STYLE_STRIKEOUT;
  3347. break;
  3348. case PANGO_ATTR_STYLE:
  3349. style |= GTK_HTML_FONT_STYLE_ITALIC;
  3350. break;
  3351. case PANGO_ATTR_SIZE:
  3352. style |= ((HTMLPangoAttrFontSize *) attr)->style;
  3353. break;
  3354. case PANGO_ATTR_FAMILY:
  3355. style |= GTK_HTML_FONT_STYLE_FIXED;
  3356. break;
  3357. default:
  3358. break;
  3359. }
  3360. }
  3361. html_text_free_attrs (list);
  3362. return style;
  3363. }
  3364. GtkHTMLFontStyle
  3365. html_text_get_fontstyle_at_index (HTMLText *text,
  3366. gint index)
  3367. {
  3368. GtkHTMLFontStyle style = GTK_HTML_FONT_STYLE_DEFAULT;
  3369. PangoAttrIterator *iter = pango_attr_list_get_iterator (text->attr_list);
  3370. if (iter) {
  3371. do {
  3372. gint start_index, end_index;
  3373. pango_attr_iterator_range (iter, &start_index, &end_index);
  3374. if (start_index <= index && index <= end_index) {
  3375. style |= style_from_attrs (iter);
  3376. break;
  3377. }
  3378. } while (pango_attr_iterator_next (iter));
  3379. pango_attr_iterator_destroy (iter);
  3380. }
  3381. return style;
  3382. }
  3383. GtkHTMLFontStyle
  3384. html_text_get_style_conflicts (HTMLText *text,
  3385. GtkHTMLFontStyle style,
  3386. gint start_index,
  3387. gint end_index)
  3388. {
  3389. GtkHTMLFontStyle conflicts = GTK_HTML_FONT_STYLE_DEFAULT;
  3390. PangoAttrIterator *iter = pango_attr_list_get_iterator (text->attr_list);
  3391. if (iter) {
  3392. do {
  3393. gint iter_start_index, iter_end_index;
  3394. pango_attr_iterator_range (iter, &iter_start_index, &iter_end_index);
  3395. if (MAX (start_index, iter_start_index) < MIN (end_index, iter_end_index))
  3396. conflicts |= style_from_attrs (iter) ^ style;
  3397. if (iter_start_index > end_index)
  3398. break;
  3399. } while (pango_attr_iterator_next (iter));
  3400. pango_attr_iterator_destroy (iter);
  3401. }
  3402. return conflicts;
  3403. }
  3404. void
  3405. html_text_change_attrs (PangoAttrList *attr_list,
  3406. GtkHTMLFontStyle style,
  3407. HTMLEngine *e,
  3408. gint start_index,
  3409. gint end_index,
  3410. gboolean avoid_default_size)
  3411. {
  3412. PangoAttribute *attr;
  3413. /* style */
  3414. if (style & GTK_HTML_FONT_STYLE_BOLD) {
  3415. attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
  3416. attr->start_index = start_index;
  3417. attr->end_index = end_index;
  3418. pango_attr_list_change (attr_list, attr);
  3419. }
  3420. if (style & GTK_HTML_FONT_STYLE_ITALIC) {
  3421. attr = pango_attr_style_new (PANGO_STYLE_ITALIC);
  3422. attr->start_index = start_index;
  3423. attr->end_index = end_index;
  3424. pango_attr_list_change (attr_list, attr);
  3425. }
  3426. if (style & GTK_HTML_FONT_STYLE_UNDERLINE) {
  3427. attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
  3428. attr->start_index = start_index;
  3429. attr->end_index = end_index;
  3430. pango_attr_list_change (attr_list, attr);
  3431. }
  3432. if (style & GTK_HTML_FONT_STYLE_STRIKEOUT) {
  3433. attr = pango_attr_strikethrough_new (TRUE);
  3434. attr->start_index = start_index;
  3435. attr->end_index = end_index;
  3436. pango_attr_list_change (attr_list, attr);
  3437. }
  3438. if (style & GTK_HTML_FONT_STYLE_FIXED) {
  3439. attr = pango_attr_family_new (e->painter->font_manager.fixed.face ? e->painter->font_manager.fixed.face : "Monospace");
  3440. attr->start_index = start_index;
  3441. attr->end_index = end_index;
  3442. pango_attr_list_change (attr_list, attr);
  3443. }
  3444. if (!avoid_default_size
  3445. || (((style & GTK_HTML_FONT_STYLE_SIZE_MASK) != GTK_HTML_FONT_STYLE_DEFAULT)
  3446. && ((style & GTK_HTML_FONT_STYLE_SIZE_MASK) != GTK_HTML_FONT_STYLE_SIZE_3))
  3447. || ((style & GTK_HTML_FONT_STYLE_FIXED) &&
  3448. e->painter->font_manager.fix_size != e->painter->font_manager.var_size)) {
  3449. attr = html_pango_attr_font_size_new (style);
  3450. html_pango_attr_font_size_calc ((HTMLPangoAttrFontSize *) attr, e);
  3451. attr->start_index = start_index;
  3452. attr->end_index = end_index;
  3453. pango_attr_list_change (attr_list, attr);
  3454. }
  3455. }
  3456. void
  3457. html_text_set_style_in_range (HTMLText *text,
  3458. GtkHTMLFontStyle style,
  3459. HTMLEngine *e,
  3460. gint start_index,
  3461. gint end_index)
  3462. {
  3463. html_text_change_attrs (text->attr_list, style, e, start_index, end_index, TRUE);
  3464. }
  3465. void
  3466. html_text_set_style (HTMLText *text,
  3467. GtkHTMLFontStyle style,
  3468. HTMLEngine *e)
  3469. {
  3470. html_text_set_style_in_range (text, style, e, 0, text->text_bytes);
  3471. }
  3472. static gboolean
  3473. unset_style_filter (PangoAttribute *attr,
  3474. gpointer data)
  3475. {
  3476. GtkHTMLFontStyle style = GPOINTER_TO_INT (data);
  3477. switch (attr->klass->type) {
  3478. case PANGO_ATTR_WEIGHT:
  3479. if (style & GTK_HTML_FONT_STYLE_BOLD)
  3480. return TRUE;
  3481. break;
  3482. case PANGO_ATTR_STYLE:
  3483. if (style & GTK_HTML_FONT_STYLE_ITALIC)
  3484. return TRUE;
  3485. break;
  3486. case PANGO_ATTR_UNDERLINE:
  3487. if (style & GTK_HTML_FONT_STYLE_UNDERLINE)
  3488. return TRUE;
  3489. break;
  3490. case PANGO_ATTR_STRIKETHROUGH:
  3491. if (style & GTK_HTML_FONT_STYLE_STRIKEOUT)
  3492. return TRUE;
  3493. break;
  3494. case PANGO_ATTR_SIZE:
  3495. if (((HTMLPangoAttrFontSize *) attr)->style & style)
  3496. return TRUE;
  3497. break;
  3498. case PANGO_ATTR_FAMILY:
  3499. if (style & GTK_HTML_FONT_STYLE_FIXED)
  3500. return TRUE;
  3501. break;
  3502. default:
  3503. break;
  3504. }
  3505. return FALSE;
  3506. }
  3507. void
  3508. html_text_unset_style (HTMLText *text,
  3509. GtkHTMLFontStyle style)
  3510. {
  3511. pango_attr_list_filter (text->attr_list, unset_style_filter, GINT_TO_POINTER (style));
  3512. }
  3513. static HTMLColor *
  3514. color_from_attrs (PangoAttrIterator *iter)
  3515. {
  3516. HTMLColor *color = NULL;
  3517. GSList *list, *l;
  3518. list = pango_attr_iterator_get_attrs (iter);
  3519. for (l = list; l; l = l->next) {
  3520. PangoAttribute *attr = (PangoAttribute *) l->data;
  3521. PangoAttrColor *ca;
  3522. switch (attr->klass->type) {
  3523. case PANGO_ATTR_FOREGROUND:
  3524. ca = (PangoAttrColor *) attr;
  3525. color = html_color_new_from_rgb (ca->color.red, ca->color.green, ca->color.blue);
  3526. break;
  3527. default:
  3528. break;
  3529. }
  3530. }
  3531. html_text_free_attrs (list);
  3532. return color;
  3533. }
  3534. static HTMLColor *
  3535. html_text_get_first_color_in_range (HTMLText *text,
  3536. HTMLEngine *e,
  3537. gint start_index,
  3538. gint end_index)
  3539. {
  3540. HTMLColor *color = NULL;
  3541. PangoAttrIterator *iter = pango_attr_list_get_iterator (text->attr_list);
  3542. if (iter) {
  3543. do {
  3544. gint iter_start_index, iter_end_index;
  3545. pango_attr_iterator_range (iter, &iter_start_index, &iter_end_index);
  3546. if (MAX (iter_start_index, start_index) <= MIN (iter_end_index, end_index)) {
  3547. color = color_from_attrs (iter);
  3548. break;
  3549. }
  3550. } while (pango_attr_iterator_next (iter));
  3551. pango_attr_iterator_destroy (iter);
  3552. }
  3553. if (!color) {
  3554. color = html_colorset_get_color (e->settings->color_set, HTMLTextColor);
  3555. html_color_ref (color);
  3556. }
  3557. return color;
  3558. }
  3559. HTMLColor *
  3560. html_text_get_color_at_index (HTMLText *text,
  3561. HTMLEngine *e,
  3562. gint index)
  3563. {
  3564. return html_text_get_first_color_in_range (text, e, index, index);
  3565. }
  3566. HTMLColor *
  3567. html_text_get_color (HTMLText *text,
  3568. HTMLEngine *e,
  3569. gint start_index)
  3570. {
  3571. return html_text_get_first_color_in_range (text, e, start_index, text->text_bytes);
  3572. }
  3573. void
  3574. html_text_set_color_in_range (HTMLText *text,
  3575. HTMLColor *color,
  3576. gint start_index,
  3577. gint end_index)
  3578. {
  3579. PangoAttribute *attr = pango_attr_foreground_new (color->color.red, color->color.green, color->color.blue);
  3580. attr->start_index = start_index;
  3581. attr->end_index = end_index;
  3582. pango_attr_list_change (text->attr_list, attr);
  3583. }
  3584. void
  3585. html_text_set_color (HTMLText *text,
  3586. HTMLColor *color)
  3587. {
  3588. html_text_set_color_in_range (text, color, 0, text->text_bytes);
  3589. }
  3590. HTMLDirection
  3591. html_text_direction_pango_to_html (PangoDirection pdir)
  3592. {
  3593. switch (pdir) {
  3594. case PANGO_DIRECTION_RTL:
  3595. return HTML_DIRECTION_RTL;
  3596. case PANGO_DIRECTION_LTR:
  3597. return HTML_DIRECTION_LTR;
  3598. default:
  3599. return HTML_DIRECTION_DERIVED;
  3600. }
  3601. }
  3602. void
  3603. html_text_change_set (HTMLText *text,
  3604. HTMLChangeFlags flags)
  3605. {
  3606. HTMLObject *slave = HTML_OBJECT (text)->next;
  3607. for (; slave && HTML_IS_TEXT_SLAVE (slave) && HTML_TEXT_SLAVE (slave)->owner == text; slave = slave->next)
  3608. slave->change |= flags;
  3609. html_object_change_set (HTML_OBJECT (text), flags);
  3610. }
  3611. void
  3612. html_text_set_link_visited (HTMLText *text,
  3613. gint offset,
  3614. HTMLEngine *engine,
  3615. gboolean is_visited)
  3616. {
  3617. HTMLEngine *object_engine = html_object_engine (HTML_OBJECT (text),engine);
  3618. Link *link = html_text_get_link_at_offset (text,offset);
  3619. if (link) {
  3620. link->is_visited = is_visited;
  3621. html_text_change_set (text, HTML_CHANGE_RECALC_PI);
  3622. html_text_queue_draw (text, object_engine, offset, 1);
  3623. html_engine_flush_draw_queue (object_engine);
  3624. }
  3625. }