diff --git a/demo/demo.c b/demo/demo.c index d40d694..9f9e49e 100644 --- a/demo/demo.c +++ b/demo/demo.c @@ -29,6 +29,7 @@ #include #include +#include #define DEMO_TYPE_WINDOW demo_window_get_type () G_DECLARE_FINAL_TYPE (DemoWindow, demo_window, DEMO, WINDOW, AdwApplicationWindow) @@ -138,7 +139,25 @@ demo_window_init (DemoWindow *self) } // test = "

There was an Old Man with a beard

Who said, "It is just as I feared!

> Two Owls and a Hen,
> Four Larks and a Wren,

Have all built their nests in my beard!"

"; - frame = format_parse_html (contents); + // frame = format_parse_html (contents); + frame = text_frame_new (); + + TextParagraph *paragraph = text_paragraph_new (); + text_paragraph_append_fragment(paragraph, TEXT_FRAGMENT(text_run_new("Hello World. "))); + text_paragraph_append_fragment(paragraph, TEXT_FRAGMENT(text_run_new("This is some text"))); + text_frame_append_block (frame, TEXT_BLOCK (paragraph)); + + paragraph = text_paragraph_new (); + // text_paragraph_append_fragment (paragraph, TEXT_FRAGMENT (text_run_new (""))); + text_paragraph_append_fragment(paragraph, TEXT_FRAGMENT(text_image_new("screenshot.png"))); + // text_paragraph_append_fragment (paragraph, TEXT_FRAGMENT (text_run_new (""))); + text_frame_append_block (frame, TEXT_BLOCK (paragraph)); + + paragraph = text_paragraph_new (); + text_paragraph_append_fragment(paragraph, TEXT_FRAGMENT(text_run_new("And some more text... "))); + text_paragraph_append_fragment(paragraph, TEXT_FRAGMENT(text_run_new(":)"))); + text_frame_append_block (frame, TEXT_BLOCK (paragraph)); + document = text_document_new (); document->frame = frame; diff --git a/demo/demo.html b/demo/demo.html index 8bd70fa..b1aedeb 100644 --- a/demo/demo.html +++ b/demo/demo.html @@ -1,5 +1,6 @@ -

GNOME (/ɡəˈnoʊm, ˈnoʊm/), originally an acronym for GNU Network Object Model Environment, is a free and open-source desktop environment for Linux and other Unix-like operating systems.

GNOME is developed by the GNOME Project, which is composed of both volunteers and paid contributors, the largest corporate contributor being Red Hat.

+

GNOME (/ɡəˈnoʊm, ˈnoʊm/), originally an acronym for GNU Network Object Model Environment, is a free and open-source desktop environment for Linux and other Unix-like operating systems. GNOME is developed by the GNOME Project, which is composed of both volunteers and paid contributors, the largest corporate contributor being Red Hat.

It is an international project that aims to develop frameworks for software development, to program end-user applications based on these frameworks, and to coordinate efforts for internationalization and localization and accessibility of that software.

+

athis is some alt textb

GNOME is the default desktop environment of many major Linux distributions, including Debian, Endless OS, Fedora Linux, Red Hat Enterprise Linux, SUSE Linux Enterprise, Ubuntu, and Tails; it is also the default of Solaris, a Unix operating system.

This text is sourced from Wikipedia under the CC BY-SA 3.0 license. The original document can be found here: https://en.wikipedia.org/wiki/GNOME.

