From a4a06661faf587ac024ac856de0f020b81bd7a8c Mon Sep 17 00:00:00 2001 From: andreasb123 Date: Wed, 2 Feb 2011 14:07:32 +0000 Subject: [PATCH] UTF8 string fix use pango for text layout git-svn-id: svn://svn.code.sf.net/p/xournal/svn/trunk@25 9fe2bcd3-a095-4d8b-a836-9b85dc8d7627 --- src/Xournalpp.cpp | 6 +- src/control/ClipboardHandler.cpp | 3 +- src/control/Cursor.cpp | 7 +- src/control/ZoomControl.cpp | 9 +- src/gui/TextEditor.cpp | 330 ++++++--------------- src/gui/TextEditor.h | 10 +- src/gui/toolbarMenubar/ToolMenuHandler.cpp | 2 +- src/model/String.cpp | 190 +++++++++--- src/model/String.h | 29 +- src/model/StringBuffer.cpp | 2 +- src/pdf/poppler/XojPopplerDocument.cpp | 10 +- src/util/ObjectStream.cpp | 2 +- src/util/OutputStream.cpp | 2 +- src/view/TextView.cpp | 188 +++--------- src/view/TextView.h | 3 +- 15 files changed, 331 insertions(+), 462 deletions(-) diff --git a/src/Xournalpp.cpp b/src/Xournalpp.cpp index 02103792..84e5145d 100644 --- a/src/Xournalpp.cpp +++ b/src/Xournalpp.cpp @@ -178,7 +178,7 @@ int main(int argc, char *argv[]) { if (special) { special = false; String t = tmp; - if (t.length() > 1) { + if (t.size() > 1) { char x[2] = { 0 }; x[0] = hexValue(tmp[0]) << 4 + hexValue(tmp[1]); path += x; @@ -201,12 +201,12 @@ int main(int argc, char *argv[]) { } } + win->show(); + if (!opened) { control->newFile(); } - win->show(); - gtk_main(); ev_metadata_manager_shutdown(); diff --git a/src/control/ClipboardHandler.cpp b/src/control/ClipboardHandler.cpp index 470c7c79..2963cf70 100644 --- a/src/control/ClipboardHandler.cpp +++ b/src/control/ClipboardHandler.cpp @@ -83,7 +83,7 @@ public: gtk_selection_data_set_pixbuf(selection, contents->image); } else if (atomSvg1 == selection->target || atomSvg2 == selection->target) { gtk_selection_data_set(selection, selection->target, 8, (guchar *) contents->svg.c_str(), - contents->svg.length()); + contents->svg.size()); } else if (atomXournal == selection->target) { gtk_selection_data_set(selection, selection->target, 8, (guchar *) contents->str->str, contents->str->len); } @@ -199,7 +199,6 @@ void ClipboardHandler::copy() { GtkTargetList * list = gtk_target_list_new(NULL, 0); GtkTargetEntry *targets; int n_targets; - int len = text.length(); // if we have text elements... if (!text.isEmpty()) { diff --git a/src/control/Cursor.cpp b/src/control/Cursor.cpp index 73cda247..3fc0be8f 100644 --- a/src/control/Cursor.cpp +++ b/src/control/Cursor.cpp @@ -127,9 +127,9 @@ void Cursor::updateCursor() { } else if (type == TOOL_TEXT) { if (this->invisible) { cursor = gdk_cursor_new(GDK_BLANK_CURSOR); + } else { + cursor = gdk_cursor_new(GDK_XTERM); } - - cursor = gdk_cursor_new(GDK_XTERM); } else if (type == TOOL_IMAGE) { // No special cursor needed } else if (type == TOOL_SELECT_RECT || type == TOOL_SELECT_REGION || type == TOOL_SELECT_OBJECT) { @@ -163,7 +163,7 @@ void Cursor::updateCursor() { cursor = gdk_cursor_new(GDK_TCROSS); } } else if (type == TOOL_VERTICAL_SPACE) { - if(this->mouseDown) { + if (this->mouseDown) { cursor = gdk_cursor_new(GDK_SB_V_DOUBLE_ARROW); } } else if (type == TOOL_HAND) { @@ -180,6 +180,7 @@ void Cursor::updateCursor() { while (it.hasNext()) { PageView * p = it.next(); + if (GDK_IS_WINDOW(p->getWidget()->window)) { gdk_window_set_cursor(p->getWidget()->window, cursor); diff --git a/src/control/ZoomControl.cpp b/src/control/ZoomControl.cpp index e5766b10..945da4ae 100644 --- a/src/control/ZoomControl.cpp +++ b/src/control/ZoomControl.cpp @@ -97,11 +97,14 @@ void ZoomControl::zoomOut() { } bool ZoomControl::onScrolledwindowMainScrollEvent(GtkWidget *widget, GdkEventScroll *event, ZoomControl * zoom) { - guint state; + guint state = event->state & gtk_accelerator_get_default_mod_mask(); - state = event->state & gtk_accelerator_get_default_mod_mask(); + // do not handle e.g. ALT + Scroll (e.g. Compiz use this shortcut for setting transparency...) + if (state != 0 && state & ~(GDK_CONTROL_MASK | GDK_SHIFT_MASK)) { + return true; + } - if (state == GDK_CONTROL_MASK) { + if (state & GDK_CONTROL_MASK) { if (event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_LEFT) { zoom->zoomIn(); } else { diff --git a/src/gui/TextEditor.cpp b/src/gui/TextEditor.cpp index 6544bf6b..37f3523e 100644 --- a/src/gui/TextEditor.cpp +++ b/src/gui/TextEditor.cpp @@ -9,38 +9,7 @@ #include "TextEditorWidget.h" -class CharContents { -public: - char c; - double width; -}; - -class LineConentents { -public: - LineConentents() { - this->charContents = NULL; - } - ~LineConentents() { - for (GList * l = this->charContents; l != NULL; l = l->next) { - CharContents * c = (CharContents *) l->data; - delete c; - } - g_list_free(this->charContents); - this->charContents = NULL; - } - - void insertChar(double width, char ch) { - CharContents * c = new CharContents(); - c->width = width; - c->c = ch; - this->charContents = g_list_append(this->charContents, c); - } - - double y; - double height; - - GList * charContents; -}; +// TODO: implement drag & drop TextEditor::TextEditor(PageView * gui, Text * text, bool ownText) { this->gui = gui; @@ -52,7 +21,7 @@ TextEditor::TextEditor(PageView * gui, Text * text, bool ownText) { this->cursorOverwrite = false; this->needImReset = false; this->textWidget = gtk_xoj_int_txt_new(this); - this->contents = NULL; + this->layout = NULL; this->virtualCursor = 0; this->markPosX = 0; this->markPosY = 0; @@ -60,7 +29,6 @@ TextEditor::TextEditor(PageView * gui, Text * text, bool ownText) { this->markPosQueue = false; this->mouseDown = 0; this->undoActions = NULL; - this->fContentsChanged = false; this->lastText = text->getText(); this->buffer = gtk_text_buffer_new(NULL); @@ -68,6 +36,7 @@ TextEditor::TextEditor(PageView * gui, Text * text, bool ownText) { if (txt == NULL) { txt = ""; } + gtk_text_buffer_set_text(this->buffer, txt, -1); GtkTextIter first = { 0 }; @@ -78,16 +47,16 @@ TextEditor::TextEditor(PageView * gui, Text * text, bool ownText) { g_object_get(settings, "gtk-cursor-blink-time", &this->cursorBlinkTime, NULL); g_object_get(settings, "gtk-cursor-blink-timeout", &this->cursorBlinkTimeout, NULL); - this->im_context = gtk_im_context_simple_new(); - gtk_im_context_focus_in(this->im_context); + this->imContext = gtk_im_context_simple_new(); + gtk_im_context_focus_in(this->imContext); - g_signal_connect (this->im_context, "commit", + g_signal_connect (this->imContext, "commit", G_CALLBACK (iMCommitCallback), this); - g_signal_connect (this->im_context, "preedit-changed", + g_signal_connect (this->imContext, "preedit-changed", G_CALLBACK (iMPreeditChangedCallback), this); - g_signal_connect (this->im_context, "retrieve-surrounding", + g_signal_connect (this->imContext, "retrieve-surrounding", G_CALLBACK (iMRetrieveSurroundingCallback), this); - g_signal_connect (this->im_context, "delete-surrounding", + g_signal_connect (this->imContext, "delete-surrounding", G_CALLBACK (imDeleteSurroundingCallback), this); blinkCallback(this); @@ -130,7 +99,7 @@ TextEditor::~TextEditor() { g_source_remove(this->blinkTimeout); } - g_object_unref(this->im_context); + g_object_unref(this->imContext); this->text = NULL; } @@ -228,13 +197,12 @@ void TextEditor::iMPreeditChangedCallback(GtkIMContext *context, TextEditor * te bool TextEditor::iMRetrieveSurroundingCallback(GtkIMContext *context, TextEditor * te) { GtkTextIter start; GtkTextIter end; - gint pos; gchar *text; gtk_text_buffer_get_iter_at_mark(te->buffer, &start, gtk_text_buffer_get_insert(te->buffer)); end = start; - pos = gtk_text_iter_get_line_index(&start); + gint pos = gtk_text_iter_get_line_index(&start); gtk_text_iter_set_line_offset(&start, 0); gtk_text_iter_forward_to_line_end(&end); @@ -271,13 +239,13 @@ bool TextEditor::onKeyPressEvent(GdkEventKey *event) { } bool retval = false; - gboolean obscure = false; + bool obscure = false; GtkTextIter iter; GtkTextMark* insert = gtk_text_buffer_get_insert(this->buffer); gtk_text_buffer_get_iter_at_mark(this->buffer, &iter, insert); bool canInsert = gtk_text_iter_can_insert(&iter, true); - if (gtk_im_context_filter_keypress(this->im_context, event)) { + if (gtk_im_context_filter_keypress(this->imContext, event)) { this->needImReset = true; if (!canInsert) { this->resetImContext(); @@ -315,7 +283,7 @@ bool TextEditor::onKeyReleaseEvent(GdkEventKey *event) { GtkTextMark *insert = gtk_text_buffer_get_insert(this->buffer); gtk_text_buffer_get_iter_at_mark(this->buffer, &iter, insert); - if (gtk_text_iter_can_insert(&iter, true) && gtk_im_context_filter_keypress(this->im_context, event)) { + if (gtk_text_iter_can_insert(&iter, true) && gtk_im_context_filter_keypress(this->imContext, event)) { this->needImReset = true; return true; } @@ -447,43 +415,19 @@ void TextEditor::moveCursor(GtkMovementStep step, int count, bool extend_selecti } void TextEditor::findPos(GtkTextIter * iter, double xPos, double yPos) { - int line = 0; - - for (GList * l = this->contents; l != NULL; l = l->next, line++) { - LineConentents * lc = (LineConentents *) l->data; - - if (lc->y > yPos) { - break; - } - } - gtk_text_iter_set_line(iter, line); - - LineConentents * lc = (LineConentents *) g_list_nth_data(this->contents, line); - if (!lc) { + if (!this->layout) { return; } - double x = 0; - double lastPadding = ABS(xPos - x); - int id = 0; - for (GList * l = lc->charContents; l != NULL; l = l->next, id++) { - CharContents * c = (CharContents *) l->data; - x += c->width; - - double padding = ABS(xPos - x); - if (padding > lastPadding) { - break; - } else { - lastPadding = padding; - } + int index = 0; + if(!pango_layout_xy_to_index(this->layout, xPos * PANGO_SCALE, yPos * PANGO_SCALE, &index, NULL)) { + index++; } - gtk_text_iter_set_line_offset(iter, id); + gtk_text_iter_set_offset(iter, index); } void TextEditor::contentsChanged(bool forceCreateUndoAction) { - this->fContentsChanged = true; - String currentText = getText()->getText(); if (forceCreateUndoAction || ABS(lastText.length()-currentText.length()) > 100) { @@ -507,7 +451,7 @@ UndoAction * TextEditor::getFirstUndoAction() { } void TextEditor::markPos(double x, double y, bool extendSelection) { - if (this->contents == NULL) { + if (this->layout == NULL) { this->markPosX = x; this->markPosY = y; this->markPosExtendSelection = extendSelection; @@ -548,28 +492,19 @@ void TextEditor::mouseReleased() { void TextEditor::jumpALine(GtkTextIter * textIter, int count) { int cursorLine = gtk_text_iter_get_line(textIter); - LineConentents * lc = (LineConentents *) g_list_nth_data(this->contents, cursorLine + count); - if (!lc) { + if(cursorLine + count < 0) { return; } - double x = 0; - double lastPadding = ABS(this->virtualCursor-x); - int id = 0; - for (GList * l = lc->charContents; l != NULL; l = l->next, id++) { - CharContents * c = (CharContents *) l->data; - x += c->width; - - double padding = ABS(this->virtualCursor-x); - if (padding > lastPadding) { - break; - } else { - lastPadding = padding; - } + PangoLayoutLine * line = pango_layout_get_line(this->layout, cursorLine + count); + if(line == NULL) { + return; } - gtk_text_iter_set_line(textIter, cursorLine + count); - gtk_text_iter_set_line_offset(textIter, id); + int index = 0; + pango_layout_line_x_to_index(line, this->virtualCursor * PANGO_SCALE, &index, NULL); + + gtk_text_iter_set_offset(textIter, index); } void TextEditor::calcVirtualCursor() { @@ -577,19 +512,12 @@ void TextEditor::calcVirtualCursor() { GtkTextIter cursorIter = { 0 }; GtkTextMark * cursor = gtk_text_buffer_get_insert(this->buffer); gtk_text_buffer_get_iter_at_mark(this->buffer, &cursorIter, cursor); - int cursorLine = gtk_text_iter_get_line(&cursorIter); - int cursorPos = gtk_text_iter_get_line_offset(&cursorIter); - LineConentents * lc = (LineConentents *) g_list_nth_data(this->contents, cursorLine); - if (!lc) { - return; - } + int offset = gtk_text_iter_get_offset(&cursorIter); - int id = 0; - for (GList * l = lc->charContents; l != NULL && id < cursorPos; l = l->next, id++) { - CharContents * c = (CharContents *) l->data; - this->virtualCursor += c->width; - } + PangoRectangle rect = { 0 }; + pango_layout_index_to_pos(this->layout, offset, &rect); + this->virtualCursor = ((double) rect.x) / PANGO_SCALE; } void TextEditor::moveCursor(const GtkTextIter *new_location, gboolean extend_selection) { @@ -800,7 +728,7 @@ void TextEditor::pasteFromClipboard() { void TextEditor::resetImContext() { if (this->needImReset) { this->needImReset = false; - gtk_im_context_reset(this->im_context); + gtk_im_context_reset(this->imContext); } } @@ -841,7 +769,7 @@ void TextEditor::redrawEditor() { gtk_widget_queue_draw(gui->getWidget()); } -void TextEditor::drawCursor(cairo_t * cr, double & x0, double & x, double & y, cairo_font_extents_t & fe, double zoom) { +void TextEditor::drawCursor(cairo_t * cr, double x, double y, double height, double zoom) { double cw = 2 / zoom; double dX = 0; if (this->cursorOverwrite) { @@ -853,182 +781,114 @@ void TextEditor::drawCursor(cairo_t * cr, double & x0, double & x, double & y, c // Not draw cursor if a move is pending if (!this->markPosQueue) { - cairo_rectangle(cr, x0 + x + dX, y - fe.height + fe.descent, cw, fe.height); if (this->cursorVisible) { - cairo_set_source_rgb(cr, 0, 0, 0); - } else { - cairo_set_source_rgb(cr, 1, 1, 1); + cairo_rectangle(cr, x + dX, y, cw, height); + cairo_fill(cr); } - cairo_fill(cr); } DocumentView::applyColor(cr, this->text); - - if (!preeditString.isEmpty()) { - cairo_text_extents_t ex = { 0 }; - cairo_text_extents(cr, this->preeditString.c_str(), &ex); - cairo_move_to(cr, x0 + x, y); - cairo_show_text(cr, this->preeditString.c_str()); - - cairo_set_line_width(cr, 1 / zoom); - - cairo_move_to(cr, x0 + x, y + 2); - x += ex.x_advance; - cairo_line_to(cr, x0 + x, y + 2); - cairo_stroke(cr); - } } void TextEditor::paint(cairo_t * cr, GdkEventExpose *event, double zoom) { - double x0 = this->text->getX(); - double y0 = this->text->getY(); - double y = y0; - double width = 0; GdkColor selectionColor = gui->getSelectionColor(); - DocumentView::applyColor(cr, this->text); - TextView::initCairo(cr, this->text); + cairo_save(cr); - cairo_text_extents_t extents = { 0 }; - cairo_font_extents_t fe = { 0 }; + DocumentView::applyColor(cr, this->text); // This need to be here, why...? I don't know, the size should be calculated anyway if t->getX() is called... this->text->getElementWidth(); - y += 3; - cairo_font_extents(cr, &fe); - - GtkTextIter iter = { 0 }; - gtk_text_buffer_get_iter_at_offset(this->buffer, &iter, 0); - - gunichar c = 1; - - int line = 0; - int pos = 0; - GtkTextIter cursorIter = { 0 }; GtkTextMark * cursor = gtk_text_buffer_get_insert(this->buffer); gtk_text_buffer_get_iter_at_mark(this->buffer, &cursorIter, cursor); - int cursorLine = gtk_text_iter_get_line(&cursorIter); - int cursorPos = gtk_text_iter_get_line_offset(&cursorIter); - - GtkTextIter start; - GtkTextIter end; - bool hasSelection = gtk_text_buffer_get_selection_bounds(this->buffer, &start, &end); - bool inSelection = false; - if (this->fContentsChanged) { - for (GList * l = this->contents; l != NULL; l = l->next) { - LineConentents * contents = (LineConentents *) l->data; - delete contents; - } + double x0 = this->text->getX(); + double y0 = this->text->getY(); + cairo_translate(cr, x0, y0); - g_list_free(this->contents); - this->contents = NULL; - this->fContentsChanged = false; + if (this->layout) { + g_object_unref(this->layout); } + this->layout = TextView::initPango(cr, this->text); - // per line - do { - LineConentents * lc = new LineConentents(); - this->contents = g_list_append(this->contents, lc); - y += fe.height - fe.descent; - lc->y = y - y0; - lc->height = fe.height - fe.descent; - - double x = 0; - pos = 0; - - // per char - while (!gtk_text_iter_ends_line(&iter) && c) { - c = gtk_text_iter_get_char(&iter); - - if (hasSelection) { - if (inSelection && gtk_text_iter_compare(&iter, &end) >= 0) { - inSelection = false; - - // do not process selection again - hasSelection = false; - } else if (!inSelection && gtk_text_iter_compare(&start, &iter) <= 0) { - inSelection = true; - } - } - - gunichar tmp[2] = { 0, 0 }; - tmp[0] = c; - cairo_text_extents_t ex = { 0 }; - cairo_text_extents(cr, (const char *) tmp, &ex); - - if (inSelection) { - cairo_set_source_rgb(cr, selectionColor.red / 65536.0, selectionColor.green / 65536.0, - selectionColor.blue / 65536.0); + if (!preeditString.isEmpty()) { + String text = this->text->getText(); + int pos = gtk_text_iter_get_offset(&cursorIter); + String txt = text.substring(0, pos); + txt += preeditString; + txt += text.substring(pos); - double width = ex.x_advance; - if (c == '\t') { - int tab = x / TextView::TAB_INDEX + 1; - width = tab * TextView::TAB_INDEX - x; - } + PangoAttribute * attrib = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE); + PangoAttrList * list = pango_layout_get_attributes(this->layout); - cairo_rectangle(cr, x + x0 - 0.5, y - fe.height + fe.descent, width + 1, fe.height); - cairo_fill(cr); + attrib->start_index = pos; + attrib->end_index = pos + preeditString.size(); - DocumentView::applyColor(cr, this->text); - } + if (list == NULL) { + list = pango_attr_list_new(); + pango_layout_set_attributes(this->layout, list); + } + pango_attr_list_insert(list, attrib); - if (cursorLine == line && cursorPos == pos) { - drawCursor(cr, x0, x, y, fe, zoom); - } + pango_layout_set_text(layout, txt.c_str(), txt.size()); + } else { + String txt = this->text->getText(); + pango_layout_set_text(layout, txt.c_str(), txt.size()); + } - if (c == '\t') { - double lastX = x; - int tab = x / TextView::TAB_INDEX + 1; - x = tab * TextView::TAB_INDEX; - lc->insertChar(x - lastX, '\t'); - } else { - cairo_move_to(cr, x + x0, y); - cairo_show_text(cr, (const char *) tmp); + GtkTextIter start; + GtkTextIter end; + bool hasSelection = gtk_text_buffer_get_selection_bounds(this->buffer, &start, &end); - x += ex.x_advance; - lc->insertChar(ex.x_advance, (char) c); - } + if (hasSelection) { + PangoAttribute * attrib = pango_attr_background_new(selectionColor.red, selectionColor.green, + selectionColor.blue); + PangoAttrList * list = pango_layout_get_attributes(this->layout); - if (!gtk_text_iter_forward_char(&iter)) { - c = 0; - } - pos++; - } + attrib->start_index = gtk_text_iter_get_offset(&start); + attrib->end_index = gtk_text_iter_get_offset(&end); - if (cursorLine == line && cursorPos == pos) { - drawCursor(cr, x0, x, y, fe, zoom); + if (list == NULL) { + list = pango_attr_list_new(); + pango_layout_set_attributes(this->layout, list); } + pango_attr_list_insert(list, attrib); + } - y += fe.height * 0.25; - - // printf("\n"); + pango_cairo_show_layout(cr, layout); + int w = 0; + int h = 0; + pango_layout_get_size(this->layout, &w, &h); + double width = ((double) w) / PANGO_SCALE; + double height = ((double) h) / PANGO_SCALE; - width = MAX(width, x); + int offset = gtk_text_iter_get_offset(&cursorIter); + PangoRectangle rect = { 0 }; + pango_layout_index_to_pos(layout, offset + preeditString.size(), &rect); + double cX = ((double) rect.x) / PANGO_SCALE; + double cY = ((double) rect.y) / PANGO_SCALE; + double cHeight = ((double) rect.height) / PANGO_SCALE; - if (gtk_text_iter_is_end(&iter)) { - break; - } + drawCursor(cr, cX, cY, cHeight, zoom); - gtk_text_iter_forward_line(&iter); - line++; - } while (c); + cairo_restore(cr); // set the line always the same size on display cairo_set_line_width(cr, 1 / zoom); cairo_set_source_rgb(cr, selectionColor.red / 65536.0, selectionColor.green / 65536.0, selectionColor.blue / 65536.0); - cairo_rectangle(cr, x0 - 5 / zoom, y0 - 5 / zoom, width + 10 / zoom, y - y0 + 10 / zoom); + cairo_rectangle(cr, x0 - 5 / zoom, y0 - 5 / zoom, width + 10 / zoom, height + 10 / zoom); cairo_stroke(cr); this->text->setWidth(width); - this->text->setHeight(y - y0); + this->text->setHeight(height); if (this->markPosQueue) { this->markPosQueue = false; markPos(this->markPosX, this->markPosY, this->markPosExtendSelection); } + } diff --git a/src/gui/TextEditor.h b/src/gui/TextEditor.h index b97fbf89..9f84004c 100644 --- a/src/gui/TextEditor.h +++ b/src/gui/TextEditor.h @@ -39,7 +39,6 @@ public: void pasteFromClipboard(); String getSelection(); - Text * getText(); void textCopyed(); @@ -53,7 +52,7 @@ public: void setFont(XojFont font); private: void redrawEditor(); - void drawCursor(cairo_t * cr, double & x0, double & x, double & y, cairo_font_extents_t & fe, double zoom); + void drawCursor(cairo_t * cr, double x, double y, double height, double zoom); void redrawCursor(); void resetImContext(); @@ -81,17 +80,14 @@ private: GtkWidget *textWidget; - GtkIMContext *im_context; + GtkIMContext *imContext; String preeditString; bool needImReset; GtkTextBuffer * buffer; double virtualCursor; - bool fContentsChanged; GList * undoActions; - GList * contents; - double markPosX; double markPosY; bool markPosExtendSelection; @@ -103,6 +99,8 @@ private: String lastText; + PangoLayout * layout; + int cursorBlinkTime; int cursorBlinkTimeout; bool cursorVisible; diff --git a/src/gui/toolbarMenubar/ToolMenuHandler.cpp b/src/gui/toolbarMenubar/ToolMenuHandler.cpp index f0bc85c3..8b4eff7f 100644 --- a/src/gui/toolbarMenubar/ToolMenuHandler.cpp +++ b/src/gui/toolbarMenubar/ToolMenuHandler.cpp @@ -223,7 +223,7 @@ void ToolMenuHandler::load(ToolbarData * d, GtkWidget * toolbar, const char * to continue; } - if (name.startsWith("COLOR(") && name.length() == 15) { + if (name.startsWith("COLOR(") && name.size() == 15) { String color = name.substring(6, 8); if (!color.startsWith("0x")) { g_warning("Toolbar:COLOR(...) has to start with 0x, get color: %s", color.c_str()); diff --git a/src/model/String.cpp b/src/model/String.cpp index e720faed..d30e9d00 100644 --- a/src/model/String.cpp +++ b/src/model/String.cpp @@ -13,14 +13,27 @@ #include #include +/** + * This code is a modified version of what SQLite uses. + */ +#define SKIP_MULTI_BYTE_SEQUENCE(input) { \ + if( (*(input++)) >= 0xc0 ) { \ + while( (*input & 0xc0) == 0x80 ){ input++; } \ + } \ +} + +#define utf8 unsigned char * + class _RefStrInternal { public: - _RefStrInternal(gchar * str) { + _RefStrInternal(char * str) { this->s = str; if (str == NULL) { - this->length = 0; + this->size = 0; + this->len = 0; } else { - this->length = strlen(str); + this->size = strlen(str); + this->len = length((utf8)str); } } @@ -39,12 +52,26 @@ public: } } - gchar * c_str() { + int length(const utf8 string) { + int len; + len = 0; + while (*string) { + ++len; + SKIP_MULTI_BYTE_SEQUENCE(string); + } + return len; + } + + char * c_str() { return s; } int getLength() const { - return length; + return this->len; + } + + int getSize() const { + return this->size; } private: _RefStrInternal(const _RefStrInternal & str) { @@ -55,8 +82,9 @@ private: private: int nref; - gchar *s; - int length; + char * s; + int size; + int len; }; String::String() { @@ -73,7 +101,7 @@ String::String(const char * str) { this->data->reference(); } -String::String(char * str, gboolean freeAutomatically) { +String::String(char * str, bool freeAutomatically) { if (freeAutomatically) { this->data = new _RefStrInternal(str); } else { @@ -92,8 +120,8 @@ int String::indexOfCaseInsensitiv(String substr, int fromIndex) const { if (source == NULL || target == NULL) { return -1; } - int sourceCount = length(); - int targetCount = substr.length(); + int sourceCount = size(); + int targetCount = substr.size(); if (fromIndex >= sourceCount) { return (targetCount == 0 ? sourceCount : -1); @@ -140,8 +168,8 @@ int String::indexOf(String substr, int fromIndex) const { if (source == NULL || target == NULL) { return -1; } - int sourceCount = length(); - int targetCount = substr.length(); + int sourceCount = size(); + int targetCount = substr.size(); if (fromIndex >= sourceCount) { return (targetCount == 0 ? sourceCount : -1); @@ -183,7 +211,7 @@ int String::indexOf(String substr, int fromIndex) const { } int String::lastIndexOf(String substr) const { - return lastIndexOf(substr, length() - 1); + return lastIndexOf(substr, size() - 1); } int String::lastIndexOf(String substr, int fromIndex) const { @@ -192,8 +220,8 @@ int String::lastIndexOf(String substr, int fromIndex) const { if (source == NULL || target == NULL) { return -1; } - int sourceCount = length(); - int targetCount = substr.length(); + int sourceCount = size(); + int targetCount = substr.size(); if (fromIndex <= 0) { return (targetCount == 0 ? 0 : -1); @@ -230,14 +258,14 @@ int String::lastIndexOf(String substr, int fromIndex) const { return -1; } -gboolean String::contains(const gchar * substr) const { +bool String::contains(const char * substr) const { if (c_str() == NULL || substr == NULL) { return false; } return g_strrstr(c_str(), substr) != NULL; } -gboolean String::equals(const gchar * other) const { +bool String::equals(const char * other) const { if (other == c_str()) { return true; } @@ -247,7 +275,7 @@ gboolean String::equals(const gchar * other) const { return strcmp(c_str(), other) == 0; } -gboolean String::equals(const String & s) const { +bool String::equals(const String & s) const { return equals(s.c_str()); } @@ -257,16 +285,16 @@ String& String::operator=(const char * str) { this->data->reference(); } -String& String::operator=(const String &str) { +String& String::operator=(const String & str) { this->data = str.data; this->data->reference(); } -gboolean String::operator==(const String & str) const { +bool String::operator==(const String & str) const { return equals(str.c_str()); } -gboolean String::operator!=(const String & str) const { +bool String::operator!=(const String & str) const { return !equals(str.c_str()); } @@ -287,7 +315,7 @@ void String::operator +=(double d) { } void String::operator +=(const char * str) { - gchar * data = g_strconcat(c_str(), str, NULL); + char * data = g_strconcat(c_str(), str, NULL); this->data->unreference(); this->data = new _RefStrInternal(data); this->data->reference(); @@ -321,18 +349,29 @@ bool String::operator >(const String & str) const { return strcmp(c_str(), str.c_str()) > 0; } -const gchar * String::c_str() const { +const char * String::c_str() const { return data->c_str(); } -gboolean String::isEmpty() const { +bool String::isEmpty() const { return this->c_str() == NULL || *c_str() == 0; } +/** + * the lengths is the count of chars which this string contains, + * this is may smaller than the size, if there are multibyte UTF8 chars + */ int String::length() const { return this->data->getLength(); } +/** + * The size is the count of bytes which this string contains + */ +int String::size() const { + return this->data->getSize(); +} + String String::substring(int start) const { if (start < 0) { start = length() + start; @@ -345,12 +384,35 @@ String String::substring(int start) const { return substring(start, length() - start); } +//String oldSubstring(String str, int start, int length) { +// if (length < 0) { +// length = str.length() - start + length; +// } +// +// if(start < 0) { +// start = str.length() - start; +// } +// +// if (start + length > str.length() || start < 0 || length < 0) { +// g_critical("substring \"%s\" (%i, %i) out of bounds", str.c_str(), start, length); +// return ""; +// } +// +// const char * orig = str.c_str(); +// char * d = (char *) g_malloc(length + 1); +// strncpy(d, &orig[start], length); +// d[length] = 0; +// +// String substr(d, true); +// return substr; +//} + String String::substring(int start, int length) const { if (length < 0) { length = this->length() - start + length; } - if(start < 0) { + if (start < 0) { start = this->length() - start; } @@ -359,12 +421,40 @@ String String::substring(int start, int length) const { return ""; } - const char * orig = c_str(); - char * d = (char *) g_malloc(length + 1); - strncpy(d, &orig[start], length); - d[length] = 0; + const utf8 string = (utf8)c_str(); + int s = start; + while (*string && s) { + SKIP_MULTI_BYTE_SEQUENCE(string); + --s; + } + + const utf8 str2; + int l = length; + for (str2 = string; *str2 && l; l--) { + SKIP_MULTI_BYTE_SEQUENCE(str2); + } + + int bytes = (int) (str2 - string); + + char * substring = (char *) g_malloc(bytes + 1); + char * output = substring; - String substr(d, true); + for (int i = 0; i < bytes; i++) { + *output++ = *string++; + } + *output = '\0'; + +// String old = oldSubstring(*this, start, length); +// +// const char * error = ""; +// +// if(old != substring) { +// error = "\nMISSMATCH!"; +// } +// +// printf("substring(\"%s\", %i, %i) =\n\"%s\"\n\"%s\"%s\n\n", this->c_str(), start, length, substring, old.c_str(),error); + + String substr(substring, true); return substr; } @@ -382,7 +472,7 @@ String String::trim() { } } - len = length() - start; + len = size() - start; while (len != 0) { char tmp = s[start + len - 1]; @@ -393,30 +483,30 @@ String String::trim() { } } - if (start == 0 && len == length()) { + if (start == 0 && len == size()) { return *this; } return substring(start, len); } -gboolean String::startsWith(const String & s) const { +bool String::startsWith(const String & s) const { return startsWith(s.c_str()); } -gboolean String::startsWith(const char * s) const { +bool String::startsWith(const char * s) const { return g_str_has_prefix(c_str(), s); } -gboolean String::endsWith(const String & s) const { +bool String::endsWith(const String & s) const { return endsWith(s.c_str()); } -gboolean String::equalsIgnorCase(const String & s) { +bool String::equalsIgnorCase(const String & s) { return this->toLowerCase().equals(s.toLowerCase()); } -gboolean String::endsWith(const char * s) const { +bool String::endsWith(const char * s) const { return g_str_has_suffix(c_str(), s); } @@ -425,7 +515,7 @@ String String::toLowerCase() const { char * data = s.data->c_str(); - for (int i = 0; i < s.length(); i++) { + for (int i = 0; i < s.size(); i++) { data[i] = tolower(data[i]); } @@ -437,22 +527,25 @@ String String::toUpperCase() const { char * data = s.data->c_str(); - for (int i = 0; i < s.length(); i++) { + for (int i = 0; i < s.size(); i++) { data[i] = toupper(data[i]); } return s; } +/** + * TODO: this has some bugs!! + */ String String::replace(String search, String replace) const { char const * const original = c_str(); char const * const pattern = search.c_str(); char const * const replacement = replace.c_str(); - size_t const replen = replace.length(); - size_t const patlen = search.length(); - size_t const orilen = length(); + int const replen = replace.size(); + int const patlen = search.size(); + int const orilen = size(); - size_t patcnt = 0; + int patcnt = 0; const char * oriptr; const char * patloc; @@ -466,7 +559,7 @@ String String::replace(String search, String replace) const { } // allocate memory for the new string - size_t const retlen = orilen + patcnt * (replen - patlen); + int const retlen = orilen + patcnt * (replen - patlen); char * const returned = (char *) g_malloc(sizeof(char) * (retlen + 1)); if (returned != NULL) { @@ -474,7 +567,7 @@ String String::replace(String search, String replace) const { // replacing all the instances of the pattern char * retptr = returned; for (oriptr = original; patloc = strstr(oriptr, pattern); oriptr = patloc + patlen) { - size_t const skplen = patloc - oriptr; + int const skplen = patloc - oriptr; // copy the section until the occurence of the pattern strncpy(retptr, oriptr, skplen); retptr += skplen; @@ -489,6 +582,11 @@ String String::replace(String search, String replace) const { return String(returned, true); } +/** + * String tokenizer + * + * THIS CLASS IS NOT UTF8 SAVE + */ StringTokenizer::StringTokenizer(String s, char token, bool returnToken) { this->str = g_strdup(s.c_str()); this->token = token; @@ -497,7 +595,7 @@ StringTokenizer::StringTokenizer(String s, char token, bool returnToken) { this->returnToken = returnToken; this->lastWasToken = false; this->x = 0; - this->len = s.length(); + this->len = s.size(); } StringTokenizer::~StringTokenizer() { diff --git a/src/model/String.h b/src/model/String.h index 025e16b4..75abf8d2 100644 --- a/src/model/String.h +++ b/src/model/String.h @@ -20,14 +20,14 @@ public: String(); String(const String & str); String(const char * data); - String(char * data, gboolean freeAutomatically); + String(char * data, bool freeAutomatically); ~String(); - String& operator=(const String &str); + String& operator=(const String & str); String& operator=(const char * str); - gboolean operator ==(const String & str) const; - gboolean operator !=(const String & str) const; + bool operator ==(const String & str) const; + bool operator !=(const String & str) const; bool operator <(const String & str) const; bool operator >(const String & str) const; @@ -38,7 +38,7 @@ public: String replace(String search, String replace) const; - const gchar * c_str() const; + const char * c_str() const; // gchar * reserveBuffer(int buffer); @@ -47,19 +47,20 @@ public: int lastIndexOf(String substr) const; int lastIndexOf(String substr, int fromIndex) const; - gboolean contains(const gchar * substr) const; + bool contains(const char * substr) const; - gboolean equals(const gchar * other) const; - gboolean equals(const String & s) const; - gboolean isEmpty() const; - gboolean startsWith(const String & s) const; - gboolean startsWith(const char * s) const; - gboolean endsWith(const String & s) const; - gboolean endsWith(const char * s) const; + bool equals(const char * other) const; + bool equals(const String & s) const; + bool isEmpty() const; + bool startsWith(const String & s) const; + bool startsWith(const char * s) const; + bool endsWith(const String & s) const; + bool endsWith(const char * s) const; - gboolean equalsIgnorCase(const String & s); + bool equalsIgnorCase(const String & s); int length() const; + int size() const; String substring(int start, int length) const; String substring(int start) const; String trim(); diff --git a/src/model/StringBuffer.cpp b/src/model/StringBuffer.cpp index 35514814..d51b2e91 100644 --- a/src/model/StringBuffer.cpp +++ b/src/model/StringBuffer.cpp @@ -14,7 +14,7 @@ StringBuffer::~StringBuffer() { } void StringBuffer::append(String s) { - append(s.c_str(), s.length()); + append(s.c_str(), s.size()); } void StringBuffer::append(const char * str) { diff --git a/src/pdf/poppler/XojPopplerDocument.cpp b/src/pdf/poppler/XojPopplerDocument.cpp index 4d9ef4f6..fb979ba3 100644 --- a/src/pdf/poppler/XojPopplerDocument.cpp +++ b/src/pdf/poppler/XojPopplerDocument.cpp @@ -225,17 +225,17 @@ bool XojPopplerDocument::load(const char *uri, const char *password, GError **er #ifdef G_OS_WIN32 wchar_t *filenameW; - int length; + int xxxxxxxxxxxx; - length = MultiByteToWideChar(CP_UTF8, 0, filename, -1, NULL, 0); + xxxxxxxxxxxx = MultiByteToWideChar(CP_UTF8, 0, filename, -1, NULL, 0); - filenameW = new WCHAR[length]; + filenameW = new WCHAR[xxxxxxxxxxxx]; if (!filenameW) return NULL; - length = MultiByteToWideChar(CP_UTF8, 0, filename, -1, filenameW, length); + xxxxxxxxxxxx = MultiByteToWideChar(CP_UTF8, 0, filename, -1, filenameW, xxxxxxxxxxxx); - newDoc = new PDFDoc(filenameW, length, password_g, password_g); + newDoc = new PDFDoc(filenameW, xxxxxxxxxxxx, password_g, password_g); delete filenameW; #else filename_g = new GooString(filename); diff --git a/src/util/ObjectStream.cpp b/src/util/ObjectStream.cpp index 2570da80..fe3dbff0 100644 --- a/src/util/ObjectStream.cpp +++ b/src/util/ObjectStream.cpp @@ -47,7 +47,7 @@ void ObjectOutputStream::writeString(const char * str) { void ObjectOutputStream::writeString(const String & s) { g_string_append(this->data, "_s"); - int len = s.length(); + int len = s.size(); char * c = (char *) &len; g_string_append_len(this->data, c, sizeof(int)); g_string_append_len(this->data, s.c_str(), len); diff --git a/src/util/OutputStream.cpp b/src/util/OutputStream.cpp index 2245eb0d..1fe7aad0 100644 --- a/src/util/OutputStream.cpp +++ b/src/util/OutputStream.cpp @@ -9,7 +9,7 @@ OutputStream::~OutputStream() { } void OutputStream::write(const String & str) { - write(str.c_str(), str.length()); + write(str.c_str(), str.size()); } void OutputStream::write(const char * str) { diff --git a/src/view/TextView.cpp b/src/view/TextView.cpp index 71597e3c..6e7fcad6 100644 --- a/src/view/TextView.cpp +++ b/src/view/TextView.cpp @@ -9,145 +9,71 @@ TextView::TextView() { TextView::~TextView() { } -void TextView::initCairo(cairo_t *cr, Text * t) { - XojFont & font = t->getFont(); - - PangoFontDescription * desc = pango_font_description_from_string(font.getName().c_str()); - pango_font_description_set_size(desc, font.getSize()); - - bool italic = pango_font_description_get_style(desc) != PANGO_STYLE_NORMAL ; - bool bold = pango_font_description_get_weight(desc) == PANGO_WEIGHT_BOLD; - - cairo_select_font_face(cr, pango_font_description_get_family(desc), italic? CAIRO_FONT_SLANT_ITALIC - : CAIRO_FONT_SLANT_NORMAL, bold ? CAIRO_FONT_WEIGHT_BOLD : CAIRO_FONT_WEIGHT_NORMAL); - cairo_set_font_size(cr, font.getSize()); +PangoLayout * TextView::initPango(cairo_t *cr, Text * t) { + PangoLayout * layout = pango_cairo_create_layout(cr); + PangoFontDescription * desc = pango_font_description_from_string(t->getFont().getName().c_str()); + pango_font_description_set_absolute_size(desc, t->getFont().getSize() * PANGO_SCALE); + pango_layout_set_font_description(layout, desc); pango_font_description_free(desc); + // TODO LOW PRIO: add autowrap and text field size for the next xournal release (with new fileformat...) + //pango_layout_set_wrap - // TODO: use http://library.gnome.org/devel/pango/stable/pango-Cairo-Rendering.html - - // double size = font.getSize(); - // cairo_matrix_t m; - // cairo_matrix_init_scale(&m, size, size); - // - // printf("size: %lf, x0 = %lf, xx = %lf, xy = %lf, y0 = %lf, yx = %lf, yy = %lf\n", size, m.x0, m.xx, m.xy, m.y0, m.yx, m.yy); - // - // cairo_set_font_matrix(cr, &m); + return layout; } -void TextView::drawText(cairo_t *cr, Text * t) { - initCairo(cr, t); - - cairo_text_extents_t extents = { 0 }; - cairo_font_extents_t fe = { 0 }; - - // This need to be here, why...? I don't know, the size should be calculated anyway if t->getX() is called... - t->getElementWidth(); - - double y = 3 + t->getY(); - StringTokenizer token(t->getText(), '\n'); - const char * str = token.next(); +void TextView::drawText(cairo_t * cr, Text * t) { + cairo_save(cr); - cairo_font_extents(cr, &fe); + cairo_translate(cr, t->getX(), t->getY()); - // TODO: with "Hallo Test X" this went wrong on some zoom factors... - // printf("fe height: %lf\n", fe.height); - // this is because cairo fonts are not always the same size on different zoom levels!! + PangoLayout * layout = initPango(cr, t); + String str = t->getText(); + pango_layout_set_text(layout, str.c_str(), str.size()); + pango_cairo_show_layout(cr, layout); - while (str) { - y += fe.height - fe.descent; + g_object_unref(layout); - StringTokenizer tokenTab(str, '\t', true); - const char * strTab = tokenTab.next(); - - double x = 0; - - while (strTab) { - if (strcmp(strTab, "\t") == 0) { - int tab = x / TextView::TAB_INDEX + 1; - x = tab * TextView::TAB_INDEX; - } else { - cairo_move_to(cr, t->getX() + x, y); - cairo_show_text(cr, strTab); - - cairo_text_extents(cr, strTab, &extents); - x += extents.x_advance; - } - - strTab = tokenTab.next(); - } - y += fe.height * 0.25; - - // double x = t->getX(); - // for (char * c = str; *c; c++) { - // char tmp[2] = { 0, 0 }; - // tmp[0] = *c; - // cairo_text_extents_t ex = { 0 }; - // cairo_text_extents(cr, tmp, &ex); - // - // printf("->\"%s\": w:%lf h:%lf xa:%lf ya:%lf xb:%lf yb:%lf\n", tmp, ex.width, ex.height, ex.x_advance, - // ex.y_advance, ex.x_bearing, ex.y_bearing); - // cairo_rectangle(cr, x + ex.x_bearing, y + ex.y_bearing, ex.width, ex.height); - // x += ex.x_advance; - // - // cairo_stroke(cr); - // } - // printf("--------\n\n"); - - // printf("->\"%s\"\n", str); - str = token.next(); - } + cairo_restore(cr); } GList * TextView::findText(Text * t, const char * search) { - GList * list = NULL; cairo_surface_t * surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); cairo_t * cr = cairo_create(surface); - initCairo(cr, t); - - cairo_text_extents_t extents = { 0 }; - cairo_font_extents_t fe = { 0 }; - StringTokenizer token(t->getText(), '\n'); - const char * str = token.next(); - double y = 3 + t->getY(); - double x = t->getX(); - int searchlen = strlen(search); + GList * list = NULL; - cairo_font_extents(cr, &fe); + PangoLayout * layout = initPango(cr, t); + String str = t->getText(); + pango_layout_set_text(layout, str.c_str(), str.size()); - while (str) { - String line = str; - int pos = -1; - y += fe.height - fe.descent; - do { - pos = line.indexOfCaseInsensitiv(search, pos + 1); - if (pos != -1) { - cairo_text_extents(cr, line.substring(0, pos).c_str(), &extents); - XojPopplerRectangle * rect = new XojPopplerRectangle(); - rect->x1 = x + extents.x_advance; - rect->y1 = y; + int pos = -1; - // because of case insensitive search, TEST and test may have different sizes (depending of the font) - cairo_text_extents(cr, line.substring(pos, searchlen).c_str(), &extents); + String text = t->getText(); - rect->x1 += extents.x_bearing; - rect->y1 += extents.y_bearing; - rect->x2 = rect->x1 + extents.x_advance; - rect->y2 = rect->y1 + extents.height; + String srch = search; - list = g_list_append(list, rect); - } + do { + pos = text.indexOfCaseInsensitiv(srch, pos + 1); + if (pos != -1) { + XojPopplerRectangle * mark = new XojPopplerRectangle(); + PangoRectangle rect = { 0 }; + pango_layout_index_to_pos(layout, pos, &rect); + mark->x1 = ((double)rect.x) / PANGO_SCALE + t->getX(); + mark->y1 = ((double)rect.y) / PANGO_SCALE + t->getY(); - } while (pos != -1); + pango_layout_index_to_pos(layout, pos + srch.length(), &rect); + mark->x2 = ((double)rect.x + rect.width) / PANGO_SCALE + t->getX(); + mark->y2 = ((double)rect.y + rect.height) / PANGO_SCALE + t->getY(); - str = token.next(); - y += fe.height * 0.25; - } + list = g_list_append(list, mark); + } + } while (pos != -1); + g_object_unref(layout); cairo_surface_destroy(surface); cairo_destroy(cr); @@ -156,33 +82,17 @@ GList * TextView::findText(Text * t, const char * search) { void TextView::calcSize(Text * t, double & width, double & height) { cairo_surface_t * surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); - cairo_t *cr; - - cr = cairo_create(surface); - - initCairo(cr, t); - - cairo_text_extents_t extents = { 0 }; - cairo_font_extents_t fe = { 0 }; - cairo_font_extents(cr, &fe); - - height = 3; - width = 0; - StringTokenizer token(t->getText(), '\n'); - const char * str = token.next(); - - while (str) { - cairo_text_extents(cr, str, &extents); - - height += fe.height * 1.25 - fe.descent; - - width = MAX(width, extents.x_advance); + cairo_t * cr = cairo_create(surface); - str = token.next(); - if (!str) { - height -= fe.height * 0.25; - } - } + PangoLayout * layout = initPango(cr, t); + String str = t->getText(); + pango_layout_set_text(layout, str.c_str(), str.size()); + int w = 0; + int h = 0; + pango_layout_get_size(layout, &w, &h); + width = ((double) w) / PANGO_SCALE; + height = ((double) h) / PANGO_SCALE; + g_object_unref(layout); cairo_surface_destroy(surface); cairo_destroy(cr); diff --git a/src/view/TextView.h b/src/view/TextView.h index 02c95688..011b425f 100644 --- a/src/view/TextView.h +++ b/src/view/TextView.h @@ -25,8 +25,7 @@ public: static void calcSize(Text * t, double & width, double & height); static void drawText(cairo_t *cr, Text * t); static GList * findText(Text * t, const char * text); - static void initCairo(cairo_t *cr, Text * t); - static const int TAB_INDEX = 30; + static PangoLayout * initPango(cairo_t *cr, Text * t); }; #endif /* __TEXTVIEW_H__ */