diff --git a/src/editor/editor.c b/src/editor/editor.c index ebd97bc..0d4e894 100644 --- a/src/editor/editor.c +++ b/src/editor/editor.c @@ -12,6 +12,7 @@ #include "editor.h" #include "../model/paragraph.h" +#include "../model/opaque.h" struct _TextEditor { @@ -101,6 +102,16 @@ text_editor_class_init (TextEditorClass *klass) g_object_class_install_properties (object_class, N_PROPS, properties); } +static int +_get_offset (TextParagraph *paragraph, + int byte_index) +{ + // NOTE: byte_index must be within bounds! + const char *text; + text = text_paragraph_get_text (paragraph); + return (int) g_utf8_pointer_to_offset (text, text + byte_index); +} + static TextItem * go_up (TextItem *item, gboolean forwards) @@ -127,8 +138,8 @@ go_up (TextItem *item, return NULL; } -static TextRun * -walk_until_previous_run (TextItem *item) +static TextFragment * +walk_until_previous_fragment (TextItem *item) { TextNode *child; TextNode *sibling; @@ -139,38 +150,38 @@ walk_until_previous_run (TextItem *item) child = text_node_get_last_child (TEXT_NODE (item)); if (child && TEXT_IS_ITEM (child)) { - if (TEXT_IS_RUN (child)) { - return TEXT_RUN (child); + if (TEXT_IS_FRAGMENT (child)) { + return TEXT_FRAGMENT (child); } - return walk_until_previous_run (TEXT_ITEM (child)); + return walk_until_previous_fragment(TEXT_ITEM(child)); } sibling = text_node_get_previous (TEXT_NODE (item)); if (sibling && TEXT_IS_ITEM (sibling)) { - if (TEXT_IS_RUN (sibling)) { - return TEXT_RUN (sibling); + if (TEXT_IS_FRAGMENT (sibling)) { + return TEXT_FRAGMENT (sibling); } - return walk_until_previous_run (TEXT_ITEM (sibling)); + return walk_until_previous_fragment(TEXT_ITEM(sibling)); } parent = go_up (item, FALSE); if (parent) { - if (TEXT_IS_RUN (parent)) { - return TEXT_RUN (parent); + if (TEXT_IS_FRAGMENT (parent)) { + return TEXT_FRAGMENT (parent); } - return walk_until_previous_run (parent); + return walk_until_previous_fragment(parent); } return NULL; } -static TextRun * -walk_until_next_run (TextItem *item) +static TextFragment * +walk_until_next_fragment (TextItem *item) { TextNode *child; TextNode *sibling; @@ -181,31 +192,31 @@ walk_until_next_run (TextItem *item) child = text_node_get_first_child (TEXT_NODE (item)); if (child && TEXT_IS_ITEM (child)) { - if (TEXT_IS_RUN (child)) { - return TEXT_RUN (child); + if (TEXT_IS_FRAGMENT (child)) { + return TEXT_FRAGMENT (child); } - return walk_until_next_run (TEXT_ITEM (child)); + return walk_until_next_fragment(TEXT_ITEM(child)); } sibling = text_node_get_next (TEXT_NODE (item)); if (sibling && TEXT_IS_ITEM (sibling)) { - if (TEXT_IS_RUN (sibling)) { - return TEXT_RUN (sibling); + if (TEXT_IS_FRAGMENT (sibling)) { + return TEXT_FRAGMENT (sibling); } - return walk_until_next_run (TEXT_ITEM (sibling)); + return walk_until_next_fragment(TEXT_ITEM(sibling)); } parent = go_up (item, TRUE); if (parent) { - if (TEXT_IS_RUN (parent)) { - return TEXT_RUN (parent); + if (TEXT_IS_FRAGMENT (parent)) { + return TEXT_FRAGMENT (parent); } - return walk_until_next_run (parent); + return walk_until_next_fragment(parent); } return NULL; @@ -295,16 +306,37 @@ walk_until_next_paragraph (TextItem *item) return NULL; } -TextRun * -text_editor_get_run_at_mark (TextEditor *self, - TextMark *mark) +TextFragment * +text_editor_get_item_at_mark (TextEditor *self, + TextMark *mark) { g_return_val_if_fail (TEXT_IS_EDITOR (self), NULL); g_return_val_if_fail (mark != NULL, NULL); - return text_paragraph_get_run_at_index (mark->paragraph, - mark->index, - NULL); + return text_paragraph_get_item_at_index (mark->paragraph, + mark->index, + NULL); +} + +static void +move_n_chars_to_byte_index (const gchar *text, + int *byte_index, + int amount) +{ + // NOTE: Not bounds checked! Make sure to check the + // string length with g_utf8_strlen() or similar before + // calling. + + const char *new_ptr; + + g_return_if_fail (text != NULL); + g_return_if_fail (byte_index != NULL); + + // Go 'amount' characters forwards and find address + new_ptr = g_utf8_offset_to_pointer (text + *byte_index, amount); + + // Calculate new byte offset + *byte_index = (int)(new_ptr - text); } /** @@ -315,22 +347,21 @@ text_editor_get_run_at_mark (TextEditor *self, * * @self: `TextEditor` instance * @mark: the `TextMark` to move - * @amount: Amount to move by + * @amount: Amount to move by (in characters) * * Returns: The distance remaining or -1 if an error occurred. */ int -_try_move_mark_left (TextEditor *self, - TextMark *mark, +_try_move_mark_left (TextMark *mark, int amount) { TextParagraph *iter; + const gchar *text; int amount_moved; + int mark_char_offset; - g_return_val_if_fail (TEXT_IS_EDITOR (self), -1); - g_return_val_if_fail (TEXT_IS_DOCUMENT (self->document), -1); - g_return_val_if_fail (amount >= 0, -1); g_return_val_if_fail (mark != NULL, -1); + g_return_val_if_fail (amount >= 0, -1); if (amount == 0) return 0; @@ -338,16 +369,21 @@ _try_move_mark_left (TextEditor *self, iter = mark->paragraph; amount_moved = 0; + text = text_paragraph_get_text (iter); + + // Calculate how many characters into the paragraph the mark is + mark_char_offset = (int) g_utf8_strlen (text, mark->index); + // Simple case: The movement is contained entirely // within the current paragraph. - if (mark->index - amount >= 0) + if (mark_char_offset - amount >= 0) { - mark->index -= amount; + move_n_chars_to_byte_index (text, &mark->index, -amount); return 0; } // Crossing one or more paragraphs - amount_moved += mark->index; + amount_moved += mark_char_offset; while (amount_moved < amount) { @@ -374,13 +410,15 @@ _try_move_mark_left (TextEditor *self, // Move partially through the paragraph by // the amount to move remaining - mark->index = num_indices - (amount - amount_moved); + // TODO: Check if this actually works + text = text_paragraph_get_text (iter); + mark->index = (int) (g_utf8_offset_to_pointer (text, num_indices - (amount - amount_moved)) - text); mark->paragraph = iter; return 0; } // Reached start of document - text_editor_move_mark_first (self, mark); + text_editor_move_mark_first (mark); return amount - amount_moved; } @@ -396,11 +434,10 @@ _try_move_mark_left (TextEditor *self, * @amount: Amount to move by */ void -text_editor_move_mark_left (TextEditor *self, - TextMark *mark, +text_editor_move_mark_left (TextMark *mark, int amount) { - _try_move_mark_left (self, mark, amount); + _try_move_mark_left (mark, amount); } /** @@ -416,18 +453,17 @@ text_editor_move_mark_left (TextEditor *self, * Returns: The distance remaining or -1 if an error occurred. */ int -_try_move_mark_right (TextEditor *self, - TextMark *mark, +_try_move_mark_right (TextMark *mark, int amount) { TextParagraph *iter; + gchar *text; int amount_moved; int last_index; + int mark_char_offset; - g_return_val_if_fail (TEXT_IS_EDITOR (self), -1); - g_return_val_if_fail (TEXT_IS_DOCUMENT (self->document), -1); - g_return_val_if_fail (amount >= 0, -1); g_return_val_if_fail (mark != NULL, -1); + g_return_val_if_fail (amount >= 0, -1); if (amount == 0) return 0; @@ -435,17 +471,22 @@ _try_move_mark_right (TextEditor *self, iter = mark->paragraph; amount_moved = 0; + text = text_paragraph_get_text (iter); + + // Calculate how many characters into the paragraph the mark is + mark_char_offset = (int) g_utf8_strlen (text, mark->index); + // Simple case: The movement is contained entirely // within the current paragraph. last_index = text_paragraph_get_length (iter); - if (mark->index + amount <= last_index) + if (mark_char_offset + amount <= last_index) { - mark->index += amount; + move_n_chars_to_byte_index (text, &mark->index, amount); return 0; } // Crossing one or more paragraphs - amount_moved += (last_index - mark->index); + amount_moved += (last_index - mark_char_offset); while (amount_moved < amount) { @@ -472,13 +513,15 @@ _try_move_mark_right (TextEditor *self, // Move partially through the paragraph by // the amount to move remaining - mark->index = (amount - amount_moved) - 1; + // TODO: Check if this actually works + text = text_paragraph_get_text (iter); + mark->index = (int) (g_utf8_offset_to_pointer (text, (amount - amount_moved) - 1) - text); mark->paragraph = iter; return 0; } // Reached end of document - text_editor_move_mark_last (self, mark); + text_editor_move_mark_last (mark); return amount - amount_moved; } @@ -494,11 +537,10 @@ _try_move_mark_right (TextEditor *self, * @amount: Amount to move by */ void -text_editor_move_mark_right (TextEditor *self, - TextMark *mark, +text_editor_move_mark_right (TextMark *mark, int amount) { - _try_move_mark_right (self, mark, amount); + _try_move_mark_right (mark, amount); } /** @@ -506,17 +548,15 @@ text_editor_move_mark_right (TextEditor *self, * * Move the mark to the beginning of the document. * - * @self: `TextEditor` instance * @mark: The `TextMark` to move */ void -text_editor_move_mark_first (TextEditor *self, - TextMark *mark) +text_editor_move_mark_first (TextMark *mark) { - g_return_if_fail (TEXT_IS_EDITOR (self)); - g_return_if_fail (TEXT_IS_DOCUMENT (self->document)); + g_return_if_fail (mark != NULL); + g_return_if_fail (TEXT_IS_DOCUMENT (mark->document)); - mark->paragraph = walk_until_next_paragraph (TEXT_ITEM (self->document->frame)); + mark->paragraph = walk_until_next_paragraph (TEXT_ITEM (mark->document->frame)); mark->index = 0; } @@ -525,22 +565,20 @@ text_editor_move_mark_first (TextEditor *self, * * Move the mark to the end of the document. * - * @self: `TextEditor` instance * @mark: The `TextMark` to move */ void -text_editor_move_mark_last (TextEditor *self, - TextMark *mark) +text_editor_move_mark_last (TextMark *mark) { - g_return_if_fail (TEXT_IS_EDITOR (self)); - g_return_if_fail (TEXT_IS_DOCUMENT (self->document)); + g_return_if_fail (mark != NULL); + g_return_if_fail (TEXT_IS_DOCUMENT (mark->document)); - mark->paragraph = walk_until_previous_paragraph (TEXT_ITEM (self->document->frame)); + mark->paragraph = walk_until_previous_paragraph (TEXT_ITEM (mark->document->frame)); mark->index = 0; if (mark->paragraph) { - mark->index = text_paragraph_get_length (mark->paragraph); + mark->index = text_paragraph_get_size_bytes (mark->paragraph); } } @@ -553,11 +591,11 @@ _ensure_paragraph (TextEditor *self) document_frame = self->document->frame; // Add paragraph with run if none exists - if (!walk_until_next_run (TEXT_ITEM (document_frame))) + if (!walk_until_next_fragment(TEXT_ITEM(document_frame))) { paragraph = text_paragraph_new (); text_frame_append_block (document_frame, TEXT_BLOCK (paragraph)); - text_paragraph_append_run (paragraph, text_run_new ("")); + text_paragraph_append_fragment(paragraph, text_run_new("")); } } @@ -590,18 +628,59 @@ _delete_run (TextEditor *self, static void _erase_text (TextRun *run, int index, - int length) + int num_chars, + int *bytes_deleted) { GString *modified; - char *text; + const char *text; + int length_bytes; - g_object_get (run, "text", &text, NULL); + text = text_fragment_get_text (TEXT_FRAGMENT (run)); + + // Calculate length (in bytes) to erase + length_bytes = (int) (g_utf8_offset_to_pointer (text + index, num_chars) - (text + index)); + + g_assert (index + length_bytes <= strlen (text)); // Assumes index and length are within range modified = g_string_new (text); - modified = g_string_erase (modified, index, length); + modified = g_string_erase (modified, index, length_bytes); g_object_set (run, "text", modified->str, NULL); g_string_free (modified, TRUE); + + if (bytes_deleted) + *bytes_deleted = length_bytes; +} + +static void +_erase_content (TextFragment *item, + int index, + int num_chars, + int *bytes_deleted) +{ + // Initialise to zero + if (bytes_deleted) + *bytes_deleted = 0; + + if (TEXT_IS_RUN (item)) + { + _erase_text (TEXT_RUN (item), index, num_chars, bytes_deleted); + } + else if (TEXT_IS_OPAQUE (item)) + { + TextParagraph *parent; + + if (bytes_deleted) + *bytes_deleted = text_fragment_get_size_bytes (item); + + parent = TEXT_PARAGRAPH (text_node_get_parent (TEXT_NODE (item))); + text_node_insert_child_before (TEXT_NODE (parent), TEXT_NODE (text_run_new ("")), TEXT_NODE (item)); + text_node_delete (TEXT_NODE (item)); + } + else + { + g_print ("Cannot delete a non-opaque inline element"); + } } static void @@ -621,7 +700,7 @@ _join_paragraphs (TextParagraph *start, TextNode *swap; swap = iter; - g_assert (TEXT_IS_RUN (swap)); + g_assert (TEXT_IS_FRAGMENT (swap)); iter = text_node_get_next (iter); @@ -638,45 +717,51 @@ _join_paragraphs (TextParagraph *start, * _delete_within_paragraph: * * @paragraph: Paragraph the deletion will be performed on - * @start_index: Starting index of the deletion. It is the caller's - * responsibility to ensure this is in-range. - * @deletion_length: The number of characters to be erased. + * @start_index: Starting byte index of the deletion. Note + * this is a byte index, not a unicode character offset + * @deletion_length: The number of unicode characters to + * be deleted. * * Returns: `TRUE` if the paragraph was deleted */ static gboolean _delete_within_paragraph (TextParagraph *paragraph, int start_index, - int deletion_length) + int deletion_length, + int *bytes_deleted) { - TextRun *start; - TextRun *end; + TextFragment *start; int paragraph_length; + int paragraph_size; int end_index; int start_run_offset; gboolean is_only; + const char *text; if (deletion_length == 0) return FALSE; + text = text_paragraph_get_text (paragraph); paragraph_length = text_paragraph_get_length (paragraph); - end_index = start_index + deletion_length; + paragraph_size = text_paragraph_get_size_bytes(paragraph); is_only = text_node_get_num_children (TEXT_NODE (paragraph)) == 1; // Check run immediately after the start_index // (as start_index may be the final index of a run) - start = text_paragraph_get_run_at_index (paragraph, start_index + 1, &start_run_offset); - end = text_paragraph_get_run_at_index (paragraph, end_index, NULL); + start = text_paragraph_get_item_at_index (paragraph, start_index + 1, &start_run_offset); + end_index = (int) (g_utf8_offset_to_pointer (text + start_index, deletion_length) - text); - g_assert (0 <= start_index && start_index <= paragraph_length); - g_assert (0 <= end_index && end_index <= paragraph_length); + g_return_val_if_fail (bytes_deleted != NULL, -1); + g_assert (0 <= start_index && start_index <= paragraph_size); + g_assert (0 <= end_index && end_index <= paragraph_size); g_assert (start_index <= end_index); // Case 1: The whole paragraph is to be deleted - if (start_index == 0 && end_index == paragraph_length + 1) + if (start_index == 0 && deletion_length == paragraph_length + 1) { text_node_delete (TEXT_NODE (paragraph)); + *bytes_deleted = 0; return TRUE; } @@ -685,34 +770,38 @@ _delete_within_paragraph (TextParagraph *paragraph, { // The paragraph should not be deleted (handled // by above condition) so erase contents - _erase_text (start, start_index, deletion_length); + _erase_content (start, start_index, deletion_length, bytes_deleted); return FALSE; } // Case 3: The paragraph contains multiple runs { int run_length; - TextRun *iter; + int run_bytes_deleted; + TextFragment *iter; int cur_deleted; - int offset_within_run; + int index_within_run; - run_length = text_run_get_length (start); - offset_within_run = start_index - start_run_offset; + run_length = text_fragment_get_length (TEXT_FRAGMENT (start)); + index_within_run = start_index - start_run_offset; cur_deleted = 0; iter = start; + *bytes_deleted = 0; + // If only part of the first run should be erased, handle it here. - if (offset_within_run != 0 || deletion_length != run_length) + if (index_within_run != 0 || deletion_length != run_length) { int to_delete; - to_delete = MIN (deletion_length, run_length - offset_within_run); + to_delete = MIN (deletion_length, run_length - _get_offset (paragraph, index_within_run)); cur_deleted += to_delete; - iter = walk_until_next_run (TEXT_ITEM (start)); + iter = walk_until_next_fragment(TEXT_ITEM(start)); // Delete part of run - _erase_text (start, offset_within_run, to_delete); + _erase_content (start, index_within_run, to_delete, &run_bytes_deleted); + *bytes_deleted += run_bytes_deleted; } // Iterate over the remaining runs @@ -720,24 +809,27 @@ _delete_within_paragraph (TextParagraph *paragraph, { g_assert (iter != NULL); - run_length = text_run_get_length (iter); + run_bytes_deleted = text_fragment_get_size_bytes (TEXT_FRAGMENT (iter)); + run_length = text_fragment_get_length (TEXT_FRAGMENT (iter)); // Check if the run is entirely contained within the deletion if (cur_deleted + run_length <= deletion_length) { - TextRun *next; + TextFragment *next; - next = walk_until_next_run (TEXT_ITEM (iter)); + next = walk_until_next_fragment(TEXT_ITEM(iter)); text_node_delete (TEXT_NODE (iter)); cur_deleted += run_length; + *bytes_deleted += run_bytes_deleted; iter = next; continue; } // Handle last element - _erase_text (iter, 0, deletion_length - cur_deleted); + _erase_content (iter, 0, deletion_length - cur_deleted, &run_bytes_deleted); + *bytes_deleted += run_bytes_deleted; break; } } @@ -777,9 +869,9 @@ _distribute_mark (TextMark *mark, static void _offset_mark (TextMark *mark, - int offset) + int byte_offset) { - mark->index += offset; + mark->index += byte_offset; } void @@ -789,6 +881,8 @@ text_editor_delete_at_mark (TextEditor *self, { TextParagraph *paragraph; int num_indices; + int start_char_offset; + const char *text; g_return_if_fail (TEXT_IS_EDITOR (self)); g_return_if_fail (TEXT_IS_DOCUMENT (self->document)); @@ -804,19 +898,25 @@ text_editor_delete_at_mark (TextEditor *self, // Handles case where the cursor cannot be moved by the // full '-length' because the start of document is reached // TODO: Should this refer to cursor directly? - remaining = _try_move_mark_left (self, self->document->cursor, -length); + remaining = _try_move_mark_left (self->document->cursor, -length); text_editor_delete_at_mark (self, start, (-length - remaining)); return; } + text = text_paragraph_get_text (start->paragraph); + + // Calculate how many characters into the paragraph the mark is + start_char_offset = (int) g_utf8_strlen (text, start->index); + // Account for final index at the end of a paragraph num_indices = text_paragraph_get_length (start->paragraph) + 1; // Case 1: Deletion affects a single paragraph // Any portion of a paragraph up to the entire thing - if (start->index + length < num_indices) + if (start_char_offset + length < num_indices) { int new_index; + int bytes_deleted; TextParagraph *new_para; TextParagraph *prev; @@ -825,12 +925,12 @@ text_editor_delete_at_mark (TextEditor *self, new_index = start->index; // True if paragraph was deleted - if (_delete_within_paragraph (start->paragraph, start->index, length)) + if (_delete_within_paragraph (start->paragraph, start->index, length, &bytes_deleted)) { if (prev) { new_para = prev; - new_index = text_paragraph_get_length (prev); + new_index = text_paragraph_get_size_bytes (prev); } else { @@ -856,7 +956,7 @@ text_editor_delete_at_mark (TextEditor *self, // Marks within affected area if (mark->paragraph == start->paragraph && start->index <= mark->index && - mark->index <= start->index + length) + mark->index <= start->index + bytes_deleted) { _distribute_mark (mark, new_para, new_index, new_para, new_index); @@ -864,9 +964,9 @@ text_editor_delete_at_mark (TextEditor *self, // Marks after affected area else if (mark->paragraph == start->paragraph && - start->index + length < mark->index) + start->index + bytes_deleted < mark->index) { - _offset_mark (mark, -length); + _offset_mark (mark, -bytes_deleted); } } @@ -882,6 +982,7 @@ text_editor_delete_at_mark (TextEditor *self, TextParagraph *iter; int cur_deleted; int paragraph_length; + int bytes_deleted; int to_delete; int remaining; @@ -893,8 +994,8 @@ text_editor_delete_at_mark (TextEditor *self, // Handle first paragraph // Erase part or all of the first paragraph but do not delete it - to_delete = paragraph_length - start->index; - _delete_within_paragraph (iter, start->index, to_delete); + to_delete = paragraph_length - start_char_offset; + _delete_within_paragraph (iter, start->index, to_delete, &bytes_deleted); cur_deleted += to_delete; // As the deletion affects multiple paragraphs, we also @@ -919,7 +1020,7 @@ text_editor_delete_at_mark (TextEditor *self, // Delete a portion of the end paragraph remaining = length - cur_deleted; - _delete_within_paragraph (iter, 0, remaining); + _delete_within_paragraph (iter, 0, remaining, &bytes_deleted); break; } @@ -971,8 +1072,9 @@ text_editor_delete_at_mark (TextEditor *self, // as we join them (see below) mark->paragraph = start->paragraph; - offset = text_paragraph_get_length (start->paragraph); - _offset_mark (mark, offset - remaining); + // TODO: Does this actually work? + offset = text_paragraph_get_size_bytes (start->paragraph); + _offset_mark (mark, offset - bytes_deleted); } } @@ -1067,22 +1169,27 @@ _length_between_marks (TextMark *start, TextMark *end) { TextParagraph *iter; + const char *text; int length; g_return_val_if_fail (start != NULL, 0); g_return_val_if_fail (end != NULL, 0); if (start->paragraph == end->paragraph) - return end->index - start->index; + { + text = text_paragraph_get_text (start->paragraph); + return (int) (g_utf8_pointer_to_offset (text + start->index, text + end->index)); + } iter = start->paragraph; - length = text_paragraph_get_length (iter) + 1 - start->index; + length = text_paragraph_get_length (iter) + 1 - _get_offset (start->paragraph, start->index); while ((iter = walk_until_next_paragraph (TEXT_ITEM (iter))) != NULL) { if (iter == end->paragraph) { - length += end->index; + text = text_paragraph_get_text (end->paragraph); + length += (int) g_utf8_strlen (text, end->index); break; } @@ -1124,6 +1231,7 @@ _ensure_ordered (TextMark **start, } } +// TODO: All usages must use offsets void split_run_at_offset (TextRun *run, TextRun **new, @@ -1149,6 +1257,7 @@ split_run_at_offset (TextRun *run, text_run_set_style_underline (*new, text_run_get_style_underline (run)); } +// TODO: All usages must use offsets void split_run_in_place (TextRun *run, TextRun **new, @@ -1179,11 +1288,11 @@ text_editor_split_at_mark (TextEditor *self, current = split->paragraph; // Case 1: Split is happening on the last index - if (split->index == text_paragraph_get_length (current)) + if (split->index == text_paragraph_get_size_bytes (current)) { // Append a new paragraph with empty run new = text_paragraph_new (); - text_paragraph_append_run (new, text_run_new ("")); + text_paragraph_append_fragment(new, TEXT_FRAGMENT (text_run_new(""))); parent = text_node_get_parent (TEXT_NODE (current)); text_node_insert_child_after (parent, TEXT_NODE (new), TEXT_NODE (current)); @@ -1192,24 +1301,32 @@ text_editor_split_at_mark (TextEditor *self, // Case 2: Split happens mid-paragraph else { - TextRun *start; + TextFragment *start; TextNode *iter; - int run_offset; + int index_within_run; new = text_paragraph_new (); - start = text_paragraph_get_run_at_index (current, split->index, &run_offset); + start = text_paragraph_get_item_at_index (current, split->index, &index_within_run); iter = TEXT_NODE (start); - // Split first run if run offset is not at beginning - if (split->index != run_offset) + // Split first run if index is not at beginning + if (split->index != index_within_run) { int split_index_within_run; TextRun *new_run; - split_index_within_run = split->index - run_offset; - split_run_at_offset (start, &new_run, split_index_within_run); - text_paragraph_append_run (new, new_run); + // TODO: Make supported + if (!TEXT_IS_RUN (start)) + { + g_print ("Unsupported!\n"); + } + else + { + split_index_within_run = split->index - index_within_run; + split_run_at_offset (TEXT_RUN (start), &new_run, _get_offset (split->paragraph, split_index_within_run)); + text_paragraph_append_fragment(new, TEXT_FRAGMENT (new_run)); + } // Move to next run iter = text_node_get_next (iter); @@ -1220,22 +1337,22 @@ text_editor_split_at_mark (TextEditor *self, { TextNode *next; - g_assert (TEXT_IS_RUN (iter)); + g_assert (TEXT_IS_FRAGMENT (iter)); next = text_node_get_next (iter); text_node_unparent (iter); - text_paragraph_append_run (new, TEXT_RUN (iter)); + text_paragraph_append_fragment(new, TEXT_FRAGMENT(iter)); iter = next; } // Ensure the original paragraph has at least one run (all runs may be // moved when the split index is at the start of the paragraph) - if (text_node_get_num_children (TEXT_PARAGRAPH (current)) == 0) + if (text_node_get_num_children (TEXT_NODE (current)) == 0) { // Add empty run - text_paragraph_append_run(current, text_run_new("")); + text_paragraph_append_fragment(current, TEXT_FRAGMENT (text_run_new(""))); } // Append paragraph to document tree @@ -1290,13 +1407,13 @@ text_editor_replace_at_mark (TextEditor *self, length = _length_between_marks (start, end); text_editor_delete_at_mark (self, start, length); - text_editor_insert_at_mark (self, start, text); + text_editor_insert_text_at_mark(self, start, text); } void -text_editor_insert_at_mark (TextEditor *self, - TextMark *start, - gchar *str) +text_editor_insert_text_at_mark (TextEditor *self, + TextMark *start, + gchar *str) { // Encapsulates insertion inside an editor module/object. // This should accept user input in the form of Operational @@ -1305,28 +1422,53 @@ text_editor_insert_at_mark (TextEditor *self, GSList *marks; char *text; GString *modified; + TextFragment *item; TextRun *run; int run_start_index; - int run_offset; + int index_within_run; int length; g_return_if_fail (TEXT_IS_EDITOR (self)); g_return_if_fail (TEXT_IS_DOCUMENT (self->document)); g_return_if_fail (TEXT_IS_PARAGRAPH (start->paragraph)); - run = text_paragraph_get_run_at_index (start->paragraph, start->index, &run_start_index); + item = text_paragraph_get_item_at_index (start->paragraph, start->index, &run_start_index); + + index_within_run = start->index - run_start_index; - run_offset = start->index - run_start_index; + if (!TEXT_IS_RUN (item)) + { + if (index_within_run == 0) + { + run = text_run_new (""); + text_node_insert_child_before (TEXT_NODE (start->paragraph), TEXT_NODE (run), TEXT_NODE (item)); + } + else if (index_within_run == text_fragment_get_size_bytes(item)) + { + run = text_run_new (""); + text_node_insert_child_after (TEXT_NODE (start->paragraph), TEXT_NODE (run), TEXT_NODE (item)); + index_within_run = 0; + } + else + { + g_print ("Not supported: Inserting into non-text run\n"); + return; + } + } + else + { + run = TEXT_RUN (item); + } // TODO: Replace with hybrid tree/piece-table structure? // Textual data is stored in buffers and indexed by the tree g_object_get (run, "text", &text, NULL); modified = g_string_new (text); - modified = g_string_insert (modified, run_offset, str); + modified = g_string_insert (modified, index_within_run, str); g_object_set (run, "text", modified->str, NULL); g_string_free (modified, TRUE); - length = strlen (str); + length = (int) strlen (str); // Adjust marks according to gravity marks = text_document_get_all_marks (self->document); @@ -1361,26 +1503,136 @@ text_editor_insert_at_mark (TextEditor *self, } void -text_editor_apply_format_bold (TextEditor *self, - TextMark *start, - TextMark *end, - gboolean is_bold) +text_editor_insert_fragment_at_mark (TextEditor *self, + TextMark *start, + TextFragment *fragment) +{ + // Encapsulates insertion inside an editor module/object. + // This should accept user input in the form of Operational + // Transformation commands. This will aid with undo/redo. + + GSList *marks; + TextFragment *item; + int run_start_index; + int index_within_run; + int size; + + g_return_if_fail (TEXT_IS_EDITOR (self)); + g_return_if_fail (TEXT_IS_DOCUMENT (self->document)); + g_return_if_fail (TEXT_IS_PARAGRAPH (start->paragraph)); + + item = text_paragraph_get_item_at_index (start->paragraph, start->index, &run_start_index); + + index_within_run = start->index - run_start_index; + + if (index_within_run == 0) + { + text_node_insert_child_before (TEXT_NODE (start->paragraph), TEXT_NODE (fragment), TEXT_NODE (item)); + } + else if (index_within_run == text_fragment_get_size_bytes(item)) + { + text_node_insert_child_after (TEXT_NODE (start->paragraph), TEXT_NODE (fragment), TEXT_NODE (item)); + } + else if (TEXT_IS_RUN (item)) + { + TextRun *new_run; + const char *text; + int offset_within_run; + + text = text_fragment_get_text (TEXT_FRAGMENT (item)); + offset_within_run = (int) g_utf8_pointer_to_offset (text, text + index_within_run); + split_run_in_place (TEXT_RUN (item), &new_run, offset_within_run); + text_node_insert_child_before (TEXT_NODE (start->paragraph), TEXT_NODE (fragment), TEXT_NODE (new_run)); + } + else + { + g_print ("Cannot split opaque inline element!\n"); + return; + } + + size = text_fragment_get_size_bytes (fragment); + + // Adjust marks according to gravity + marks = text_document_get_all_marks (self->document); + + for (GSList *mark_iter = marks; + mark_iter != NULL; + mark_iter = mark_iter->next) + { + TextMark *mark; + + mark = (TextMark *)mark_iter->data; + + // Mark is on insertion point + if (mark->paragraph == start->paragraph && + mark->index == start->index) + { + _distribute_mark (mark, + start->paragraph, start->index, + start->paragraph, start->index + size); + continue; + } + + // Mark is after insertion point + if (mark->paragraph == start->paragraph && + mark->index > start->index) + { + _offset_mark (mark, size); + } + } + + g_slist_free (marks); +} + +// TODO: Decouple format from run when we introduce the stylesheet +typedef enum +{ + FORMAT_BOLD, + FORMAT_ITALIC, + FORMAT_UNDERLINE +} Format; + +static void +set_run_format (TextRun *run, + Format format, + gboolean in_use) +{ + switch (format) + { + case FORMAT_BOLD: + text_run_set_style_bold (run, in_use); + break; + case FORMAT_ITALIC: + text_run_set_style_italic (run, in_use); + break; + case FORMAT_UNDERLINE: + text_run_set_style_underline (run, in_use); + break; + } +} + +static void +text_editor_apply_format (TextEditor *self, + TextMark *start, + TextMark *end, + Format format, + gboolean in_use) { - TextRun *iter; - TextRun *last; + TextFragment *iter; + TextFragment *last; int start_run_index; int end_run_index; _ensure_ordered (&start, &end); - iter = text_paragraph_get_run_at_index (start->paragraph, start->index, &start_run_index); - last = text_paragraph_get_run_at_index (end->paragraph, end->index, &end_run_index); + iter = text_paragraph_get_item_at_index (start->paragraph, start->index, &start_run_index); + last = text_paragraph_get_item_at_index (end->paragraph, end->index, &end_run_index); // Check if start and end indices are in the same run if (iter == last) { - TextRun *first_split; - TextRun *second_split; + TextFragment *first_split; + TextFragment *second_split; int start_index_offset; int end_index_offset; @@ -1397,18 +1649,18 @@ text_editor_apply_format_bold (TextEditor *self, split_run_in_place (first_split, &second_split, end_index_offset); // Apply format to middle run - text_run_set_style_bold (first_split, is_bold); + set_run_format (first_split, format, in_use); return; } // Check if we need to split the first run if (start->index - start_run_index != 0) { - TextRun *new_run; + TextFragment *new_run; split_run_in_place (iter, &new_run, start->index - start_run_index); // Apply format to new run - text_run_set_style_bold (new_run, is_bold); + set_run_format (new_run, format, in_use); iter = new_run; } @@ -1419,7 +1671,7 @@ text_editor_apply_format_bold (TextEditor *self, split_run_in_place (last, &new_run, end->index - end_run_index); // Apply format to old run - text_run_set_style_bold (last, is_bold); + set_run_format (last, format, in_use); } while (iter != NULL) @@ -1427,20 +1679,76 @@ text_editor_apply_format_bold (TextEditor *self, if (iter == last) break; - text_run_set_style_bold (iter, is_bold); + set_run_format (iter, format, in_use); - iter = walk_until_next_run (TEXT_ITEM (iter)); + iter = walk_until_next_fragment(TEXT_ITEM(iter)); } } +void +text_editor_apply_format_bold (TextEditor *self, + TextMark *start, + TextMark *end, + gboolean is_bold) +{ + text_editor_apply_format (self, start, end, FORMAT_BOLD, is_bold); +} + +void +text_editor_apply_format_italic (TextEditor *self, + TextMark *start, + TextMark *end, + gboolean is_italic) +{ + text_editor_apply_format (self, start, end, FORMAT_ITALIC, is_italic); +} + +void +text_editor_apply_format_underline (TextEditor *self, + TextMark *start, + TextMark *end, + gboolean is_underline) +{ + text_editor_apply_format (self, start, end, FORMAT_UNDERLINE, is_underline); +} + gboolean text_editor_get_format_bold_at_mark (TextEditor *self, TextMark *mark) { - TextRun *run; + TextFragment *run; + + run = text_editor_get_item_at_mark (self, mark); + if (TEXT_IS_RUN (run)) + return text_run_get_style_bold (TEXT_RUN (run)); + + return FALSE; +} - run = text_editor_get_run_at_mark (self, mark); - return text_run_get_style_bold (run); +gboolean +text_editor_get_format_italic_at_mark (TextEditor *self, + TextMark *mark) +{ + TextFragment *run; + + run = text_editor_get_item_at_mark (self, mark); + if (TEXT_IS_RUN (run)) + return text_run_get_style_italic (TEXT_RUN (run)); + + return FALSE; +} + +gboolean +text_editor_get_format_underline_at_mark (TextEditor *self, + TextMark *mark) +{ + TextFragment *run; + + run = text_editor_get_item_at_mark (self, mark); + if (TEXT_IS_RUN (run)) + return text_run_get_style_underline (TEXT_RUN (run)); + + return FALSE; } TextMark * @@ -1488,14 +1796,14 @@ void text_editor_move_first (TextEditor *self, TextEditorMarkType type) { - text_editor_move_mark_first (self, _get_mark (self, type)); + text_editor_move_mark_first (_get_mark (self, type)); } void text_editor_move_last (TextEditor *self, TextEditorMarkType type) { - text_editor_move_mark_last (self, _get_mark (self, type)); + text_editor_move_mark_last (_get_mark (self, type)); } void @@ -1503,7 +1811,7 @@ text_editor_move_right (TextEditor *self, TextEditorMarkType type, int amount) { - text_editor_move_mark_right (self, _get_mark (self, type), amount); + text_editor_move_mark_right (_get_mark (self, type), amount); } void @@ -1511,15 +1819,23 @@ text_editor_move_left (TextEditor *self, TextEditorMarkType type, int amount) { - text_editor_move_mark_left (self, _get_mark (self, type), amount); + text_editor_move_mark_left (_get_mark (self, type), amount); } void -text_editor_insert (TextEditor *self, - TextEditorMarkType type, - gchar *str) +text_editor_insert_text (TextEditor *self, + TextEditorMarkType type, + gchar *str) +{ + text_editor_insert_text_at_mark (self, _get_mark(self, type), str); +} + +void +text_editor_insert_fragment (TextEditor *self, + TextEditorMarkType type, + TextFragment *fragment) { - text_editor_insert_at_mark (self, _get_mark (self, type), str); + text_editor_insert_fragment_at_mark (self, _get_mark(self, type), fragment); } void @@ -1546,11 +1862,11 @@ text_editor_split (TextEditor *self, text_editor_split_at_mark (self, _get_mark (self, type)); } -TextRun * -text_editor_get_run (TextEditor *self, - TextEditorMarkType type) +TextFragment * +text_editor_get_item (TextEditor *self, + TextEditorMarkType type) { - return text_editor_get_run_at_mark (self, _get_mark (self, type)); + return text_editor_get_item_at_mark (self, _get_mark (self, type)); } gchar * diff --git a/src/editor/editor.h b/src/editor/editor.h index 12c6580..692794f 100644 --- a/src/editor/editor.h +++ b/src/editor/editor.h @@ -29,26 +29,30 @@ typedef enum TextEditor *text_editor_new (TextDocument *document); -void text_editor_move_mark_first (TextEditor *self, TextMark *mark); -void text_editor_move_mark_last (TextEditor *self, TextMark *mark); -void text_editor_move_mark_right (TextEditor *self, TextMark *mark, int amount); -void text_editor_move_mark_left (TextEditor *self, TextMark *mark, int amount); -void text_editor_insert_at_mark (TextEditor *self, TextMark *start, gchar *str); -void text_editor_delete_at_mark (TextEditor *self, TextMark *start, int length); -void text_editor_replace_at_mark (TextEditor *self, TextMark *start, TextMark *end, gchar *text); -void text_editor_split_at_mark (TextEditor *self, TextMark *mark); +// TODO: Refactor into TextMark +void text_editor_move_mark_first (TextMark *mark); +void text_editor_move_mark_last (TextMark *mark); +void text_editor_move_mark_right (TextMark *mark, int amount); +void text_editor_move_mark_left (TextMark *mark, int amount); + +void text_editor_insert_text_at_mark (TextEditor *self, TextMark *start, gchar *str); +void text_editor_insert_fragment_at_mark (TextEditor *self, TextMark *start, TextFragment *fragment); +void text_editor_delete_at_mark (TextEditor *self, TextMark *start, int length); +void text_editor_replace_at_mark (TextEditor *self, TextMark *start, TextMark *end, gchar *text); +void text_editor_split_at_mark (TextEditor *self, TextMark *mark); void text_editor_move_first (TextEditor *self, TextEditorMarkType type); void text_editor_move_last (TextEditor *self, TextEditorMarkType type); void text_editor_move_right (TextEditor *self, TextEditorMarkType type, int amount); void text_editor_move_left (TextEditor *self, TextEditorMarkType type, int amount); -void text_editor_insert (TextEditor *self, TextEditorMarkType type, gchar *str); +void text_editor_insert_text (TextEditor *self, TextEditorMarkType type, gchar *str); +void text_editor_insert_fragment (TextEditor *self, TextEditorMarkType type, TextFragment *fragment); void text_editor_delete (TextEditor *self, TextEditorMarkType type, int length); void text_editor_replace (TextEditor *self, TextEditorMarkType start_type, TextEditorMarkType end_type, gchar *text); void text_editor_split (TextEditor *self, TextEditorMarkType type); -TextRun *text_editor_get_run (TextEditor *self, TextEditorMarkType type); -TextRun *text_editor_get_run_at_mark (TextEditor *self, TextMark *mark); +TextFragment *text_editor_get_item (TextEditor *self, TextEditorMarkType type); +TextFragment *text_editor_get_item_at_mark (TextEditor *self, TextMark *mark); gchar *text_editor_dump_plain_text (TextEditor *self); @@ -60,7 +64,11 @@ TextParagraph *text_editor_previous_paragraph (TextParagraph *paragraph); // Format Helpers // TODO: Make this more abstract -void text_editor_apply_format_bold (TextEditor *self, TextMark *start, TextMark *end, gboolean is_bold); -gboolean text_editor_get_format_bold_at_mark (TextEditor *self, TextMark *mark); +void text_editor_apply_format_bold (TextEditor *self, TextMark *start, TextMark *end, gboolean is_bold); +gboolean text_editor_get_format_bold_at_mark (TextEditor *self, TextMark *mark); +void text_editor_apply_format_italic (TextEditor *self, TextMark *start, TextMark *end, gboolean is_italic); +gboolean text_editor_get_format_italic_at_mark (TextEditor *self, TextMark *mark); +void text_editor_apply_format_underline (TextEditor *self, TextMark *start, TextMark *end, gboolean is_underline); +gboolean text_editor_get_format_underline_at_mark (TextEditor *self, TextMark *mark); G_END_DECLS diff --git a/src/format/import-html.c b/src/format/import-html.c index fd34828..cfa94d7 100644 --- a/src/format/import-html.c +++ b/src/format/import-html.c @@ -17,6 +17,7 @@ #include "../model/paragraph.h" #include "../model/block.h" #include "../model/run.h" +#include "../model/image.h" // Style Info // TODO: Refactor this into a stylesheet module rather than setting it on runs directly @@ -48,6 +49,27 @@ build_text_frame_recursive (xmlNode *nodes, *current = text_paragraph_new (); text_frame_append_block (frame, TEXT_BLOCK (*current)); } + if (g_str_equal (cur_node->name, "img")) + { + TextImage *image; + xmlAttr *iter; + char *img_src; + + *current = text_paragraph_new (); + text_frame_append_block (frame, TEXT_BLOCK (*current)); + + img_src = NULL; + + for (iter = cur_node->properties; iter != NULL; iter = iter->next) + { + if (g_str_equal (iter->name, "src")) + img_src = g_strdup (iter->name); + } + + image = text_image_new (img_src); + text_paragraph_append_fragment(*current, TEXT_FRAGMENT(image)); + + } else if (g_str_equal (cur_node->name, "b")) is_bold = TRUE; else if (g_str_equal (cur_node->name, "i")) @@ -70,7 +92,7 @@ build_text_frame_recursive (xmlNode *nodes, text_run_set_style_bold (new_run, is_bold); text_run_set_style_italic (new_run, is_italic); text_run_set_style_underline (new_run, is_underline); - text_paragraph_append_run (*current, new_run); + text_paragraph_append_fragment(*current, new_run); } // PROCESS CHILDREN diff --git a/src/layout/layout-box.c b/src/layout/layout-box.c deleted file mode 100644 index 3a630b0..0000000 --- a/src/layout/layout-box.c +++ /dev/null @@ -1,318 +0,0 @@ -/* layout-box.c - * - * Copyright 2022 Matthew Jakeman - * - * This file is dual-licensed under the terms of the Mozilla Public - * License 2.0 and the Lesser General Public License 2.1 (or any - * later version). - * - * SPDX-License-Identifier: MPL-2.0 OR LGPL-2.1-or-later - */ - -#include "layout-box.h" - -#include "../model/paragraph.h" -#include "../tree/node.h" - -typedef struct -{ - TextItem *item; - PangoLayout *layout; - gboolean has_cursor; - int cursor_index; - - TextDimensions cursor; - TextDimensions bbox; -} TextLayoutBoxPrivate; - -G_DEFINE_FINAL_TYPE_WITH_PRIVATE (TextLayoutBox, text_layout_box, TEXT_TYPE_NODE) - -enum { - PROP_0, - N_PROPS -}; - -static GParamSpec *properties [N_PROPS]; - -/** - * text_layout_box_new: - * - * Create a new #TextLayoutBox. - * - * Returns: (transfer full): a newly created #TextLayoutBox - */ -TextLayoutBox * -text_layout_box_new (void) -{ - return g_object_new (TEXT_TYPE_LAYOUT_BOX, NULL); -} - -static void -text_layout_box_finalize (GObject *object) -{ - TextLayoutBox *self = (TextLayoutBox *)object; - TextLayoutBoxPrivate *priv = text_layout_box_get_instance_private (self); - - // Dispose of children - for (TextNode *child = text_node_get_first_child (TEXT_NODE (self)); - child != NULL; - child = text_node_get_next (TEXT_NODE (self))) - { - g_clear_object (&child); - } - - G_OBJECT_CLASS (text_layout_box_parent_class)->finalize (object); -} - -static void -text_layout_box_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - TextLayoutBox *self = TEXT_LAYOUT_BOX (object); - - switch (prop_id) - { - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -text_layout_box_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - TextLayoutBox *self = TEXT_LAYOUT_BOX (object); - - switch (prop_id) - { - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -void -_set_attributes (TextParagraph *paragraph, - PangoLayout *pango_layout) -{ - TextNode *run; - PangoAttrList *list; - - int start_index; - - list = pango_attr_list_new(); - - g_return_if_fail (TEXT_IS_PARAGRAPH (paragraph)); - - start_index = 0; - - for (run = text_node_get_first_child (TEXT_NODE (paragraph)); - run != NULL; - run = text_node_get_next (run)) - { - gboolean is_bold, is_italic, is_underline; - PangoAttribute *attr; - int run_length; - - run_length = text_run_get_length (TEXT_RUN (run)); - - // Get Style Properties - is_bold = text_run_get_style_bold (TEXT_RUN (run)); - is_italic = text_run_get_style_italic (TEXT_RUN (run)); - is_underline = text_run_get_style_underline (TEXT_RUN (run)); - - // Attribute: Bold - if (is_bold) - { - attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD); - attr->start_index = start_index; - attr->end_index = start_index + run_length; - pango_attr_list_insert (list, attr); - } - - // Attribute: Italic - if (is_italic) - { - attr = pango_attr_style_new (PANGO_STYLE_ITALIC); - attr->start_index = start_index; - attr->end_index = start_index + run_length; - pango_attr_list_insert (list, attr); - } - - // Attribute: Underline - if (is_underline) - { - attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE); - attr->start_index = start_index; - attr->end_index = start_index + run_length; - pango_attr_list_insert (list, attr); - } - - start_index += run_length; - } - - pango_layout_set_attributes (pango_layout, list); -} - -void -text_layout_box_layout (TextLayoutBox *self, - PangoContext *context, - int width, - int offset_x, - int offset_y) -{ - g_return_if_fail (TEXT_IS_LAYOUT_BOX (self)); - - TextLayoutBoxPrivate *priv = text_layout_box_get_instance_private (self); - - int height = 0; - - g_debug ("Starting for %s\n", g_type_name_from_instance (self)); - - g_debug ("Has item: %d\n", priv->item != NULL); - g_debug ("Has paragraph: %d\n", TEXT_IS_PARAGRAPH (priv->item)); - - if (priv->item && TEXT_IS_PARAGRAPH (priv->item)) - { - gchar *text; - text = text_paragraph_get_text (TEXT_PARAGRAPH (priv->item)); - g_debug (" - String %s\n", text); - - if (!priv->layout) - priv->layout = pango_layout_new (context); - - pango_layout_set_text (priv->layout, text, -1); - pango_layout_set_wrap (priv->layout, PANGO_WRAP_WORD_CHAR); - pango_layout_set_width (priv->layout, PANGO_SCALE * width); - pango_layout_get_pixel_size (priv->layout, NULL, &height); - g_debug (" - Height %d\n", height); - - // TODO: THIS WILL NOT WORK FOR PARAGRAPHS WITH MORE THAN - // ONE RUN -> FIX BY CONSIDERING RUNS INDIVIDUALLY - // (might need support for inline layouts?) - if (priv->has_cursor) - { - PangoRectangle cursor_rect; - pango_layout_index_to_pos (priv->layout, - priv->cursor_index, - &cursor_rect); - - // Hardcode width to 1 - priv->cursor.x = cursor_rect.x / PANGO_SCALE; - priv->cursor.y = cursor_rect.y / PANGO_SCALE; - priv->cursor.height = cursor_rect.height / PANGO_SCALE; - priv->cursor.width = 1; - } - - // Set style information - // TODO: Matching from ruleset - _set_attributes (TEXT_PARAGRAPH (priv->item), priv->layout); - - g_free (text); - } - - // Account for children (should we force elements to choose between - // children and text? seems like a sensible simplification) - - for (TextNode *node = text_node_get_first_child (TEXT_NODE (self)); - node != NULL; - node = text_node_get_next (node)) - { - TextLayoutBox *child_box = TEXT_LAYOUT_BOX (node); - - g_debug (" - Found child\n"); - - // We can assume bbox already exists by now, as the layout() method - // has been called already in the layout manager. - - TextLayoutBoxPrivate *priv = text_layout_box_get_instance_private (child_box); - height += priv->bbox.height; - g_debug (" - Child height %d\n", height); - } - - priv->bbox.x = offset_x; - priv->bbox.y = offset_y; - priv->bbox.width = width; - priv->bbox.height = height; -} - -void -text_layout_box_set_item (TextLayoutBox *self, - TextItem *item) -{ - TextLayoutBoxPrivate *priv = text_layout_box_get_instance_private (self); - priv->item = item; - g_debug ("Set item to non null: %d\n", priv->item != NULL); -} - -TextItem * -text_layout_box_get_item (TextLayoutBox *self) -{ - TextLayoutBoxPrivate *priv = text_layout_box_get_instance_private (self); - return priv->item; -} - -void -text_layout_box_set_cursor (TextLayoutBox *self, - int index) -{ - TextLayoutBoxPrivate *priv = text_layout_box_get_instance_private (self); - - if (index < 0) { - priv->has_cursor = FALSE; - priv->cursor_index = -1; - return; - } - - priv->has_cursor = TRUE; - priv->cursor_index = index; -} - -gboolean -text_layout_box_get_cursor (TextLayoutBox *self, - const TextDimensions **cursor) - -{ - TextLayoutBoxPrivate *priv = text_layout_box_get_instance_private (self); - - *cursor = &priv->cursor; - - return priv->has_cursor; -} - -const TextDimensions * -text_layout_box_get_bbox (TextLayoutBox *self) -{ - TextLayoutBoxPrivate *priv = text_layout_box_get_instance_private (self); - - return &priv->bbox; -} - -PangoLayout * -text_layout_box_get_pango_layout (TextLayoutBox *self) -{ - TextLayoutBoxPrivate *priv = text_layout_box_get_instance_private (self); - return priv->layout; -} - -static void -text_layout_box_class_init (TextLayoutBoxClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = text_layout_box_finalize; - object_class->get_property = text_layout_box_get_property; - object_class->set_property = text_layout_box_set_property; -} - -static void -text_layout_box_init (TextLayoutBox *self) -{ - TextLayoutBoxPrivate *priv = text_layout_box_get_instance_private (self); - - priv->has_cursor = FALSE; - priv->cursor_index = -1; -} diff --git a/src/layout/layout.c b/src/layout/layout.c index df8c783..81f73a8 100644 --- a/src/layout/layout.c +++ b/src/layout/layout.c @@ -11,12 +11,12 @@ #include "layout.h" -struct _TextLayout +typedef struct { - GObject parent_instance; -}; + int padding; +} TextLayoutPrivate; -G_DEFINE_FINAL_TYPE (TextLayout, text_layout, G_TYPE_OBJECT) +G_DEFINE_TYPE_WITH_PRIVATE (TextLayout, text_layout, G_TYPE_OBJECT) enum { PROP_0, @@ -69,56 +69,86 @@ text_layout_set_property (GObject *object, } } -static void -do_layout_recursive (TextLayout *self, - TextLayoutBox *parent, - PangoContext *context, - TextItem *item, - int width) +TextLayoutBox * +build_layout_tree_recursive (TextLayout *self, + PangoContext *context, + TextItem *item) { - int child_offset_x; - int child_offset_y; + TextLayoutBox *box; + TextNode *iter; - child_offset_x = 0; - child_offset_y = 0; - - g_return_if_fail (TEXT_IS_LAYOUT (self)); - g_return_if_fail (TEXT_IS_LAYOUT_BOX (parent)); - g_return_if_fail (TEXT_IS_ITEM (item)); - - // Append children (in future, recursively) - for (TextNode *node = text_node_get_first_child (TEXT_NODE (item)); - node != NULL; - node = text_node_get_next (node)) + g_return_val_if_fail (TEXT_IS_LAYOUT (self), NULL); + g_return_val_if_fail (PANGO_IS_CONTEXT (context), NULL); + g_return_val_if_fail (TEXT_IS_ITEM (item), NULL); + + // Construct a layout item for this node using the item factory + // Subclasses can override this to add and use their own items + box = TEXT_LAYOUT_GET_CLASS (self)->item_factory (TEXT_ITEM (item)); + + // For now, if a node does not provide a LayoutBox then we assume + // it and its children are invisible. Perhaps we want to introduce + // some kind of LayoutAnonymousBox which is transparently skipped by + // the layout engine. + if (!TEXT_IS_LAYOUT_BOX (box)) + return NULL; + + // Setup Box + text_layout_box_set_item (box, item); + text_item_detach (TEXT_ITEM (item)); // TODO: Move to a 'cleanup_tree' function? + text_item_attach (TEXT_ITEM (item), TEXT_NODE (box)); + + // Append children + for (iter = text_node_get_first_child (TEXT_NODE (item)); + iter != NULL; + iter = text_node_get_next (iter)) { - g_assert (TEXT_IS_ITEM (node)); + TextLayoutBox *child_box; - g_debug ("Counting child %s\n", g_type_name_from_instance (node)); + g_assert (TEXT_IS_ITEM(iter)); - // Let's treat paragraphs opaquely for now. In the future, we need - // to manually consider each text run in order for inline equations - // and images. - if (TEXT_IS_PARAGRAPH (node)) + child_box = build_layout_tree_recursive (self, context, TEXT_ITEM (iter)); + + if (TEXT_IS_LAYOUT_BOX (child_box)) { - TextLayoutBox *box = text_layout_box_new (); - text_layout_box_set_item (box, TEXT_ITEM (node)); - text_item_detach (TEXT_ITEM (node)); // TODO: Don't do this - text_item_attach (TEXT_ITEM (node), TEXT_NODE (box)); - - text_node_append_child (TEXT_NODE (parent), TEXT_NODE (box)); - g_debug ("Added child %s\n", g_type_name_from_instance (node)); - - // TODO: This function should be properly recursive in the future, - // so avoid calling it here. Below should be the only time it is - // called (i.e. post-order traversal). - text_layout_box_layout (box, context, width, child_offset_x, child_offset_y); - child_offset_y += text_layout_box_get_bbox (box)->height; + text_node_append_child (TEXT_NODE (box), TEXT_NODE (child_box)); } } - // When we make this recursive, should pass in offsets - text_layout_box_layout (parent, context, width, 0, 0); - g_debug ("Layout for %s\n", g_type_name_from_instance (parent)); + return box; +} + +TextLayoutBox * +text_layout_default_item_factory (TextItem *item) +{ + GType type; + type = G_TYPE_FROM_INSTANCE (item); + + // Go from most specific to least specific, otherwise + // we could accidentally create the wrong layout box + // by taking the base class instead of the subclass + + // Images + if (type == TEXT_TYPE_IMAGE) + return TEXT_LAYOUT_BOX (text_layout_inline_new ()); + + // Paragraphs + if (type == TEXT_TYPE_PARAGRAPH) + return TEXT_LAYOUT_BOX (text_layout_block_new ()); + + // Text Runs + if (type == TEXT_TYPE_RUN) + return NULL; + + // Frames + if (type == TEXT_TYPE_FRAME) + return TEXT_LAYOUT_BOX (text_layout_block_new ()); + + // It is an error to provide a type for which no layout + // item exists - we cannot display it. + g_critical ("Cannot create layout item for type '%s'.", + g_type_name (type)); + + return NULL; } TextLayoutBox * @@ -127,32 +157,36 @@ text_layout_build_layout_tree (TextLayout *self, TextFrame *frame, int width) { + TextLayoutBox *root; + g_return_val_if_fail (TEXT_IS_LAYOUT (self), NULL); g_return_val_if_fail (TEXT_IS_FRAME (frame), NULL); - TextLayoutBox *root = text_layout_box_new (); - do_layout_recursive (self, root, context, TEXT_ITEM (frame), width); + root = build_layout_tree_recursive (self, context, TEXT_ITEM (frame)); + text_layout_box_layout (root, context, width, 0, 0); return root; } TextLayoutBox * text_layout_find_above (TextLayoutBox *item) { - return TEXT_LAYOUT_BOX (text_node_get_previous (TEXT_LAYOUT_BOX (item))); + g_return_val_if_fail (TEXT_IS_LAYOUT_BOX (item), NULL); + return TEXT_LAYOUT_BOX (text_node_get_previous (TEXT_NODE (item))); } TextLayoutBox * text_layout_find_below (TextLayoutBox *item) { - return TEXT_LAYOUT_BOX (text_node_get_next (TEXT_LAYOUT_BOX (item))); + g_return_val_if_fail (TEXT_IS_LAYOUT_BOX (item), NULL); + return TEXT_LAYOUT_BOX (text_node_get_next (TEXT_NODE (item))); } TextLayoutBox * -text_layout_pick_internal (TextLayoutBox *root, - int x, - int y, - double *min_y_distance, - TextNode **min_y_layout) +text_layout_pick_internal (TextLayoutBox *root, + double x, + double y, + double *min_y_distance, + TextLayoutBox **min_y_layout) { // Note: 'x' and 'y' are relative to the document origin TextNode *child; @@ -172,10 +206,10 @@ text_layout_pick_internal (TextLayoutBox *root, bbox = text_layout_box_get_bbox (layout_item); // Recursively check child layouts first - found = TEXT_NODE (text_layout_pick (layout_item, x - bbox->x, y - bbox->y)); + found = TEXT_NODE (text_layout_pick_internal (layout_item, x - bbox->x, y - bbox->y, min_y_distance, min_y_layout)); if (found) { - return found; + return TEXT_LAYOUT_BOX (found); } // Check if the cursor is fully within the bounding box @@ -192,7 +226,7 @@ text_layout_pick_internal (TextLayoutBox *root, y <= bbox->y + bbox->height) { *min_y_distance = 0; - *min_y_layout = TEXT_NODE (layout_item); + *min_y_layout = layout_item; continue; } @@ -204,7 +238,7 @@ text_layout_pick_internal (TextLayoutBox *root, if (dist_to_y < *min_y_distance) { *min_y_distance = dist_to_y; - *min_y_layout = TEXT_NODE (layout_item); + *min_y_layout = layout_item; } } @@ -217,8 +251,8 @@ text_layout_pick (TextLayoutBox *root, int y) { double min_y_distance = G_MAXDOUBLE; - TextNode *min_y_layout = NULL; - TextNode *result; + TextLayoutBox *min_y_layout = NULL; + TextLayoutBox *result; result = text_layout_pick_internal(root, x, y, &min_y_distance, &min_y_layout); @@ -233,6 +267,8 @@ text_layout_pick (TextLayoutBox *root, static void text_layout_class_init (TextLayoutClass *klass) { + klass->item_factory = text_layout_default_item_factory; + GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = text_layout_finalize; diff --git a/src/layout/layout.h b/src/layout/layout.h index d971472..9b2a78f 100644 --- a/src/layout/layout.h +++ b/src/layout/layout.h @@ -15,17 +15,26 @@ #include #include -#include "layout-box.h" +#include "layoutbox.h" +#include "layoutblock.h" +#include "layoutinline.h" #include "../model/frame.h" #include "../model/paragraph.h" #include "../model/mark.h" +#include "../model/image.h" G_BEGIN_DECLS #define TEXT_TYPE_LAYOUT (text_layout_get_type()) -G_DECLARE_FINAL_TYPE (TextLayout, text_layout, TEXT, LAYOUT, GObject) +G_DECLARE_DERIVABLE_TYPE (TextLayout, text_layout, TEXT, LAYOUT, GObject) + +struct _TextLayoutClass +{ + GObjectClass parent_class; + TextLayoutBox *(*item_factory)(TextItem *item); +}; TextLayout *text_layout_new (void); diff --git a/src/layout/layoutblock.c b/src/layout/layoutblock.c new file mode 100644 index 0000000..7ada87e --- /dev/null +++ b/src/layout/layoutblock.c @@ -0,0 +1,354 @@ +/* layoutblock.c + * + * Copyright 2022 Matthew Jakeman + * + * This file is dual-licensed under the terms of the Mozilla Public + * License 2.0 and the Lesser General Public License 2.1 (or any + * later version). + * + * SPDX-License-Identifier: MPL-2.0 OR LGPL-2.1-or-later + */ + +#include "layoutblock.h" + +#include "../model/paragraph.h" +#include "../model/image.h" + +#include "layoutinline.h" + +typedef struct +{ + PangoLayout *layout; +} TextLayoutBlockPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (TextLayoutBlock, text_layout_block, TEXT_TYPE_LAYOUT_BOX) + +enum { + PROP_0, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + +TextLayoutBlock * +text_layout_block_new () +{ + return TEXT_LAYOUT_BLOCK (g_object_new (TEXT_TYPE_LAYOUT_BLOCK, NULL)); +} + +static void +text_layout_block_finalize (GObject *object) +{ + TextLayoutBlock *self = (TextLayoutBlock *)object; + TextLayoutBlockPrivate *priv = text_layout_block_get_instance_private (self); + + G_OBJECT_CLASS (text_layout_block_parent_class)->finalize (object); +} + +static void +text_layout_block_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + TextLayoutBlock *self = TEXT_LAYOUT_BLOCK (object); + + switch (prop_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +text_layout_block_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + TextLayoutBlock *self = TEXT_LAYOUT_BLOCK (object); + + switch (prop_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + + +static void +_set_inline_attribute (TextOpaque *opaque, + PangoAttrList *list, + int start_index, + int run_length) +{ + PangoAttribute *attr; + PangoRectangle rect; + + TextLayoutBox *inline_box; + const TextDimensions *bbox; + + inline_box = TEXT_LAYOUT_BOX (text_item_get_attachment (TEXT_ITEM (opaque))); + bbox = text_layout_box_get_bbox (inline_box); + + g_assert (TEXT_IS_LAYOUT_INLINE (inline_box)); + + // Get Style Properties + rect.width = (int) bbox->width * PANGO_SCALE; + rect.height = (int) bbox->height * PANGO_SCALE; + + // Shape Attribute + attr = pango_attr_shape_new (&rect, &rect); + attr->start_index = start_index; + attr->end_index = start_index + run_length; + pango_attr_list_insert (list, attr); +} + +static void +_set_run_attribute (TextRun *run, + PangoAttrList *list, + int start_index, + int run_length) +{ + gboolean is_bold, is_italic, is_underline; + PangoAttribute *attr; + + // Get Style Properties + is_bold = text_run_get_style_bold (TEXT_RUN (run)); + is_italic = text_run_get_style_italic (TEXT_RUN (run)); + is_underline = text_run_get_style_underline (TEXT_RUN (run)); + + // Attribute: Bold + if (is_bold) + { + attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD); + attr->start_index = start_index; + attr->end_index = start_index + run_length; + pango_attr_list_insert (list, attr); + } + + // Attribute: Italic + if (is_italic) + { + attr = pango_attr_style_new (PANGO_STYLE_ITALIC); + attr->start_index = start_index; + attr->end_index = start_index + run_length; + pango_attr_list_insert (list, attr); + } + + // Attribute: Underline + if (is_underline) + { + attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE); + attr->start_index = start_index; + attr->end_index = start_index + run_length; + pango_attr_list_insert (list, attr); + } +} + +void +_set_attributes (TextParagraph *paragraph, + PangoLayout *pango_layout) +{ + TextNode *fragment; + PangoAttrList *list; + + int start_index; + + list = pango_attr_list_new(); + + g_return_if_fail (TEXT_IS_PARAGRAPH (paragraph)); + + start_index = 0; + + for (fragment = text_node_get_first_child (TEXT_NODE (paragraph)); + fragment != NULL; + fragment = text_node_get_next (fragment)) + { + int run_length; + + run_length = text_fragment_get_size_bytes (TEXT_FRAGMENT (fragment)); + + if (TEXT_IS_RUN (fragment)) + _set_run_attribute (TEXT_RUN (fragment), list, start_index, run_length); + else if (TEXT_IS_OPAQUE (fragment)) + _set_inline_attribute (TEXT_OPAQUE (fragment), list, start_index, run_length); + + start_index += run_length; + } + + pango_layout_set_attributes (pango_layout, list); +} + +static void +do_block_layout (TextLayoutBox *self, + PangoContext *context, + int width, + int offset_x, + int offset_y) +{ + TextNode *iter; + TextDimensions *bbox; + + int child_offset_y; + int height; + + height = 0; + child_offset_y = 0; + bbox = text_layout_box_get_mutable_bbox (self); + + // Recompute child element offset + for (iter = text_node_get_first_child (TEXT_NODE (self)); + iter != NULL; + iter = text_node_get_next (TEXT_NODE (iter))) + { + const TextDimensions *child_bbox; + TextLayoutBox *child_box = TEXT_LAYOUT_BOX (iter); + + g_assert (TEXT_IS_LAYOUT_BLOCK (iter)); + + text_layout_box_layout (child_box, context, width, offset_x, offset_y + child_offset_y); + + child_bbox = text_layout_box_get_bbox (child_box); + child_offset_y += (int) child_bbox->height; + } + + height += child_offset_y; + + bbox->x = offset_x; + bbox->y = offset_y; + bbox->width = width; + bbox->height = height; +} + +static void +do_inline_layout (TextLayoutBox *self, + PangoContext *context, + int width, + int offset_x, + int offset_y) +{ + TextNode *iter; + TextItem *item; + TextDimensions *bbox; + TextLayoutBlockPrivate *priv; + + int height; + int byte_offset; + + item = text_layout_box_get_item (self); + priv = text_layout_block_get_instance_private (TEXT_LAYOUT_BLOCK (self)); + bbox = text_layout_box_get_mutable_bbox (self); + + // Precompute inline children requested size + for (iter = text_node_get_first_child (TEXT_NODE (self)); + iter != NULL; + iter = text_node_get_next (TEXT_NODE (iter))) + { + // Get fixed size so we can set pango attributes accordingly + g_assert (TEXT_IS_LAYOUT_INLINE (iter)); + text_layout_box_layout (TEXT_LAYOUT_BOX (iter), context, 0, 0, 0); + } + + // Setup pango layout + if (item && TEXT_IS_PARAGRAPH (item)) + { + gchar *text; + text = text_paragraph_get_text (TEXT_PARAGRAPH (item)); + + if (!priv->layout) + priv->layout = pango_layout_new (context); + + // Set style information + // TODO: Matching from ruleset + _set_attributes (TEXT_PARAGRAPH (item), priv->layout); + + // Set basic layout properties + pango_layout_set_text (priv->layout, text, -1); + pango_layout_set_wrap (priv->layout, PANGO_WRAP_WORD_CHAR); + pango_layout_set_width (priv->layout, PANGO_SCALE * width); + pango_layout_get_pixel_size (priv->layout, NULL, &height); + g_free (text); + } + + // Recompute x/y offsets of inline children + + byte_offset = 0; + for (iter = text_node_get_first_child (TEXT_NODE (item)); + iter != NULL; + iter = text_node_get_next (TEXT_NODE (iter))) + { + TextNode *inline_box; + PangoRectangle rect; + + // Try to get layout item attachment + inline_box = text_item_get_attachment (TEXT_ITEM (iter)); + + if (TEXT_IS_LAYOUT_INLINE (inline_box)) + { + // Get starting x,y position of run at this index + pango_layout_index_to_pos (priv->layout, byte_offset, &rect); + + // Re-layout child with new x/y offset + text_layout_box_layout (TEXT_LAYOUT_BOX (inline_box), context, 0, + rect.x / PANGO_SCALE, + rect.y / PANGO_SCALE); + } + + // Increase byte offset into the paragraph + byte_offset += text_fragment_get_size_bytes (TEXT_FRAGMENT (iter)); + } + + bbox->x = offset_x; + bbox->y = offset_y; + bbox->width = width; + bbox->height = height; +} + +static void +text_layout_block_layout (TextLayoutBox *self, + PangoContext *context, + int width, + int offset_x, + int offset_y) +{ + TextNode *first_child; + + g_return_if_fail (TEXT_IS_LAYOUT_BLOCK (self)); + + // Blocks can only contain all block children OR + // all inline children, but not a mixture of each. + first_child = text_node_get_first_child (TEXT_NODE (self)); + + if (TEXT_IS_LAYOUT_BLOCK (first_child)) + do_block_layout (self, context, width, offset_x, offset_y); + else + do_inline_layout (self, context, width, offset_x, offset_y); +} + +PangoLayout * +text_layout_block_get_pango_layout (TextLayoutBlock *self) +{ + TextLayoutBlockPrivate *priv = text_layout_block_get_instance_private (self); + return priv->layout; +} + +static void +text_layout_block_class_init (TextLayoutBlockClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = text_layout_block_finalize; + object_class->get_property = text_layout_block_get_property; + object_class->set_property = text_layout_block_set_property; + + TextLayoutBoxClass *layout_box_class = TEXT_LAYOUT_BOX_CLASS (klass); + + layout_box_class->layout = text_layout_block_layout; +} + +static void +text_layout_block_init (TextLayoutBlock *self) +{ + TextLayoutBlockPrivate *priv = text_layout_block_get_instance_private (self); +} diff --git a/src/layout/layoutblock.h b/src/layout/layoutblock.h new file mode 100644 index 0000000..a2bb1fb --- /dev/null +++ b/src/layout/layoutblock.h @@ -0,0 +1,38 @@ +/* layoutblock.h + * + * Copyright 2022 Matthew Jakeman + * + * This file is dual-licensed under the terms of the Mozilla Public + * License 2.0 and the Lesser General Public License 2.1 (or any + * later version). + * + * SPDX-License-Identifier: MPL-2.0 OR LGPL-2.1-or-later + */ + +#pragma once + +#include +#include + +#include "layoutbox.h" +#include "layoutbox-impl.h" + +#include "types.h" + +G_BEGIN_DECLS + +#define TEXT_TYPE_LAYOUT_BLOCK (text_layout_block_get_type()) + +G_DECLARE_DERIVABLE_TYPE (TextLayoutBlock, text_layout_block, TEXT, LAYOUT_BLOCK, TextLayoutBox) + +struct _TextLayoutBlockClass +{ + TextLayoutBoxClass parent_class; +}; + +TextLayoutBlock *text_layout_block_new (void); + +PangoLayout * +text_layout_block_get_pango_layout (TextLayoutBlock *self); + +G_END_DECLS diff --git a/src/layout/layoutbox-impl.h b/src/layout/layoutbox-impl.h new file mode 100644 index 0000000..30040a2 --- /dev/null +++ b/src/layout/layoutbox-impl.h @@ -0,0 +1,21 @@ +/* layoutbox-impl.h + * + * Copyright 2022 Matthew Jakeman + * + * This file is dual-licensed under the terms of the Mozilla Public + * License 2.0 and the Lesser General Public License 2.1 (or any + * later version). + * + * SPDX-License-Identifier: MPL-2.0 OR LGPL-2.1-or-later + */ + +#pragma once + +#include "layoutbox.h" + +G_BEGIN_DECLS + +TextDimensions * +text_layout_box_get_mutable_bbox (TextLayoutBox *self); + +G_END_DECLS diff --git a/src/layout/layoutbox.c b/src/layout/layoutbox.c new file mode 100644 index 0000000..7ab2de9 --- /dev/null +++ b/src/layout/layoutbox.c @@ -0,0 +1,160 @@ +/* layoutbox.c + * + * Copyright 2022 Matthew Jakeman + * + * This file is dual-licensed under the terms of the Mozilla Public + * License 2.0 and the Lesser General Public License 2.1 (or any + * later version). + * + * SPDX-License-Identifier: MPL-2.0 OR LGPL-2.1-or-later + */ + +#include "layoutbox.h" +#include "layoutbox-impl.h" + +typedef struct +{ + TextItem *item; + TextDimensions bbox; +} TextLayoutBoxPrivate; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (TextLayoutBox, text_layout_box, TEXT_TYPE_NODE) + +enum { + PROP_0, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + +/** + * text_layout_box_new: + * + * Create a new #TextLayoutBox. + * + * Returns: (transfer full): a newly created #TextLayoutBox + */ +TextLayoutBox * +text_layout_box_new (void) +{ + return g_object_new (TEXT_TYPE_LAYOUT_BOX, NULL); +} + +static void +text_layout_box_finalize (GObject *object) +{ + TextLayoutBox *self = (TextLayoutBox *)object; + TextLayoutBoxPrivate *priv = text_layout_box_get_instance_private (self); + + // TODO: Dispose of children + + G_OBJECT_CLASS (text_layout_box_parent_class)->finalize (object); +} + +static void +text_layout_box_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + TextLayoutBox *self = TEXT_LAYOUT_BOX (object); + + switch (prop_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +text_layout_box_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + TextLayoutBox *self = TEXT_LAYOUT_BOX (object); + + switch (prop_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +void +text_layout_box_layout (TextLayoutBox *self, + PangoContext *context, + int width, + int offset_x, + int offset_y) +{ + TEXT_LAYOUT_BOX_GET_CLASS (self)->layout (self, context, width, offset_x, offset_y); +} + +void +text_layout_box_real_layout (TextLayoutBox *self, + PangoContext *context, + int width, + int offset_x, + int offset_y) +{ + g_return_if_fail (TEXT_IS_LAYOUT_BOX (self)); + + TextLayoutBoxPrivate *priv; + + priv = text_layout_box_get_instance_private (self); + + priv->bbox.x = offset_x; + priv->bbox.y = offset_y; + priv->bbox.width = width; + priv->bbox.height = 0; + + g_warning ("%s does not override text_layout_box_layout(), no layout will occur.\n", + g_type_name_from_instance ((GTypeInstance *) self)); +} + +void +text_layout_box_set_item (TextLayoutBox *self, + TextItem *item) +{ + TextLayoutBoxPrivate *priv = text_layout_box_get_instance_private (self); + priv->item = item; +} + +TextItem * +text_layout_box_get_item (TextLayoutBox *self) +{ + TextLayoutBoxPrivate *priv = text_layout_box_get_instance_private (self); + return priv->item; +} + +TextDimensions * +text_layout_box_get_mutable_bbox (TextLayoutBox *self) +{ + TextLayoutBoxPrivate *priv = text_layout_box_get_instance_private (self); + return &priv->bbox; +} + +const TextDimensions * +text_layout_box_get_bbox (TextLayoutBox *self) +{ + return text_layout_box_get_mutable_bbox (self); +} + +static void +text_layout_box_class_init (TextLayoutBoxClass *klass) +{ + klass->layout = text_layout_box_real_layout; + + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = text_layout_box_finalize; + object_class->get_property = text_layout_box_get_property; + object_class->set_property = text_layout_box_set_property; +} + +static void +text_layout_box_init (TextLayoutBox *self) +{ + TextLayoutBoxPrivate *priv = text_layout_box_get_instance_private (self); +} diff --git a/src/layout/layout-box.h b/src/layout/layoutbox.h similarity index 76% rename from src/layout/layout-box.h rename to src/layout/layoutbox.h index 63335ad..b0496f1 100644 --- a/src/layout/layout-box.h +++ b/src/layout/layoutbox.h @@ -1,4 +1,4 @@ -/* layout-box.h +/* layoutbox.h * * Copyright 2022 Matthew Jakeman * @@ -28,10 +28,9 @@ G_DECLARE_DERIVABLE_TYPE (TextLayoutBox, text_layout_box, TEXT, LAYOUT_BOX, Text struct _TextLayoutBoxClass { TextNodeClass parent_class; + void (*layout)(TextLayoutBox *self, PangoContext *context, int width, int offset_x, int offset_y); }; -TextLayoutBox *text_layout_box_new (void); - void text_layout_box_set_item (TextLayoutBox *self, TextItem *item); @@ -49,15 +48,4 @@ text_layout_box_layout (TextLayoutBox *self, const TextDimensions * text_layout_box_get_bbox (TextLayoutBox *self); -PangoLayout * -text_layout_box_get_pango_layout (TextLayoutBox *self); - -void -text_layout_box_set_cursor (TextLayoutBox *self, - int index); - -gboolean -text_layout_box_get_cursor (TextLayoutBox *self, - const TextDimensions **cursor); - G_END_DECLS diff --git a/src/layout/layoutinline.c b/src/layout/layoutinline.c new file mode 100644 index 0000000..22cce77 --- /dev/null +++ b/src/layout/layoutinline.c @@ -0,0 +1,121 @@ +/* layoutinline.c + * + * Copyright 2022 Matthew Jakeman + * + * This file is dual-licensed under the terms of the Mozilla Public + * License 2.0 and the Lesser General Public License 2.1 (or any + * later version). + * + * SPDX-License-Identifier: MPL-2.0 OR LGPL-2.1-or-later + */ + +#include "layoutinline.h" + +typedef struct +{ + int padding; +} TextLayoutInlinePrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (TextLayoutInline, text_layout_inline, TEXT_TYPE_LAYOUT_BOX) + +enum { + PROP_0, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + +/** + * text_layout_inline_new: + * + * Create a new #TextLayoutInline. + * + * Returns: (transfer full): a newly created #TextLayoutInline + */ +TextLayoutInline * +text_layout_inline_new (void) +{ + return g_object_new (TEXT_TYPE_LAYOUT_INLINE, NULL); +} + +static void +text_layout_inline_finalize (GObject *object) +{ + TextLayoutInline *self = (TextLayoutInline *)object; + TextLayoutInlinePrivate *priv = text_layout_inline_get_instance_private (self); + + // TODO: Dispose of children + + G_OBJECT_CLASS (text_layout_inline_parent_class)->finalize (object); +} + +static void +text_layout_inline_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + TextLayoutInline *self = TEXT_LAYOUT_INLINE (object); + + switch (prop_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +text_layout_inline_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + TextLayoutInline *self = TEXT_LAYOUT_INLINE (object); + + switch (prop_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +void +text_layout_inline_layout (TextLayoutBox *self, + PangoContext *context, + int width, + int offset_x, + int offset_y) +{ + TextLayoutInlinePrivate *priv; + TextDimensions *bbox; + + g_return_if_fail (TEXT_IS_LAYOUT_INLINE (self)); + + priv = text_layout_inline_get_instance_private (TEXT_LAYOUT_INLINE (self)); + bbox = text_layout_box_get_mutable_bbox (self); + + bbox->x = offset_x; + bbox->y = offset_y; + bbox->width = 200; + bbox->height = 100; +} + +static void +text_layout_inline_class_init (TextLayoutInlineClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = text_layout_inline_finalize; + object_class->get_property = text_layout_inline_get_property; + object_class->set_property = text_layout_inline_set_property; + + TextLayoutBoxClass *layout_box_class = TEXT_LAYOUT_BOX_CLASS (klass); + + layout_box_class->layout = text_layout_inline_layout; +} + +static void +text_layout_inline_init (TextLayoutInline *self) +{ + TextLayoutInlinePrivate *priv = text_layout_inline_get_instance_private (self); +} diff --git a/src/layout/layoutinline.h b/src/layout/layoutinline.h new file mode 100644 index 0000000..29b1b78 --- /dev/null +++ b/src/layout/layoutinline.h @@ -0,0 +1,35 @@ +/* layoutinline.h + * + * Copyright 2022 Matthew Jakeman + * + * This file is dual-licensed under the terms of the Mozilla Public + * License 2.0 and the Lesser General Public License 2.1 (or any + * later version). + * + * SPDX-License-Identifier: MPL-2.0 OR LGPL-2.1-or-later + */ + +#pragma once + +#include +#include + +#include "layoutbox.h" +#include "layoutbox-impl.h" + +#include "types.h" + +G_BEGIN_DECLS + +#define TEXT_TYPE_LAYOUT_INLINE (text_layout_inline_get_type()) + +G_DECLARE_DERIVABLE_TYPE (TextLayoutInline, text_layout_inline, TEXT, LAYOUT_INLINE, TextLayoutBox) + +struct _TextLayoutInlineClass +{ + TextLayoutBoxClass parent_class; +}; + +TextLayoutInline *text_layout_inline_new (void); + +G_END_DECLS diff --git a/src/layout/meson.build b/src/layout/meson.build index 7ff027e..451ff7e 100644 --- a/src/layout/meson.build +++ b/src/layout/meson.build @@ -1,11 +1,15 @@ text_engine_sources += files([ 'layout.c', - 'layout-box.c' + 'layoutbox.c', + 'layoutblock.c', + 'layoutinline.c' ]) layout_headers = [ 'layout.h', - 'layout-box.h', + 'layoutbox.h', + 'layoutblock.h', + 'layoutinline.h', 'types.h' ] diff --git a/src/layout/types.h b/src/layout/types.h index ee23171..a8fc850 100644 --- a/src/layout/types.h +++ b/src/layout/types.h @@ -11,6 +11,8 @@ /* TODO: Make these GBoxed for introspection support! */ +#pragma once + #include typedef struct diff --git a/src/model/document.c b/src/model/document.c index d2b790f..756d079 100644 --- a/src/model/document.c +++ b/src/model/document.c @@ -99,7 +99,7 @@ text_document_create_mark (TextDocument *doc, g_return_val_if_fail (TEXT_IS_DOCUMENT (doc), NULL); g_return_val_if_fail (TEXT_IS_PARAGRAPH (paragraph), NULL); - new = text_mark_new (paragraph, index, gravity); + new = text_mark_new (doc, paragraph, index, gravity); doc->marks = g_slist_append (doc->marks, new); return new; @@ -144,5 +144,5 @@ text_document_clear_mark (TextDocument *doc, static void text_document_init (TextDocument *self) { - self->cursor = text_mark_new (NULL, 0, TEXT_GRAVITY_RIGHT); + self->cursor = text_mark_new (self, NULL, 0, TEXT_GRAVITY_RIGHT); } diff --git a/src/model/fragment.c b/src/model/fragment.c new file mode 100644 index 0000000..0f0a222 --- /dev/null +++ b/src/model/fragment.c @@ -0,0 +1,121 @@ +/* fragment.c + * + * Copyright 2022 Matthew Jakeman + * + * This file is dual-licensed under the terms of the Mozilla Public + * License 2.0 and the Lesser General Public License 2.1 (or any + * later version). + * + * SPDX-License-Identifier: MPL-2.0 OR LGPL-2.1-or-later + */ + +#include "fragment.h" + +typedef struct +{ + int _padding; +} TextFragmentPrivate; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (TextFragment, text_fragment, TEXT_TYPE_ITEM) + +enum { + PROP_0, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + +static void +text_fragment_finalize (GObject *object) +{ + TextFragment *self = (TextFragment *)object; + TextFragmentPrivate *priv = text_fragment_get_instance_private (self); + + G_OBJECT_CLASS (text_fragment_parent_class)->finalize (object); +} + +static void +text_fragment_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + TextFragment *self = TEXT_FRAGMENT (object); + TextFragmentPrivate *priv = text_fragment_get_instance_private (self); + + switch (prop_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +text_fragment_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + TextFragment *self = TEXT_FRAGMENT (object); + TextFragmentPrivate *priv = text_fragment_get_instance_private (self); + + switch (prop_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +const char * +text_fragment_real_get_text (TextFragment *self) +{ + TextFragmentPrivate *priv = text_fragment_get_instance_private (self); + return ""; +} + +int +text_fragment_get_length (TextFragment *self) +{ + const char *text; + + g_return_val_if_fail (TEXT_IS_FRAGMENT (self), -1); + + text = text_fragment_get_text (self); + // g_print ("Length of %s is %d\n", g_type_name_from_instance (self), (int) g_utf8_strlen (text, -1)); + return (int) g_utf8_strlen (text, -1); +} + +int +text_fragment_get_size_bytes (TextFragment *self) +{ + const char *text; + + g_return_val_if_fail (TEXT_IS_FRAGMENT (self), -1); + + text = text_fragment_get_text (self); + // g_print ("Size of %s is %d\n", g_type_name_from_instance (self), (int) strlen (text)); + return (int) strlen (text); +} + +const char * +text_fragment_get_text (TextFragment *self) +{ + return TEXT_FRAGMENT_CLASS (G_OBJECT_GET_CLASS (self))->get_text (self); +} + +static void +text_fragment_class_init (TextFragmentClass *klass) +{ + klass->get_text = text_fragment_real_get_text; + + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = text_fragment_finalize; + object_class->get_property = text_fragment_get_property; + object_class->set_property = text_fragment_set_property; +} + +static void +text_fragment_init (TextFragment *self) +{ +} diff --git a/src/model/fragment.h b/src/model/fragment.h new file mode 100644 index 0000000..5ff6f6d --- /dev/null +++ b/src/model/fragment.h @@ -0,0 +1,34 @@ +/* fragment.h + * + * Copyright 2022 Matthew Jakeman + * + * This file is dual-licensed under the terms of the Mozilla Public + * License 2.0 and the Lesser General Public License 2.1 (or any + * later version). + * + * SPDX-License-Identifier: MPL-2.0 OR LGPL-2.1-or-later + */ + +#pragma once + +#include + +#include "item.h" + +G_BEGIN_DECLS + +#define TEXT_TYPE_FRAGMENT (text_fragment_get_type()) + +G_DECLARE_DERIVABLE_TYPE (TextFragment, text_fragment, TEXT, FRAGMENT, TextItem) + +struct _TextFragmentClass +{ + TextNodeClass parent_class; + const char *(*get_text)(TextFragment *self); +}; + +int text_fragment_get_length (TextFragment *self); +const char* text_fragment_get_text (TextFragment *self); +int text_fragment_get_size_bytes (TextFragment *self); + +G_END_DECLS diff --git a/src/model/image.c b/src/model/image.c new file mode 100644 index 0000000..6522aa5 --- /dev/null +++ b/src/model/image.c @@ -0,0 +1,107 @@ +/* image.c + * + * Copyright 2022 Matthew Jakeman + * + * This file is dual-licensed under the terms of the Mozilla Public + * License 2.0 and the Lesser General Public License 2.1 (or any + * later version). + * + * SPDX-License-Identifier: MPL-2.0 OR LGPL-2.1-or-later + */ + +#include "image.h" + +struct _TextImage +{ + TextOpaque parent_instance; + gchar *src; +}; + + +G_DEFINE_FINAL_TYPE (TextImage, text_image, TEXT_TYPE_OPAQUE) + +enum { + PROP_0, + PROP_SRC, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + +TextImage * +text_image_new (const gchar *src) +{ + return g_object_new (TEXT_TYPE_IMAGE, + "src", src, + NULL); +} + +static void +text_image_finalize (GObject *object) +{ + TextImage *self = (TextImage *)object; + + G_OBJECT_CLASS (text_image_parent_class)->finalize (object); +} + +static void +text_image_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + TextImage *self = TEXT_IMAGE (object); + + switch (prop_id) + { + case PROP_SRC: + g_value_set_string (value, self->src); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +text_image_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + TextImage *self = TEXT_IMAGE (object); + + switch (prop_id) + { + case PROP_SRC: + if (self->src) + g_free (self->src); + self->src = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +text_image_class_init (TextImageClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = text_image_finalize; + object_class->get_property = text_image_get_property; + object_class->set_property = text_image_set_property; + + properties [PROP_SRC] + = g_param_spec_string ("src", + "Source", + "Source", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +text_image_init (TextImage *self) +{ +} diff --git a/src/model/image.h b/src/model/image.h new file mode 100644 index 0000000..2b2cbb6 --- /dev/null +++ b/src/model/image.h @@ -0,0 +1,27 @@ +/* image.h + * + * Copyright 2022 Matthew Jakeman + * + * This file is dual-licensed under the terms of the Mozilla Public + * License 2.0 and the Lesser General Public License 2.1 (or any + * later version). + * + * SPDX-License-Identifier: MPL-2.0 OR LGPL-2.1-or-later + */ + +#pragma once + +#include + +#include "item.h" +#include "opaque.h" + +G_BEGIN_DECLS + +#define TEXT_TYPE_IMAGE (text_image_get_type()) + +G_DECLARE_FINAL_TYPE (TextImage, text_image, TEXT, IMAGE, TextOpaque) + +TextImage *text_image_new (const gchar *src); + +G_END_DECLS diff --git a/src/model/mark.c b/src/model/mark.c index bc6d6e3..17f19c7 100644 --- a/src/model/mark.c +++ b/src/model/mark.c @@ -21,13 +21,15 @@ G_DEFINE_BOXED_TYPE (TextMark, text_mark, text_mark_copy, text_mark_free) * Returns: (transfer full): A newly created #TextMark */ TextMark * -text_mark_new (TextParagraph *paragraph, - int index, - TextGravity gravity) +text_mark_new (struct _TextDocument *document, + TextParagraph *paragraph, + int index, + TextGravity gravity) { TextMark *self; self = g_slice_new0 (TextMark); + self->document = document; self->paragraph = paragraph; self->index = index; self->gravity = gravity; @@ -51,7 +53,7 @@ text_mark_copy (TextMark *self) g_return_val_if_fail (self, NULL); - copy = text_mark_new (self->paragraph, self->index, self->gravity); + copy = text_mark_new (self->document, self->paragraph, self->index, self->gravity); return copy; } diff --git a/src/model/mark.h b/src/model/mark.h index 953b5c8..9a13db2 100644 --- a/src/model/mark.h +++ b/src/model/mark.h @@ -25,16 +25,19 @@ typedef enum } TextGravity; typedef struct _TextMark TextMark; +struct _TextDocument; struct _TextMark { + struct _TextDocument *document; + TextParagraph *paragraph; - int index; + int index; // byte index (i.e. NOT unicode) TextGravity gravity; }; GType text_mark_get_type (void) G_GNUC_CONST; -TextMark *text_mark_new (TextParagraph *paragraph, int index, TextGravity gravity); +TextMark *text_mark_new (struct _TextDocument *document, TextParagraph *paragraph, int index, TextGravity gravity); TextMark *text_mark_copy (TextMark *self); void text_mark_free (TextMark *self); diff --git a/src/model/meson.build b/src/model/meson.build index c88b796..3467d2e 100644 --- a/src/model/meson.build +++ b/src/model/meson.build @@ -5,7 +5,10 @@ text_engine_sources += files([ 'frame.c', 'paragraph.c', 'mark.c', - 'document.c' + 'document.c', + 'fragment.c', + 'image.c', + 'opaque.c' ]) model_headers = [ @@ -15,7 +18,10 @@ model_headers = [ 'frame.h', 'paragraph.h', 'mark.h', - 'document.h' + 'document.h', + 'fragment.h', + 'image.h', + 'opaque.h' ] install_headers(model_headers, subdir : header_dir / 'model') diff --git a/src/model/opaque.c b/src/model/opaque.c new file mode 100644 index 0000000..63aa29a --- /dev/null +++ b/src/model/opaque.c @@ -0,0 +1,90 @@ +/* opaque.c + * + * Copyright 2022 Matthew Jakeman + * + * This file is dual-licensed under the terms of the Mozilla Public + * License 2.0 and the Lesser General Public License 2.1 (or any + * later version). + * + * SPDX-License-Identifier: MPL-2.0 OR LGPL-2.1-or-later + */ + +#include "opaque.h" + +typedef struct +{ + int _padding; +} TextOpaquePrivate; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (TextOpaque, text_opaque, TEXT_TYPE_FRAGMENT) + +enum { + PROP_0, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + +static void +text_opaque_finalize (GObject *object) +{ + TextOpaque *self = (TextOpaque *)object; + TextOpaquePrivate *priv = text_opaque_get_instance_private (self); + + G_OBJECT_CLASS (text_opaque_parent_class)->finalize (object); +} + +static void +text_opaque_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + TextOpaque *self = TEXT_OPAQUE (object); + + switch (prop_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +text_opaque_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + TextOpaque *self = TEXT_OPAQUE (object); + + switch (prop_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +const char* +text_opaque_get_text (TextFragment *self) +{ + return TEXT_OPAQUE_REPLACEMENT_CHAR; +} + +static void +text_opaque_class_init (TextOpaqueClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = text_opaque_finalize; + object_class->get_property = text_opaque_get_property; + object_class->set_property = text_opaque_set_property; + + TextFragmentClass *fragment_class = TEXT_FRAGMENT_CLASS (klass); + + fragment_class->get_text = text_opaque_get_text; +} + +static void +text_opaque_init (TextOpaque *self) +{ +} diff --git a/src/model/opaque.h b/src/model/opaque.h new file mode 100644 index 0000000..be52261 --- /dev/null +++ b/src/model/opaque.h @@ -0,0 +1,32 @@ +/* opaque.h + * + * Copyright 2022 Matthew Jakeman + * + * This file is dual-licensed under the terms of the Mozilla Public + * License 2.0 and the Lesser General Public License 2.1 (or any + * later version). + * + * SPDX-License-Identifier: MPL-2.0 OR LGPL-2.1-or-later + */ + +#pragma once + +#include + +#include "fragment.h" + +G_BEGIN_DECLS + +#define TEXT_TYPE_OPAQUE (text_opaque_get_type()) + +G_DECLARE_DERIVABLE_TYPE (TextOpaque, text_opaque, TEXT, OPAQUE, TextFragment) + +struct _TextOpaqueClass +{ + TextFragmentClass parent_class; +}; + +// Match the unicode replacement character than GTK uses +static const char TEXT_OPAQUE_REPLACEMENT_CHAR[] = { '\xEF', '\xBF', '\xBC', '\0' }; + +G_END_DECLS diff --git a/src/model/paragraph.c b/src/model/paragraph.c index 52b8d99..ae7086c 100644 --- a/src/model/paragraph.c +++ b/src/model/paragraph.c @@ -70,13 +70,13 @@ text_paragraph_set_property (GObject *object, } void -text_paragraph_append_run (TextParagraph *self, - TextRun *run) +text_paragraph_append_fragment (TextParagraph *self, + TextFragment *fragment) { g_return_if_fail (TEXT_IS_PARAGRAPH (self)); - g_return_if_fail (TEXT_IS_RUN (run)); + g_return_if_fail (TEXT_IS_FRAGMENT (fragment)); - text_node_append_child (TEXT_NODE (self), TEXT_NODE (run)); + text_node_append_child (TEXT_NODE (self), TEXT_NODE (fragment)); } /** @@ -107,9 +107,9 @@ text_paragraph_get_text (TextParagraph *self) { const gchar *run_text; - g_assert (TEXT_IS_RUN (child)); + g_assert (TEXT_IS_FRAGMENT (child)); - g_object_get (child, "text", &run_text, NULL); + run_text = text_fragment_get_text (TEXT_FRAGMENT (child)); g_string_append (str, run_text); } @@ -130,17 +130,36 @@ text_paragraph_get_length (TextParagraph *self) child != NULL; child = text_node_get_next (child)) { - g_assert (TEXT_IS_RUN (child)); - length += text_run_get_length (TEXT_RUN (child)); + length += text_fragment_get_length (TEXT_FRAGMENT (child)); } return length; } -TextRun * -text_paragraph_get_run_at_index (TextParagraph *self, - int index, - int *starting_index) +int +text_paragraph_get_size_bytes (TextParagraph *self) +{ + TextNode *child; + int length; + + g_return_val_if_fail (TEXT_IS_PARAGRAPH (self), -1); + + length = 0; + + for (child = text_node_get_first_child (TEXT_NODE (self)); + child != NULL; + child = text_node_get_next (child)) + { + length += (int) strlen (text_fragment_get_text ((TEXT_FRAGMENT (child)))); + } + + return length; +} + +TextFragment * +text_paragraph_get_item_at_index (TextParagraph *self, + int byte_index, + int *starting_index) { TextNode *child; int length; @@ -149,23 +168,23 @@ text_paragraph_get_run_at_index (TextParagraph *self, g_return_val_if_fail (TEXT_IS_PARAGRAPH (self), NULL); - if (index == 0) + if (byte_index == 0) { TextNode *first; first = text_node_get_first_child (TEXT_NODE (self)); if (starting_index) *starting_index = 0; - return TEXT_RUN (first); + return TEXT_FRAGMENT (first); } for (child = text_node_get_first_child (TEXT_NODE (self)); child != NULL; child = text_node_get_next (child)) { - int delta_length; - g_assert (TEXT_IS_RUN (child)); - delta_length = text_run_get_length (TEXT_RUN (child)); + int delta_size; + g_assert (TEXT_IS_FRAGMENT (child)); + delta_size = text_fragment_get_size_bytes (TEXT_FRAGMENT (child)); // Index is considered part of a run if it is immediately // after the last character in the run. For example: @@ -175,16 +194,18 @@ text_paragraph_get_run_at_index (TextParagraph *self, // ^ // this index is part of the run / // - if (length < index && index <= length + delta_length) + if (length < byte_index && byte_index <= length + delta_size) { if (starting_index) *starting_index = length; - return TEXT_RUN (child); + return TEXT_FRAGMENT (child); } - length += delta_length; + length += delta_size; } + g_critical ("Invalid index: %d passed to text_paragraph_get_item_at_index ()\n", byte_index); + if (starting_index) *starting_index = -1; return NULL; diff --git a/src/model/paragraph.h b/src/model/paragraph.h index a58857b..af3f8d8 100644 --- a/src/model/paragraph.h +++ b/src/model/paragraph.h @@ -23,10 +23,11 @@ G_BEGIN_DECLS G_DECLARE_FINAL_TYPE (TextParagraph, text_paragraph, TEXT, PARAGRAPH, TextBlock) -TextParagraph *text_paragraph_new (void); -void text_paragraph_append_run (TextParagraph *para, TextRun *run); -TextRun *text_paragraph_get_run_at_index (TextParagraph *self, int index, int *starting_index); -int text_paragraph_get_length (TextParagraph *self); -char *text_paragraph_get_text (TextParagraph *self); +TextParagraph *text_paragraph_new (void); +void text_paragraph_append_fragment (TextParagraph *self, TextFragment *fragment); +TextFragment *text_paragraph_get_item_at_index (TextParagraph *self, int byte_index, int *starting_index); +int text_paragraph_get_length (TextParagraph *self); +int text_paragraph_get_size_bytes (TextParagraph *self); +char *text_paragraph_get_text (TextParagraph *self); G_END_DECLS diff --git a/src/model/run.c b/src/model/run.c index 07dd6fa..36bb2e2 100644 --- a/src/model/run.c +++ b/src/model/run.c @@ -13,15 +13,15 @@ struct _TextRun { - TextItem parent_instance; - gchar *text; + TextFragment parent_instance; + char *text; gboolean is_bold; gboolean is_italic; gboolean is_underline; }; -G_DEFINE_FINAL_TYPE (TextRun, text_run, TEXT_TYPE_ITEM) +G_DEFINE_FINAL_TYPE (TextRun, text_run, TEXT_TYPE_FRAGMENT) enum { PROP_0, @@ -57,11 +57,11 @@ text_run_get_property (GObject *object, switch (prop_id) { - case PROP_TEXT: - g_value_set_string (value, self->text); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + case PROP_TEXT: + g_value_set_string (value, self->text); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } @@ -75,24 +75,16 @@ text_run_set_property (GObject *object, switch (prop_id) { - case PROP_TEXT: - if (self->text) - g_free (self->text); - self->text = g_value_dup_string (value); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + case PROP_TEXT: + if (self->text) + g_free (self->text); + self->text = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } -int -text_run_get_length (TextRun *self) -{ - g_return_val_if_fail (TEXT_IS_RUN (self), -1); - - return strlen (self->text); -} - gboolean text_run_get_style_bold (TextRun *self) { @@ -132,6 +124,12 @@ text_run_set_style_underline (TextRun *self, self->is_underline = is_underline; } +const char* +text_run_get_text (TextFragment *self) +{ + return (TEXT_RUN (self))->text; +} + static void text_run_class_init (TextRunClass *klass) { @@ -142,13 +140,17 @@ text_run_class_init (TextRunClass *klass) object_class->set_property = text_run_set_property; properties [PROP_TEXT] - = g_param_spec_string ("text", - "Text", - "Text", - NULL, - G_PARAM_READWRITE|G_PARAM_CONSTRUCT); + = g_param_spec_string ("text", + "Text", + "Text", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT); g_object_class_install_properties (object_class, N_PROPS, properties); + + TextFragmentClass *fragment_class = TEXT_FRAGMENT_CLASS (klass); + + fragment_class->get_text = text_run_get_text; } static void diff --git a/src/model/run.h b/src/model/run.h index 4eaee42..9f5b841 100644 --- a/src/model/run.h +++ b/src/model/run.h @@ -14,15 +14,15 @@ #include #include "item.h" +#include "fragment.h" G_BEGIN_DECLS #define TEXT_TYPE_RUN (text_run_get_type()) -G_DECLARE_FINAL_TYPE (TextRun, text_run, TEXT, RUN, TextItem) +G_DECLARE_FINAL_TYPE (TextRun, text_run, TEXT, RUN, TextFragment) TextRun *text_run_new (const gchar *text); -int text_run_get_length (TextRun *self); gboolean text_run_get_style_bold (TextRun *self); void text_run_set_style_bold (TextRun *self, gboolean is_bold); diff --git a/src/text-engine.c b/src/text-engine.c index 79146a4..f28eeb9 100644 --- a/src/text-engine.c +++ b/src/text-engine.c @@ -18,11 +18,20 @@ void text_engine_init () { + GdkDisplay *display; + GtkCssProvider *provider; + // Add a GTK Inspector page for debugging documents if (g_io_extension_point_lookup ("gtk-inspector-page")) - g_io_extension_point_implement ("gtk-inspector-page", - TEXT_TYPE_INSPECTOR, - "text-engine", - 10); + g_io_extension_point_implement ("gtk-inspector-page", + TEXT_TYPE_INSPECTOR, + "text-engine", + 10); + // Add CSS Provider for internal stylesheet + display = gdk_display_get_default (); + provider = gtk_css_provider_new (); + gtk_css_provider_load_from_resource (provider, "/com/mattjakeman/TextEngine/style.css"); + gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); } diff --git a/src/ui/display.c b/src/ui/display.c index bec043c..47e84ec 100644 --- a/src/ui/display.c +++ b/src/ui/display.c @@ -153,7 +153,8 @@ text_display_set_property (GObject *object, switch (prop_id) { case PROP_DOCUMENT: - text_node_clear (&self->layout_tree); + if (self->layout_tree) + text_node_clear (&self->layout_tree); self->document = g_value_get_object (value); if (self->document) @@ -191,7 +192,7 @@ text_display_set_property (GObject *object, if (adj) { self->hadjustment = g_object_ref_sink (adj); - g_signal_connect_swapped (self->vadjustment, "value-changed", G_CALLBACK (gtk_widget_queue_draw), self); + g_signal_connect_swapped (self->hadjustment, "value-changed", G_CALLBACK (gtk_widget_queue_draw), self); } gtk_widget_queue_allocate (GTK_WIDGET (self)); break; @@ -225,7 +226,9 @@ _rebuild_layout_tree (TextDisplay *self, int width) { g_print ("Rebuilding layout tree\n"); - text_node_clear (&self->layout_tree); + if (self->layout_tree) + text_node_clear (&self->layout_tree); + self->layout_tree = text_layout_build_layout_tree (self->layout, gtk_widget_get_pango_context (GTK_WIDGET (self)), self->document->frame, @@ -233,41 +236,107 @@ _rebuild_layout_tree (TextDisplay *self, int width) } static void -layout_snapshot_recursive (GtkWidget *widget, - TextLayoutBox *layout_box, - GtkSnapshot *snapshot, - GdkRGBA *fg_color, - int *delta_height) +draw_box_recursive (GtkWidget *widget, + TextLayoutBox *layout_box, + GtkSnapshot *snapshot, + GdkRGBA *fg_color, + int *delta_height); + +static void +draw_block (GtkWidget *widget, + TextLayoutBox *layout_box, + GtkSnapshot *snapshot, + GdkRGBA *fg_color, + int *delta_height) { int offset = 0; + TextItem *item; + const TextDimensions *bbox; + // Get bounding box + bbox = text_layout_box_get_bbox (layout_box); + + // Draw children first + gtk_snapshot_save (snapshot); for (TextNode *node = text_node_get_first_child (TEXT_NODE (layout_box)); node != NULL; node = text_node_get_next (node)) { g_assert (TEXT_IS_LAYOUT_BOX (node)); - int delta_height; - layout_snapshot_recursive (widget, TEXT_LAYOUT_BOX (node), snapshot, fg_color, &delta_height); - offset += delta_height; - } + int child_delta_height; - PangoLayout *layout = text_layout_box_get_pango_layout (layout_box); - const TextDimensions *bbox = text_layout_box_get_bbox (layout_box); + draw_box_recursive(widget, TEXT_LAYOUT_BOX(node), snapshot, fg_color, &child_delta_height); + offset += child_delta_height; + } + gtk_snapshot_restore (snapshot); - if (layout) + // Draw Text (if applicable) + if (TEXT_IS_LAYOUT_BLOCK (layout_box)) { - gtk_snapshot_save (snapshot); - gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (0, offset)); - gtk_snapshot_append_layout (snapshot, layout, fg_color); - gtk_snapshot_restore (snapshot); + PangoLayout *layout; + + layout = text_layout_block_get_pango_layout (TEXT_LAYOUT_BLOCK (layout_box)); - offset = bbox->height; + if (layout) + { + gtk_snapshot_save (snapshot); + gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (0, offset)); + gtk_snapshot_append_layout (snapshot, layout, fg_color); + // item = text_layout_box_get_item (layout_box); + // draw_inline_elements (snapshot, layout, item, bbox->x, bbox->y); + gtk_snapshot_restore (snapshot); + } } gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (0, bbox->height)); - *delta_height = bbox->height; + *delta_height = (int) bbox->height; +} + +static void +draw_inline (GtkWidget *widget, + TextLayoutBox *layout_box, + GtkSnapshot *snapshot, + GdkRGBA *fg_color) +{ + GdkRGBA placeholder; + const TextDimensions *bbox; + + gdk_rgba_parse (&placeholder, "red"); + + // Get bounding box + bbox = text_layout_box_get_bbox (layout_box); + + // Draw bounding box as solid colour (for now) + gtk_snapshot_save (snapshot); + gtk_snapshot_append_color (snapshot, &placeholder, + &GRAPHENE_RECT_INIT ( + bbox->x, + bbox->y, + bbox->width, + bbox->height)); + gtk_snapshot_restore (snapshot); +} + +static void +draw_box_recursive (GtkWidget *widget, + TextLayoutBox *layout_box, + GtkSnapshot *snapshot, + GdkRGBA *fg_color, + int *delta_height) +{ + int offset = 0; + TextItem *item; + const TextDimensions *bbox; + + *delta_height = 0; + + // For block elements, draw content and children + if (TEXT_IS_LAYOUT_BLOCK (layout_box)) + draw_block (widget, layout_box, snapshot, fg_color, delta_height); + else if (TEXT_IS_LAYOUT_INLINE (layout_box)) + draw_inline (widget, layout_box, snapshot, fg_color); } static void @@ -275,33 +344,65 @@ draw_cursor_snapshot (GtkSnapshot *snapshot, TextMark *cursor, GdkRGBA *color) { - TextLayoutBox *box; + TextLayoutBlock *block; TextParagraph *item; + TextFragment *inline_item; int index; item = cursor->paragraph; index = cursor->index; - box = TEXT_LAYOUT_BOX (text_item_get_attachment (item)); + inline_item = text_paragraph_get_item_at_index (item, index, NULL); + block = TEXT_LAYOUT_BLOCK (text_item_get_attachment (item)); - if (TEXT_IS_LAYOUT_BOX (box)) { + if (TEXT_IS_LAYOUT_BOX (block)) + { int x, y, height, width; const TextDimensions *bbox; PangoLayout *layout; - bbox = text_layout_box_get_bbox(box); - layout = text_layout_box_get_pango_layout(box); - - PangoRectangle cursor_rect; - pango_layout_index_to_pos (layout, - index, - &cursor_rect); + bbox = text_layout_box_get_bbox (block); - // Hardcode width to 1 - x = cursor_rect.x / PANGO_SCALE; - y = cursor_rect.y / PANGO_SCALE; - height = cursor_rect.height / PANGO_SCALE; - width = 1; + // if (TEXT_IS_RUN (inline_item)) + { + layout = text_layout_block_get_pango_layout (block); + + PangoRectangle cursor_rect; + pango_layout_index_to_pos (layout, + index, + &cursor_rect); + + // Hardcode width to 1 + x = cursor_rect.x / PANGO_SCALE; + y = cursor_rect.y / PANGO_SCALE; + height = cursor_rect.height / PANGO_SCALE; + width = 1; + } + /*else if (inline_item != NULL) + { + // Treat object as opaque with start and end cursor positions + // TODO: Make this far more robust e.g. for editable inlines + // TODO: What even is an inline_box? Give this a proper name + TextLayoutBox *inline_box; + const TextDimensions *inline_bbox; + inline_box = TEXT_LAYOUT_BOX (text_item_get_attachment (inline_item)); + inline_bbox = text_layout_box_get_bbox (inline_box); + + if (index == 0) + { + x = 0; + y = 0; + height = (int) inline_bbox->height; + width = 1; + } + else + { + x = (int) inline_bbox->width; + y = 0; + height = (int) inline_bbox->height; + width = 1; + } + }*/ gtk_snapshot_append_color (snapshot, color, &GRAPHENE_RECT_INIT (bbox->x + x, bbox->y + y, width, height)); } @@ -470,7 +571,7 @@ draw_selection_snapshot (GtkSnapshot *snapshot, TextMark *cursor, TextMark *selection) { - TextLayoutBox *layout; + TextLayoutBlock *layout; TextParagraph *current; const TextDimensions *bbox; gboolean draw_selection; @@ -478,11 +579,11 @@ draw_selection_snapshot (GtkSnapshot *snapshot, // Check if cursor and selection are within the same paragraph if (cursor->paragraph == selection->paragraph) { - layout = TEXT_LAYOUT_BOX (text_item_get_attachment (TEXT_ITEM (cursor->paragraph))); - bbox = text_layout_box_get_bbox (layout); + layout = TEXT_LAYOUT_BLOCK (text_item_get_attachment (TEXT_ITEM (cursor->paragraph))); + bbox = text_layout_box_get_bbox (TEXT_LAYOUT_BOX (layout)); gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (0, bbox->y)); - draw_selection_partial_layout_snapshot (snapshot, text_layout_box_get_pango_layout (layout), + draw_selection_partial_layout_snapshot (snapshot, text_layout_block_get_pango_layout (layout), cursor->index, selection->index, selection_color); @@ -502,8 +603,8 @@ draw_selection_snapshot (GtkSnapshot *snapshot, if (!current) break; - layout = TEXT_LAYOUT_BOX (text_item_get_attachment (TEXT_ITEM (current))); - bbox = text_layout_box_get_bbox (layout); + layout = TEXT_LAYOUT_BLOCK (text_item_get_attachment (TEXT_ITEM (current))); + bbox = text_layout_box_get_bbox (TEXT_LAYOUT_BOX (layout)); gtk_snapshot_save (snapshot); gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (0, bbox->y)); @@ -511,15 +612,15 @@ draw_selection_snapshot (GtkSnapshot *snapshot, if (current == cursor->paragraph) { // Draw partial start segment - draw_selection_partial_layout_snapshot (snapshot, text_layout_box_get_pango_layout (layout), + draw_selection_partial_layout_snapshot (snapshot, text_layout_block_get_pango_layout (layout), cursor->index, - text_paragraph_get_length(cursor->paragraph), + text_paragraph_get_size_bytes (cursor->paragraph), selection_color); } else if (current == selection->paragraph) { // Draw partial end segment - draw_selection_partial_layout_snapshot (snapshot, text_layout_box_get_pango_layout (layout), + draw_selection_partial_layout_snapshot (snapshot, text_layout_block_get_pango_layout (layout), 0, selection->index, selection_color); @@ -531,7 +632,7 @@ draw_selection_snapshot (GtkSnapshot *snapshot, { // Draw full segment draw_selection_layout_snapshot (snapshot, - text_layout_box_get_pango_layout (layout), + text_layout_block_get_pango_layout (layout), selection_color); } @@ -590,7 +691,7 @@ text_display_snapshot (GtkWidget *widget, // Draw layout tree gtk_snapshot_save (snapshot); - layout_snapshot_recursive (widget, self->layout_tree, snapshot, &fg_color, &delta_height); + draw_box_recursive(widget, self->layout_tree, snapshot, &fg_color, &delta_height); gtk_snapshot_restore (snapshot); // Draw cursors @@ -624,7 +725,9 @@ text_display_measure (GtkWidget *widget, // Account for start/end margins for_size -= self->margin_start + self->margin_end; - text_node_clear (&self->layout_tree); + if (self->layout_tree) + text_node_clear (&self->layout_tree); + self->layout_tree = text_layout_build_layout_tree (self->layout, context, self->document->frame, @@ -797,7 +900,7 @@ commit (GtkIMContext *context, self->document->selection != NULL ? text_editor_replace (self->editor, TEXT_EDITOR_CURSOR, TEXT_EDITOR_SELECTION, str) - : text_editor_insert (self->editor, TEXT_EDITOR_CURSOR, str); + : text_editor_insert_text(self->editor, TEXT_EDITOR_CURSOR, str); _unset_selection (self->document); @@ -816,17 +919,17 @@ static gboolean _move_cursor_home (TextMark *cursor) { TextParagraph *para; - TextLayoutBox *layout; + TextLayoutBlock *layout; int index; index = cursor->index; para = cursor->paragraph; - layout = TEXT_LAYOUT_BOX (text_item_get_attachment (TEXT_ITEM (para))); + layout = TEXT_LAYOUT_BLOCK (text_item_get_attachment (TEXT_ITEM (para))); if (layout) { PangoLayout *pango; GSList *iter; - pango = text_layout_box_get_pango_layout (layout); + pango = text_layout_block_get_pango_layout (layout); int base_index = 0; for (iter = pango_layout_get_lines (pango); @@ -866,17 +969,17 @@ static gboolean _move_cursor_end (TextMark *cursor) { TextParagraph *para; - TextLayoutBox *layout; + TextLayoutBlock *layout; int index; index = cursor->index; para = cursor->paragraph; - layout = TEXT_LAYOUT_BOX (text_item_get_attachment (TEXT_ITEM (para))); + layout = TEXT_LAYOUT_BLOCK (text_item_get_attachment (TEXT_ITEM (para))); if (layout) { PangoLayout *pango; GSList *iter; - pango = text_layout_box_get_pango_layout (layout); + pango = text_layout_block_get_pango_layout (layout); iter = pango_layout_get_lines (pango); int base_index = 0; @@ -917,7 +1020,7 @@ _move_cursor_vertically (TextMark *cursor, gboolean up) { TextParagraph *para; - TextLayoutBox *box_layout; + TextLayoutBlock *block_layout; PangoLayout *pango_layout; PangoLayoutLine *line; PangoRectangle position; @@ -927,8 +1030,8 @@ _move_cursor_vertically (TextMark *cursor, index = cursor->index; para = cursor->paragraph; - box_layout = TEXT_LAYOUT_BOX (text_item_get_attachment (TEXT_ITEM (para))); - pango_layout = text_layout_box_get_pango_layout(box_layout); + block_layout = TEXT_LAYOUT_BLOCK (text_item_get_attachment (TEXT_ITEM (para))); + pango_layout = text_layout_block_get_pango_layout(block_layout); // First try move within the paragraph pango_layout_index_to_line_x (pango_layout, index, FALSE, &cur_line_index, &x_pos); @@ -945,18 +1048,18 @@ _move_cursor_vertically (TextMark *cursor, // If there are no more lines left, move to above or below box_layout pango_layout_index_to_pos(pango_layout, index, &position); - box_layout = up - ? text_layout_find_above (box_layout) - : text_layout_find_below (box_layout); + block_layout = up + ? TEXT_LAYOUT_BLOCK (text_layout_find_above (TEXT_LAYOUT_BOX (block_layout))) + : TEXT_LAYOUT_BLOCK (text_layout_find_below (TEXT_LAYOUT_BOX (block_layout))); - if (!box_layout) + if (!block_layout) return FALSE; - pango_layout = text_layout_box_get_pango_layout(box_layout); + pango_layout = text_layout_block_get_pango_layout (block_layout); line = pango_layout_get_line(pango_layout, up ? pango_layout_get_line_count (pango_layout) - 1 : 0); pango_layout_line_x_to_index (line, position.x, &index, NULL); - para = TEXT_PARAGRAPH (text_layout_box_get_item (box_layout)); + para = TEXT_PARAGRAPH (text_layout_box_get_item (TEXT_LAYOUT_BOX (block_layout))); cursor->index = index; cursor->paragraph = para; @@ -1118,6 +1221,18 @@ key_pressed (GtkEventControllerKey *controller, goto reallocate; } + // Select all + if (keyval == GDK_KEY_a && ctrl_pressed) + { + if (!selection) + _set_selection (self->document); + + text_editor_move_first (self->editor, TEXT_EDITOR_SELECTION); + text_editor_move_last (self->editor, TEXT_EDITOR_CURSOR); + + goto reallocate; + } + // Handle formatting if (keyval == GDK_KEY_b && ctrl_pressed) { @@ -1131,6 +1246,40 @@ key_pressed (GtkEventControllerKey *controller, goto reallocate; } + if (keyval == GDK_KEY_i && ctrl_pressed) + { + gboolean is_italic; + + is_italic = text_editor_get_format_italic_at_mark (self->editor, self->document->cursor); + text_editor_apply_format_italic (self->editor, + self->document->cursor, + self->document->selection, + !is_italic); + goto reallocate; + } + + if (keyval == GDK_KEY_u && ctrl_pressed) + { + gboolean is_underline; + + is_underline = text_editor_get_format_underline_at_mark (self->editor, self->document->cursor); + text_editor_apply_format_underline (self->editor, + self->document->cursor, + self->document->selection, + !is_underline); + goto reallocate; + } + + // Insert image + if (keyval == GDK_KEY_1 && ctrl_pressed) + { + TextImage *img; + img = text_image_new ("placeholder.png"); + text_editor_insert_fragment(self->editor, TEXT_EDITOR_CURSOR, TEXT_FRAGMENT(img)); + + goto reallocate; + } + return FALSE; reallocate: @@ -1170,18 +1319,29 @@ set_mark_from_cursor (TextDisplay *self, // TODO: Properly find the nearest leaf node // when we have more complex renderers - g_return_if_fail (TEXT_IS_PARAGRAPH (item)); - int index, trailing; + if (TEXT_IS_PARAGRAPH (item)) + { + int index, trailing; + + // Pango automatically clamps the coordinates to the layout for us + pango_layout_xy_to_index (text_layout_block_get_pango_layout (TEXT_LAYOUT_BLOCK (box)), + (int)((x - bbox->x) * (double)PANGO_SCALE), + (int)((y - bbox->y) * (double)PANGO_SCALE), + &index, &trailing); - // Pango automatically clamps the coordinates to the layout for us - pango_layout_xy_to_index (text_layout_box_get_pango_layout (box), - (x - bbox->x) * PANGO_SCALE, - (y - bbox->y) * PANGO_SCALE, - &index, &trailing); + mark->paragraph = TEXT_PARAGRAPH (item); + mark->index = index; + } + else if (TEXT_IS_IMAGE (item)) + { + // Treat bounding box opaquely + mark->index = 0; + mark->paragraph = TEXT_PARAGRAPH (text_node_get_parent (TEXT_NODE (item))); + if ((x - bbox->x) > (bbox->width / 2)) + mark->index = 1; - mark->paragraph = TEXT_PARAGRAPH (item); - mark->index = index; + } } } } diff --git a/src/ui/inspector.c b/src/ui/inspector.c index 6fa1bf5..d30efc5 100644 --- a/src/ui/inspector.c +++ b/src/ui/inspector.c @@ -14,13 +14,14 @@ #include "display.h" #include "../model/document.h" #include "../model/run.h" +#include "../model/image.h" struct _TextInspector { GtkWidget parent_instance; GObject *object; - TextFrame *frame; + TextDocument *document; GtkWidget *vbox; GtkWidget *colview; @@ -39,9 +40,7 @@ static GParamSpec *properties [N_PROPS]; #define TITLE "Text Engine" -static void -bind_frame (TextInspector *inspector, - TextFrame *frame); +static void populate_data_from_frame (TextInspector *inspector); TextInspector * text_inspector_new (void) @@ -96,11 +95,9 @@ text_inspector_set_property (GObject *object, if (TEXT_IS_DISPLAY (self->object)) { TextDocument *document; - g_object_get (self->object, "document", &document, NULL); - - if (TEXT_IS_FRAME (document->frame)) - bind_frame (self, document->frame); + self->document = document; + populate_data_from_frame (self); } break; default: @@ -114,7 +111,7 @@ create_list_model (TextItem *item) GListStore *store; TextNode *node; - if (TEXT_IS_RUN (item)) + if (TEXT_IS_FRAGMENT (item)) return NULL; store = g_list_store_new (TEXT_TYPE_ITEM); @@ -132,18 +129,18 @@ create_list_model (TextItem *item) } static void -bind_frame (TextInspector *self, - TextFrame *frame) +populate_data_from_frame (TextInspector *self) { GtkTreeListModel *tree_model; GtkSingleSelection *selection_model; GListStore *root; g_return_if_fail (TEXT_IS_INSPECTOR (self)); - g_return_if_fail (TEXT_IS_FRAME (frame)); + g_return_if_fail (TEXT_IS_DOCUMENT (self->document)); + g_return_if_fail (TEXT_IS_FRAME (self->document->frame)); root = g_list_store_new (TEXT_TYPE_ITEM); - g_list_store_append (root, TEXT_ITEM (frame)); + g_list_store_append (root, TEXT_ITEM (self->document->frame)); tree_model = gtk_tree_list_model_new (G_LIST_MODEL (root), FALSE, TRUE, (GtkTreeListModelCreateModelFunc) create_list_model, @@ -231,17 +228,27 @@ type_bind (GtkSignalListItemFactory *self, } void -text_setup (GtkSignalListItemFactory *self, - GtkListItem *listitem, - gpointer user_data) +common_setup (GtkSignalListItemFactory *self, + GtkListItem *listitem, + gpointer user_data) { - GtkWidget *label; + GtkWidget *label, *tag; + GtkWidget *hbox; + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + + tag = gtk_label_new (""); + gtk_label_set_xalign (GTK_LABEL (tag), 0.5f); + gtk_widget_add_css_class (tag, "inspector-tag"); + gtk_box_append (GTK_BOX (hbox), tag); + gtk_widget_hide (tag); label = gtk_label_new (""); gtk_label_set_xalign (GTK_LABEL (label), 0); gtk_label_set_single_line_mode (GTK_LABEL (label), TRUE); + gtk_box_append (GTK_BOX (hbox), label); - gtk_list_item_set_child (listitem, label); + gtk_list_item_set_child (listitem, hbox); } void @@ -249,19 +256,23 @@ text_bind (GtkSignalListItemFactory *self, GtkListItem *listitem, gpointer user_data) { - GtkWidget *label; + GtkWidget *hbox, *tag, *label; GtkTreeListRow *row; TextItem *item; - const gchar *type; - label = gtk_list_item_get_child (listitem); + hbox = gtk_list_item_get_child (listitem); row = GTK_TREE_LIST_ROW (gtk_list_item_get_item (listitem)); item = gtk_tree_list_row_get_item (row); + tag = gtk_widget_get_first_child ( hbox); + label = gtk_widget_get_next_sibling (tag); + g_assert (GTK_IS_TREE_LIST_ROW (row)); g_assert (TEXT_IS_ITEM (item)); + gtk_widget_hide (tag); + if (TEXT_IS_RUN (item)) { const gchar *text; @@ -269,6 +280,59 @@ text_bind (GtkSignalListItemFactory *self, g_object_get (item, "text", &text, NULL); gtk_label_set_text (GTK_LABEL (label), text); } + else if (TEXT_IS_IMAGE (item)) + { + const gchar *src; + + g_object_get (item, "src", &src, NULL); + gtk_label_set_text (GTK_LABEL (label), src); + + gtk_widget_show (tag); + gtk_label_set_text (GTK_LABEL (tag), "image"); + } + else + { + gtk_label_set_text (GTK_LABEL (label), NULL); + } +} + +void +style_bind (GtkSignalListItemFactory *self, + GtkListItem *listitem, + gpointer user_data) +{ + GtkWidget *hbox, *tag, *label; + GtkTreeListRow *row; + TextItem *item; + + hbox = gtk_list_item_get_child (listitem); + row = GTK_TREE_LIST_ROW (gtk_list_item_get_item (listitem)); + + item = gtk_tree_list_row_get_item (row); + + tag = gtk_widget_get_first_child ( hbox); + label = gtk_widget_get_next_sibling (tag); + + g_assert (GTK_IS_TREE_LIST_ROW (row)); + g_assert (TEXT_IS_ITEM (item)); + + if (TEXT_IS_RUN (item)) + { + GString *string; + char *text; + + string = g_string_new (""); + if (text_run_get_style_bold (TEXT_RUN (item))) + string = g_string_append (string, "bold "); + if (text_run_get_style_italic (TEXT_RUN (item))) + string = g_string_append (string, "italic "); + if (text_run_get_style_underline (TEXT_RUN (item))) + string = g_string_append (string, "underline "); + + text = g_string_free (string, FALSE); + gtk_label_set_text (GTK_LABEL (label), text); + g_free (text); + } else { gtk_label_set_text (GTK_LABEL (label), NULL); @@ -298,7 +362,7 @@ setup_colview () gtk_column_view_append_column (GTK_COLUMN_VIEW (colview), column); factory = gtk_signal_list_item_factory_new (); - g_signal_connect (factory, "setup", G_CALLBACK (text_setup), NULL); + g_signal_connect (factory, "setup", G_CALLBACK (common_setup), NULL); g_signal_connect (factory, "bind", G_CALLBACK (text_bind), NULL); column = gtk_column_view_column_new ("Contents", factory); @@ -306,6 +370,15 @@ setup_colview () gtk_column_view_column_set_resizable (column, TRUE); gtk_column_view_append_column (GTK_COLUMN_VIEW (colview), column); + factory = gtk_signal_list_item_factory_new (); + g_signal_connect (factory, "setup", G_CALLBACK (common_setup), NULL); + g_signal_connect (factory, "bind", G_CALLBACK (style_bind), NULL); + + column = gtk_column_view_column_new ("Style", factory); + gtk_column_view_column_set_expand (column, TRUE); + gtk_column_view_column_set_resizable (column, TRUE); + gtk_column_view_append_column (GTK_COLUMN_VIEW (colview), column); + return colview; } @@ -314,6 +387,7 @@ text_inspector_init (TextInspector *self) { GtkWidget *infobar; GtkWidget *label; + GtkWidget *scroll_area; self->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); gtk_widget_set_parent (self->vbox, GTK_WIDGET (self)); @@ -323,8 +397,13 @@ text_inspector_init (TextInspector *self) infobar = gtk_info_bar_new (); gtk_info_bar_add_child (GTK_INFO_BAR (infobar), label); + gtk_info_bar_add_button (GTK_INFO_BAR (infobar), "Refresh Model", GTK_BUTTONS_OK); + g_signal_connect_swapped (infobar, "response", G_CALLBACK (populate_data_from_frame), self); gtk_box_append (GTK_BOX (self->vbox), infobar); + scroll_area = gtk_scrolled_window_new (); + gtk_box_append (GTK_BOX (self->vbox), scroll_area); + self->colview = setup_colview (); - gtk_box_append (GTK_BOX (self->vbox), self->colview); + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scroll_area), self->colview); } diff --git a/src/ui/meson.build b/src/ui/meson.build index e7fb5f7..48ad025 100644 --- a/src/ui/meson.build +++ b/src/ui/meson.build @@ -7,4 +7,11 @@ ui_headers = [ 'display.h', ] +gnome = import('gnome') +text_engine_sources += gnome.compile_resources('resources', + 'resources.gresource.xml', + source_dir: '.', + c_name: 'resources' +) + install_headers(ui_headers, subdir : header_dir / 'ui') diff --git a/src/ui/resources.gresource.xml b/src/ui/resources.gresource.xml new file mode 100644 index 0000000..3d0e22e --- /dev/null +++ b/src/ui/resources.gresource.xml @@ -0,0 +1,6 @@ + + + + style.css + + diff --git a/src/ui/style.css b/src/ui/style.css new file mode 100644 index 0000000..a5bdf57 --- /dev/null +++ b/src/ui/style.css @@ -0,0 +1,6 @@ +.inspector-tag { + background-color: @blue_5; + color: @fg_color; + padding: 4px; + border-radius: 4px; +} \ No newline at end of file diff --git a/test/delete.c b/test/delete.c index 8e3d13b..d848426 100644 --- a/test/delete.c +++ b/test/delete.c @@ -50,19 +50,19 @@ delete_fixture_set_up (DeleteFixture *fixture, run1 = text_run_new (RUN1); run2 = text_run_new (RUN2); run3 = text_run_new (RUN3); - text_paragraph_append_run (para1, run1); - text_paragraph_append_run (para1, run2); - text_paragraph_append_run (para1, run3); + text_paragraph_append_fragment(para1, run1); + text_paragraph_append_fragment(para1, run2); + text_paragraph_append_fragment(para1, run3); text_frame_append_block (frame, TEXT_BLOCK (para1)); para2 = text_paragraph_new (); run4 = text_run_new (RUN4); - text_paragraph_append_run (para2, run4); + text_paragraph_append_fragment(para2, run4); text_frame_append_block (frame, TEXT_BLOCK (para2)); para3 = text_paragraph_new (); run5 = text_run_new (RUN5); - text_paragraph_append_run (para3, run5); + text_paragraph_append_fragment(para3, run5); text_frame_append_block (frame, TEXT_BLOCK (para3)); fixture->doc = text_document_new (); @@ -115,7 +115,7 @@ test_whole_run_others_in_paragraph (DeleteFixture *fixture, text_editor_delete (fixture->editor, TEXT_EDITOR_CURSOR, 10); // run 2 should no longer exist - g_assert_true (text_editor_get_run (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run1); + g_assert_true (text_editor_get_item (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run1); g_assert_cmpint (fixture->doc->cursor->index, ==, 10); // check length @@ -150,7 +150,7 @@ test_whole_run_last_in_paragraph (DeleteFixture *fixture, g_assert_cmpint (length, ==, 0); // check cursor position - g_assert_true (text_editor_get_run (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run4); + g_assert_true (text_editor_get_item (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run4); g_assert_cmpint (fixture->doc->cursor->index, ==, 0); // check text diff --git a/test/insert.c b/test/insert.c index 4b6975d..5ca110b 100644 --- a/test/insert.c +++ b/test/insert.c @@ -42,13 +42,13 @@ insert_fixture_set_up (InsertFixture *fixture, para1 = text_paragraph_new (); run1 = text_run_new (RUN1); run2 = text_run_new (RUN2); - text_paragraph_append_run (para1, run1); - text_paragraph_append_run (para1, run2); + text_paragraph_append_fragment(para1, run1); + text_paragraph_append_fragment(para1, run2); text_frame_append_block (frame, TEXT_BLOCK (para1)); para2 = text_paragraph_new (); run3 = text_run_new (RUN3); - text_paragraph_append_run (para2, run3); + text_paragraph_append_fragment(para2, run3); text_frame_append_block (frame, TEXT_BLOCK (para2)); fixture->doc = text_document_new (); @@ -79,7 +79,7 @@ test_insert_test_start (InsertFixture *fixture, gchar *text; - text_editor_insert (fixture->editor, TEXT_EDITOR_CURSOR, "Alas! "); + text_editor_insert_text(fixture->editor, TEXT_EDITOR_CURSOR, "Alas! "); // changed g_object_get (fixture->run1, "text", &text, NULL); @@ -106,7 +106,7 @@ test_insert_test_middle (InsertFixture *fixture, gchar *text; text_editor_move_right (fixture->editor, TEXT_EDITOR_CURSOR, 26); - text_editor_insert (fixture->editor, TEXT_EDITOR_CURSOR, "n't"); + text_editor_insert_text(fixture->editor, TEXT_EDITOR_CURSOR, "n't"); // changed g_object_get (fixture->run1, "text", &text, NULL); @@ -133,7 +133,7 @@ test_insert_test_end (InsertFixture *fixture, gchar *text; text_editor_move_right (fixture->editor, TEXT_EDITOR_CURSOR, 41); - text_editor_insert (fixture->editor, TEXT_EDITOR_CURSOR, "or at least I thought so..."); + text_editor_insert_text(fixture->editor, TEXT_EDITOR_CURSOR, "or at least I thought so..."); // changed g_object_get (fixture->run1, "text", &text, NULL); @@ -157,7 +157,7 @@ test_insert_test_nothing (InsertFixture *fixture, { gchar *text; - text_editor_insert (fixture->editor, TEXT_EDITOR_CURSOR, ""); + text_editor_insert_text(fixture->editor, TEXT_EDITOR_CURSOR, ""); // changed g_object_get (fixture->run1, "text", &text, NULL); diff --git a/test/mark.c b/test/mark.c index bcfa96a..d76d924 100644 --- a/test/mark.c +++ b/test/mark.c @@ -50,19 +50,19 @@ mark_fixture_set_up (MarkFixture *fixture, run1 = text_run_new (RUN1); run2 = text_run_new (RUN2); run3 = text_run_new (RUN3); - text_paragraph_append_run (para1, run1); - text_paragraph_append_run (para1, run2); - text_paragraph_append_run (para1, run3); + text_paragraph_append_fragment(para1, run1); + text_paragraph_append_fragment(para1, run2); + text_paragraph_append_fragment(para1, run3); text_frame_append_block (frame, TEXT_BLOCK (para1)); para2 = text_paragraph_new (); run4 = text_run_new (RUN4); - text_paragraph_append_run (para2, run4); + text_paragraph_append_fragment(para2, run4); text_frame_append_block (frame, TEXT_BLOCK (para2)); para3 = text_paragraph_new (); run5 = text_run_new (RUN5); - text_paragraph_append_run (para3, run5); + text_paragraph_append_fragment(para3, run5); text_frame_append_block (frame, TEXT_BLOCK (para3)); fixture->doc = text_document_new (); @@ -307,7 +307,7 @@ test_insert_on (MarkFixture *fixture, // Perform insertion text_editor_move_right (fixture->editor, TEXT_EDITOR_CURSOR, 9); - text_editor_insert (fixture->editor, TEXT_EDITOR_CURSOR, "Hello"); + text_editor_insert_text(fixture->editor, TEXT_EDITOR_CURSOR, "Hello"); // before: // cursor >< mark @@ -341,7 +341,7 @@ test_insert_after (MarkFixture *fixture, // Perform insertion text_editor_move_right (fixture->editor, TEXT_EDITOR_CURSOR, 9); - text_editor_insert (fixture->editor, TEXT_EDITOR_CURSOR, "Hello"); + text_editor_insert_text(fixture->editor, TEXT_EDITOR_CURSOR, "Hello"); // before: // cursor >< >< mark diff --git a/test/move.c b/test/move.c index bc6e68b..064d23c 100644 --- a/test/move.c +++ b/test/move.c @@ -43,7 +43,7 @@ move_fixture_set_up_single (MoveFixture *fixture, para1 = text_paragraph_new (); run1 = text_run_new (RUN1); - text_paragraph_append_run (para1, run1); + text_paragraph_append_fragment(para1, run1); text_frame_append_block (frame, TEXT_BLOCK (para1)); fixture->doc = text_document_new (); @@ -77,9 +77,9 @@ move_fixture_set_up_runs (MoveFixture *fixture, run1 = text_run_new (RUN5); run2 = text_run_new (RUN6); run3 = text_run_new (RUN7); - text_paragraph_append_run (para1, run1); - text_paragraph_append_run (para1, run2); - text_paragraph_append_run (para1, run3); + text_paragraph_append_fragment(para1, run1); + text_paragraph_append_fragment(para1, run2); + text_paragraph_append_fragment(para1, run3); text_frame_append_block (frame, TEXT_BLOCK (para1)); fixture->doc = text_document_new (); @@ -108,18 +108,18 @@ move_fixture_set_up_paragraphs (MoveFixture *fixture, para1 = text_paragraph_new (); run1 = text_run_new (RUN1); run2 = text_run_new (RUN2); - text_paragraph_append_run (para1, run1); - text_paragraph_append_run (para1, run2); + text_paragraph_append_fragment(para1, run1); + text_paragraph_append_fragment(para1, run2); text_frame_append_block (frame, TEXT_BLOCK (para1)); para2 = text_paragraph_new (); run3 = text_run_new (RUN3); - text_paragraph_append_run (para2, run3); + text_paragraph_append_fragment(para2, run3); text_frame_append_block (frame, TEXT_BLOCK (para2)); para3 = text_paragraph_new (); run4 = text_run_new (RUN4); - text_paragraph_append_run (para3, run4); + text_paragraph_append_fragment(para3, run4); text_frame_append_block (frame, TEXT_BLOCK (para3)); fixture->doc = text_document_new (); @@ -195,11 +195,11 @@ test_left_traversal_across_run (MoveFixture *fixture, // go to index 29 (run two) text_editor_move_right (fixture->editor, TEXT_EDITOR_CURSOR, 29); - g_assert_true (text_editor_get_run (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run2); + g_assert_true (text_editor_get_item (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run2); // test moving left by amount text_editor_move_left (fixture->editor, TEXT_EDITOR_CURSOR, amount); - g_assert_true (text_editor_get_run (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run1); + g_assert_true (text_editor_get_item (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run1); g_assert_cmpint (fixture->doc->cursor->index, ==, 29 - amount); } @@ -213,11 +213,11 @@ test_right_traversal_across_run (MoveFixture *fixture, // go to index 28 (run one) text_editor_move_right (fixture->editor, TEXT_EDITOR_CURSOR, 28); - g_assert_true (text_editor_get_run (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run1); + g_assert_true (text_editor_get_item (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run1); // test moving right by amount text_editor_move_right (fixture->editor, TEXT_EDITOR_CURSOR, amount); - g_assert_true (text_editor_get_run (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run2); + g_assert_true (text_editor_get_item (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run2); g_assert_cmpint (fixture->doc->cursor->index, ==, 28 + amount); } @@ -247,12 +247,12 @@ test_left_traversal_across_paragraph (MoveFixture *fixture, // move to start of p2 (run3) text_editor_move_right (fixture->editor, TEXT_EDITOR_CURSOR, 65); - g_assert_true (text_editor_get_run (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run3); + g_assert_true (text_editor_get_item (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run3); g_assert_cmpint (fixture->doc->cursor->index, ==, 0); // move backwards by amount text_editor_move_left (fixture->editor, TEXT_EDITOR_CURSOR, amount); - g_assert_true (text_editor_get_run (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run2); + g_assert_true (text_editor_get_item (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run2); // check index g_assert_cmpint (fixture->doc->cursor->index, ==, (64 - (amount - 1))); @@ -267,11 +267,11 @@ test_right_traversal_across_paragraph (MoveFixture *fixture, // move to end of p1 (run2) text_editor_move_right (fixture->editor, TEXT_EDITOR_CURSOR, 64); - g_assert_true (text_editor_get_run (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run2); + g_assert_true (text_editor_get_item (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run2); // move forwards by amount text_editor_move_right (fixture->editor, TEXT_EDITOR_CURSOR, amount); - g_assert_true (text_editor_get_run (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run3); + g_assert_true (text_editor_get_item (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run3); // check index g_assert_cmpint (fixture->doc->cursor->index, ==, amount - 1); @@ -283,11 +283,11 @@ test_left_traversal_across_several_paragraphs (MoveFixture *fixture, { // move to start of p3 (run4) text_editor_move_right (fixture->editor, TEXT_EDITOR_CURSOR, 85); - g_assert_true (text_editor_get_run (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run4); + g_assert_true (text_editor_get_item (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run4); // move left by 62 characters (to run1) text_editor_move_left (fixture->editor, TEXT_EDITOR_CURSOR, 62); - g_assert_true (text_editor_get_run (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run1); + g_assert_true (text_editor_get_item (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run1); // check index is 23 in p1 g_assert_cmpint (fixture->doc->cursor->index, ==, 23); @@ -299,7 +299,7 @@ test_right_traversal_across_several_paragraphs (MoveFixture *fixture, { // move to p3, index 2 (run4) text_editor_move_right (fixture->editor, TEXT_EDITOR_CURSOR, 87); - g_assert_true (text_editor_get_run (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run4); + g_assert_true (text_editor_get_item (fixture->editor, TEXT_EDITOR_CURSOR) == fixture->run4); // check index is 2 in p3 g_assert_cmpint (fixture->doc->cursor->index, ==, 2); diff --git a/test/replace.c b/test/replace.c index 21ebb40..d4d5ad8 100644 --- a/test/replace.c +++ b/test/replace.c @@ -50,19 +50,19 @@ replace_fixture_set_up (ReplaceFixture *fixture, run1 = text_run_new (RUN1); run2 = text_run_new (RUN2); run3 = text_run_new (RUN3); - text_paragraph_append_run (para1, run1); - text_paragraph_append_run (para1, run2); - text_paragraph_append_run (para1, run3); + text_paragraph_append_fragment(para1, run1); + text_paragraph_append_fragment(para1, run2); + text_paragraph_append_fragment(para1, run3); text_frame_append_block (frame, TEXT_BLOCK (para1)); para2 = text_paragraph_new (); run4 = text_run_new (RUN4); - text_paragraph_append_run (para2, run4); + text_paragraph_append_fragment(para2, run4); text_frame_append_block (frame, TEXT_BLOCK (para2)); para3 = text_paragraph_new (); run5 = text_run_new (RUN5); - text_paragraph_append_run (para3, run5); + text_paragraph_append_fragment(para3, run5); text_frame_append_block (frame, TEXT_BLOCK (para3)); fixture->doc = text_document_new (); diff --git a/test/split.c b/test/split.c index 05c6ab9..b94ef54 100644 --- a/test/split.c +++ b/test/split.c @@ -50,19 +50,19 @@ split_fixture_set_up (SplitFixture *fixture, run1 = text_run_new (RUN1); run2 = text_run_new (RUN2); run3 = text_run_new (RUN3); - text_paragraph_append_run (para1, run1); - text_paragraph_append_run (para1, run2); - text_paragraph_append_run (para1, run3); + text_paragraph_append_fragment(para1, run1); + text_paragraph_append_fragment(para1, run2); + text_paragraph_append_fragment(para1, run3); text_frame_append_block (frame, TEXT_BLOCK (para1)); para2 = text_paragraph_new (); run4 = text_run_new (RUN4); - text_paragraph_append_run (para2, run4); + text_paragraph_append_fragment(para2, run4); text_frame_append_block (frame, TEXT_BLOCK (para2)); para3 = text_paragraph_new (); run5 = text_run_new (RUN5); - text_paragraph_append_run (para3, run5); + text_paragraph_append_fragment(para3, run5); text_frame_append_block (frame, TEXT_BLOCK (para3)); fixture->doc = text_document_new ();