diff options
author | Sebastian Geerken <devnull@localhost> | 2012-09-14 11:34:19 +0200 |
---|---|---|
committer | Sebastian Geerken <devnull@localhost> | 2012-09-14 11:34:19 +0200 |
commit | e4367b16dc131f34936bbb8fd09557b5aa5acbd7 (patch) | |
tree | 487a35941bf20bbc95a3d0b1dee420b00771f5b6 /dw | |
parent | abd446c2eebe1f96764b6d95f1c6c61ae9bc40b2 (diff) | |
parent | 94e451ffa5ece79a3b071ee553650bf8bd869a46 (diff) |
Merge of <http://hg.dillo.org/dillo>.
Diffstat (limited to 'dw')
-rw-r--r-- | dw/Makefile.am | 7 | ||||
-rw-r--r-- | dw/fltkcomplexbutton.cc | 8 | ||||
-rw-r--r-- | dw/fltkcomplexbutton.hh | 5 | ||||
-rw-r--r-- | dw/fltkplatform.cc | 48 | ||||
-rw-r--r-- | dw/fltkplatform.hh | 2 | ||||
-rw-r--r-- | dw/fltkui.cc | 43 | ||||
-rw-r--r-- | dw/fltkviewbase.cc | 26 | ||||
-rw-r--r-- | dw/hyphenator.cc | 335 | ||||
-rw-r--r-- | dw/hyphenator.hh | 48 | ||||
-rw-r--r-- | dw/layout.hh | 15 | ||||
-rw-r--r-- | dw/platform.hh | 10 | ||||
-rw-r--r-- | dw/style.cc | 16 | ||||
-rw-r--r-- | dw/style.hh | 13 | ||||
-rw-r--r-- | dw/table.cc | 15 | ||||
-rw-r--r-- | dw/tablecell.cc | 4 | ||||
-rw-r--r-- | dw/tablecell.hh | 2 | ||||
-rw-r--r-- | dw/textblock.cc | 1472 | ||||
-rw-r--r-- | dw/textblock.hh | 244 | ||||
-rw-r--r-- | dw/textblock_linebreaking.cc | 962 | ||||
-rw-r--r-- | dw/types.hh | 1 | ||||
-rw-r--r-- | dw/widget.cc | 1 |
21 files changed, 2416 insertions, 861 deletions
diff --git a/dw/Makefile.am b/dw/Makefile.am index 3014b35d..07948b5f 100644 --- a/dw/Makefile.am +++ b/dw/Makefile.am @@ -1,5 +1,7 @@ AM_CPPFLAGS = \ - -I$(top_srcdir) + -I$(top_srcdir) \ + -DDILLO_LIBDIR='"$(pkglibdir)/"' + noinst_LIBRARIES = \ libDw-core.a \ @@ -57,6 +59,8 @@ libDw_widgets_a_SOURCES = \ alignedtextblock.hh \ bullet.cc \ bullet.hh \ + hyphenator.cc \ + hyphenator.hh \ image.cc \ image.hh \ listitem.cc \ @@ -68,6 +72,7 @@ libDw_widgets_a_SOURCES = \ tablecell.cc \ tablecell.hh \ textblock.cc \ + textblock_linebreaking.cc \ textblock.hh EXTRA_DIST = preview.xbm diff --git a/dw/fltkcomplexbutton.cc b/dw/fltkcomplexbutton.cc index 89295190..0f124cf9 100644 --- a/dw/fltkcomplexbutton.cc +++ b/dw/fltkcomplexbutton.cc @@ -1,5 +1,6 @@ -// fltkcomplexbutton.cc contains code from FLTK 1.3's src/Fl_Button.cxx -// that is Copyright 1998-2010 by Bill Spitzak and others. +// fltkcomplexbutton.cc is derived from src/Fl_Button.cxx from FLTK's 1.3 +// branch at http://fltk.org in early 2011. +// src/Fl_Button.cxx is Copyright 1998-2010 by Bill Spitzak and others. /* * This program is free software; you can redistribute it and/or modify @@ -108,7 +109,8 @@ int ComplexButton::handle(int event) { return 1; } else return 0; case FL_KEYBOARD : - if (Fl::focus() == this && Fl::event_key() == ' ' && + if (Fl::focus() == this && + (Fl::event_key() == ' ' || Fl::event_key() == FL_Enter) && !(Fl::event_state() & (FL_SHIFT | FL_CTRL | FL_ALT | FL_META))) { set_changed(); Fl_Widget_Tracker wp(this); diff --git a/dw/fltkcomplexbutton.hh b/dw/fltkcomplexbutton.hh index 3a14cfb2..43be6b57 100644 --- a/dw/fltkcomplexbutton.hh +++ b/dw/fltkcomplexbutton.hh @@ -1,6 +1,7 @@ -// fltkcomplexbutton.hh contains code from FLTK 1.3's FL/Fl_Button.H -// that is Copyright 1998-2010 by Bill Spitzak and others. +// fltkcomplexbutton.hh is derived from FL/Fl_Button.H from FLTK's 1.3 branch +// at http://fltk.org in early 2011. +// FL/Fl_Button.H is Copyright 1998-2010 by Bill Spitzak and others. /* * This program is free software; you can redistribute it and/or modify diff --git a/dw/fltkplatform.cc b/dw/fltkplatform.cc index 68819c91..099c449c 100644 --- a/dw/fltkplatform.cc +++ b/dw/fltkplatform.cc @@ -141,7 +141,7 @@ FltkFont::~FltkFont () static void strstrip(char *big, const char *little) { if (strlen(big) >= strlen(little) && - strcasecmp(big + strlen(big) - strlen(little), little) == 0) + misc::AsciiStrcasecmp(big + strlen(big) - strlen(little), little) == 0) *(big + strlen(big) - strlen(little)) = '\0'; } @@ -525,14 +525,18 @@ int FltkPlatform::textWidth (core::style::Font *font, const char *text, if ((cu = fl_toupper(c)) == c) { /* already uppercase, just draw the character */ fl_font(ff->font, ff->size); - width += font->letterSpacing; - width += (int)fl_width(text + curr, next - curr); + if (fl_nonspacing(cu) == 0) { + width += font->letterSpacing; + width += (int)fl_width(text + curr, next - curr); + } } else { /* make utf8 string for converted char */ nb = fl_utf8encode(cu, chbuf); fl_font(ff->font, sc_fontsize); - width += font->letterSpacing; - width += (int)fl_width(chbuf, nb); + if (fl_nonspacing(cu) == 0) { + width += font->letterSpacing; + width += (int)fl_width(chbuf, nb); + } } } } else { @@ -544,7 +548,9 @@ int FltkPlatform::textWidth (core::style::Font *font, const char *text, while (next < len) { next = nextGlyph(text, curr); - width += font->letterSpacing; + c = fl_utf8decode(text + curr, text + next, &nb); + if (fl_nonspacing(c) == 0) + width += font->letterSpacing; curr = next; } } @@ -553,6 +559,36 @@ int FltkPlatform::textWidth (core::style::Font *font, const char *text, return width; } +char *FltkPlatform::textToUpper (const char *text, int len) +{ + char *newstr = NULL; + + if (len > 0) { + int newlen; + + newstr = (char*) malloc(3 * len + 1); + newlen = fl_utf_toupper((const unsigned char*)text, len, newstr); + assert(newlen <= 3 * len); + newstr[newlen] = '\0'; + } + return newstr; +} + +char *FltkPlatform::textToLower (const char *text, int len) +{ + char *newstr = NULL; + + if (len > 0) { + int newlen; + + newstr = (char*) malloc(3 * len + 1); + newlen = fl_utf_tolower((const unsigned char*)text, len, newstr); + assert(newlen <= 3 * len); + newstr[newlen] = '\0'; + } + return newstr; +} + int FltkPlatform::nextGlyph (const char *text, int idx) { return fl_utf8fwd (&text[idx + 1], text, &text[strlen (text)]) - text; diff --git a/dw/fltkplatform.hh b/dw/fltkplatform.hh index db4bc794..7b4272eb 100644 --- a/dw/fltkplatform.hh +++ b/dw/fltkplatform.hh @@ -153,6 +153,8 @@ public: void detachView (core::View *view); int textWidth (core::style::Font *font, const char *text, int len); + char *textToUpper (const char *text, int len); + char *textToLower (const char *text, int len); int nextGlyph (const char *text, int idx); int prevGlyph (const char *text, int idx); float dpiX (); diff --git a/dw/fltkui.cc b/dw/fltkui.cc index c0c8ff42..f9e6f3f6 100644 --- a/dw/fltkui.cc +++ b/dw/fltkui.cc @@ -266,6 +266,24 @@ template <class I> void FltkSpecificResource<I>::setEnabled (bool enabled) // ---------------------------------------------------------------------- +class EnterButton : public Fl_Button { +public: + EnterButton (int x,int y,int w,int h, const char* label = 0) : + Fl_Button (x,y,w,h,label) {}; + int handle(int e); +}; + +int EnterButton::handle(int e) +{ + if (e == FL_KEYBOARD && Fl::focus() == this && Fl::event_key() == FL_Enter){ + set_changed(); + simulate_key_action(); + do_callback(); + return 1; + } + return Fl_Button::handle(e); +} + FltkLabelButtonResource::FltkLabelButtonResource (FltkPlatform *platform, const char *label): FltkSpecificResource <dw::core::ui::LabelButtonResource> (platform) @@ -283,8 +301,8 @@ Fl_Widget *FltkLabelButtonResource::createNewWidget (core::Allocation *allocation) { Fl_Button *button = - new Fl_Button (allocation->x, allocation->y, allocation->width, - allocation->ascent + allocation->descent, label); + new EnterButton (allocation->x, allocation->y, allocation->width, + allocation->ascent + allocation->descent, label); button->callback (widgetCallback, this); button->when (FL_WHEN_RELEASE); return button; @@ -385,11 +403,22 @@ void FltkComplexButtonResource::widgetCallback (Fl_Widget *widget, FltkComplexButtonResource *res = (FltkComplexButtonResource*)data; if (!Fl::event_button3()) { - res->click_x = Fl::event_x(); - res->click_y = Fl::event_y(); - dw::core::EventButton event; - setButtonEvent(&event); - res->emitClicked(&event); + int w = widget->w(), h = widget->h(); + + res->click_x = Fl::event_x() - widget->x(); + res->click_y = Fl::event_y() - widget->y(); + if (res->style) { + res->click_x -= res->style->boxOffsetX(); + res->click_y -= res->style->boxOffsetY(); + w -= res->style->boxDiffWidth(); + h -= res->style->boxDiffHeight(); + } + if (res->click_x >= 0 && res->click_y >= 0 && + res->click_x < w && res->click_y < h) { + dw::core::EventButton event; + setButtonEvent(&event); + res->emitClicked(&event); + } } } diff --git a/dw/fltkviewbase.cc b/dw/fltkviewbase.cc index bf3aba22..240937e2 100644 --- a/dw/fltkviewbase.cc +++ b/dw/fltkviewbase.cc @@ -531,9 +531,9 @@ FltkWidgetView::~FltkWidgetView () } void FltkWidgetView::drawText (core::style::Font *font, - core::style::Color *color, - core::style::Color::Shading shading, - int X, int Y, const char *text, int len) + core::style::Color *color, + core::style::Color::Shading shading, + int X, int Y, const char *text, int len) { FltkFont *ff = (FltkFont*)font; fl_font(ff->font, ff->size); @@ -548,7 +548,7 @@ void FltkWidgetView::drawText (core::style::Font *font, viewY = translateCanvasYToViewY (Y); int curr = 0, next = 0, nb; char chbuf[4]; - int c, cu; + int c, cu, width; if (font->fontVariant == core::style::FONT_VARIANT_SMALL_CAPS) { int sc_fontsize = lout::misc::roundInt(ff->size * 0.78); @@ -558,24 +558,30 @@ void FltkWidgetView::drawText (core::style::Font *font, if ((cu = fl_toupper(c)) == c) { /* already uppercase, just draw the character */ fl_font(ff->font, ff->size); + width = (int)fl_width(text + curr, next - curr); + if (curr && width) + viewX += font->letterSpacing; fl_draw(text + curr, next - curr, viewX, viewY); - viewX += font->letterSpacing; - viewX += (int)fl_width(text + curr, next - curr); + viewX += width; } else { /* make utf8 string for converted char */ nb = fl_utf8encode(cu, chbuf); fl_font(ff->font, sc_fontsize); + width = (int)fl_width(chbuf, nb); + if (curr && width) + viewX += font->letterSpacing; fl_draw(chbuf, nb, viewX, viewY); - viewX += font->letterSpacing; - viewX += (int)fl_width(chbuf, nb); + viewX += width; } } } else { while (next < len) { next = theLayout->nextGlyph(text, curr); + width = (int)fl_width(text + curr, next - curr); + if (curr && width) + viewX += font->letterSpacing; fl_draw(text + curr, next - curr, viewX, viewY); - viewX += font->letterSpacing + - (int)fl_width(text + curr,next - curr); + viewX += width; curr = next; } } diff --git a/dw/hyphenator.cc b/dw/hyphenator.cc new file mode 100644 index 00000000..0fbdb699 --- /dev/null +++ b/dw/hyphenator.cc @@ -0,0 +1,335 @@ +#include "hyphenator.hh" + +#include "../lout/misc.hh" +#include "../lout/unicode.hh" +#include <stdio.h> +#include <string.h> +#include <limits.h> + +#define LEN 1000 + +/* + * This is a direct translation of the Python implementation by Ned + * Batchelder. + */ + +using namespace lout::object; +using namespace lout::container::typed; +using namespace lout::misc; + +namespace dw { + +HashTable <TypedPair <TypedPointer <core::Platform>, ConstString>, + Hyphenator> *Hyphenator::hyphenators = + new HashTable <TypedPair <TypedPointer <core::Platform>, ConstString>, + Hyphenator> (true, true); + +Hyphenator::Hyphenator (core::Platform *platform, + const char *patFile, const char *excFile) +{ + this->platform = platform; + tree = NULL; // As long we are not sure whether a pattern file can be read. + + FILE *patF = fopen (patFile, "r"); + if (patF) { + tree = new HashTable <Integer, Collection <Integer> > (true, true); + while (!feof (patF)) { + char buf[LEN + 1]; + char *s = fgets (buf, LEN, patF); + if (s) { + // TODO Better exit with an error, when the line is too long. + int l = strlen (s); + if (s[l - 1] == '\n') + s[l - 1] = 0; + insertPattern (s); + } + } + fclose (patF); + } + + exceptions = NULL; // Again, only instanciated when needed. + + FILE *excF = fopen (excFile, "r"); + if (excF) { + exceptions = new HashTable <ConstString, Vector <Integer> > (true, true); + while (!feof (excF)) { + char buf[LEN + 1]; + char *s = fgets (buf, LEN, excF); + if (s) { + // TODO Better exit with an error, when the line is too long. + int l = strlen (s); + if (s[l - 1] == '\n') + s[l - 1] = 0; + insertException (s); + } + } + fclose (excF); + } +} + +Hyphenator::~Hyphenator () +{ + if (tree) + delete tree; + if (exceptions) + delete exceptions; +} + +Hyphenator *Hyphenator::getHyphenator (core::Platform *platform, + const char *lang) +{ + // TODO Not very efficient. Other key than TypedPair? + // (Keeping the parts of the pair on the stack does not help, since + // ~TypedPair deletes them, so they have to be kept on the heap.) + TypedPair <TypedPointer <core::Platform>, ConstString> *pair = + new TypedPair <TypedPointer <core::Platform>, + ConstString> (new TypedPointer <core::Platform> (platform), + new String (lang)); + + Hyphenator *hyphenator = hyphenators->get (pair); + if (hyphenator) + delete pair; + else { + char patFile [PATH_MAX]; + snprintf (patFile, sizeof (patFile), "%s/hyphenation/%s.pat", + DILLO_LIBDIR, lang); + char excFile [PATH_MAX]; + snprintf (excFile, sizeof(excFile), "%s/hyphenation/%s.exc", + DILLO_LIBDIR, lang); + + //printf ("Loading hyphenation patterns for language '%s' from '%s' and " + // "exceptions from '%s' ...\n", lang, patFile, excFile); + + hyphenator = new Hyphenator (platform, patFile, excFile); + hyphenators->put (pair, hyphenator); + } + + //lout::misc::StringBuffer sb; + //hyphenators->intoStringBuffer (&sb); + //printf ("%s\n", sb.getChars ()); + + return hyphenator; +} + +void Hyphenator::insertPattern (char *s) +{ + // Convert the a pattern like 'a1bc3d4' into a string of chars 'abcd' + // and a list of points [ 0, 1, 0, 3, 4 ]. + int l = strlen (s); + char chars [l + 1]; + Vector <Integer> *points = new Vector <Integer> (1, true); + + // TODO numbers consisting of multiple digits? + // TODO Encoding: This implementation works exactly like the Python + // implementation, based on UTF-8. Does this always work? + int numChars = 0; + for (int i = 0; s[i]; i++) + if (s[i] >= '0' && s[i] <= '9') + points->put (new Integer (s[i] - '0'), numChars); + else + chars[numChars++] = s[i]; + chars[numChars] = 0; + + for (int i = 0; i < numChars + 1; i++) { + Integer *val = points->get (i); + if (val == NULL) + points->put (new Integer (0), i); + } + + // Insert the pattern into the tree. Each character finds a dict + // another level down in the tree, and leaf nodes have the list of + // points. + + HashTable <Integer, Collection <Integer> > *t = tree; + for (int i = 0; chars[i]; i++) { + Integer c (chars[i]); + if (!t->contains(&c)) + t->put (new Integer (chars[i]), + new HashTable <Integer, Collection <Integer> > (true, true)); + t = (HashTable <Integer, Collection <Integer> >*) t->get (&c); + } + + t->put (new Integer (0), points); +} + +void Hyphenator::insertException (char *s) +{ + Vector<Integer> *breaks = new Vector<Integer> (1, true); + + int len = strlen (s); + for (int i = 0; i < len - 1; i++) + if((unsigned char)s[i] == 0xc2 && (unsigned char)s[i + 1] == 0xad) + breaks->put (new Integer (i - 2 * breaks->size())); + + char noHyphens[len - 2 * breaks->size() + 1]; + int j = 0; + for (int i = 0; i < len; ) { + if(i < len - 1 && + (unsigned char)s[i] == 0xc2 && (unsigned char)s[i + 1] == 0xad) + i += 2; + else + noHyphens[j++] = s[i++]; + } + noHyphens[j] = 0; + + exceptions->put (new String (noHyphens), breaks); +} + +/** + * Simple test to avoid much costs. Passing it does not mean that the word + * can be hyphenated. + */ +bool Hyphenator::isHyphenationCandidate (const char *word) +{ + // Short words aren't hyphenated. + return (strlen (word) > 4); // TODO UTF-8? +} + +/** + * Test whether the character on which "s" points (UTF-8) is an actual + * part of the word. Other characters at the beginning and end are + * ignored. + * + * TODO Currently only suitable for English and German. + * TODO Only lowercase. (Uppercase not needed.) + */ +bool Hyphenator::isCharPartOfActualWord (char *s) +{ +#if 0 + // Return true when "s" points to a letter. + return (s[0] >= 'a' && s[0] <= 'z') || + // UTF-8: starts with 0xc3 + ((unsigned char)s[0] == 0xc3 && + ((unsigned char)s[1] == 0xa4 /* ä */ || + (unsigned char)s[1] == 0xb6 /* ö */ || + (unsigned char)s[1] == 0xbc /* ü */ || + (unsigned char)s[1] == 0x9f /* ß */ )); +#endif + + return lout::unicode::isAlpha (lout::unicode::decodeUtf8 (s)); +} + +/** + * Given a word, returns a list of the possible hyphenation points. + */ +int *Hyphenator::hyphenateWord(const char *word, int *numBreaks) +{ + if ((tree == NULL && exceptions ==NULL) || !isHyphenationCandidate (word)) { + *numBreaks = 0; + return NULL; + } + + char *wordLc = platform->textToLower (word, strlen (word)); + + // Determine "actual" word. See isCharPartOfActualWord for exact definition. + + // Only this actual word is used, and "startActualWord" is added to the + // break positions, so that these refer to the total word. + int startActualWord = 0; + while (wordLc[startActualWord] && + !isCharPartOfActualWord (wordLc + startActualWord)) + startActualWord = platform->nextGlyph (wordLc, startActualWord); + + if (wordLc[startActualWord] == 0) { + // No letters etc in word: do not hyphenate at all. + delete wordLc; + *numBreaks = 0; + return NULL; + } + + int endActualWord = startActualWord, i = endActualWord; + while (wordLc[i]) { + if (isCharPartOfActualWord (wordLc + i)) + endActualWord = i; + i = platform->nextGlyph (wordLc, i); + } + + endActualWord = platform->nextGlyph (wordLc, endActualWord); + wordLc[endActualWord] = 0; + + // If the word is an exception, get the stored points. + Vector <Integer> *exceptionalBreaks; + ConstString key (wordLc + startActualWord); + if (exceptions != NULL && (exceptionalBreaks = exceptions->get (&key))) { + int *result = new int[exceptionalBreaks->size()]; + for (int i = 0; i < exceptionalBreaks->size(); i++) + result[i] = exceptionalBreaks->get(i)->getValue() + startActualWord; + delete wordLc; + *numBreaks = exceptionalBreaks->size(); + return result; + } + + // tree == NULL means that there is no pattern file. + if (tree == NULL) { + delete wordLc; + *numBreaks = 0; + return NULL; + } + + char work[strlen (word) + 3]; + strcpy (work, "."); + strcat (work, wordLc + startActualWord); + strcat (work, "."); + + int l = strlen (work); + SimpleVector <int> points (l + 1); + points.setSize (l + 1, 0); + + Integer null (0); + + for (int i = 0; i < l; i++) { + HashTable <Integer, Collection <Integer> > *t = tree; + for (int j = i; j < l; j++) { + Integer c (work[j]); + if (t->contains (&c)) { + t = (HashTable <Integer, Collection <Integer> >*) t->get (&c); + if (t->contains (&null)) { + Vector <Integer> *p = (Vector <Integer>*) t->get (&null); + + for (int k = 0; k < p->size (); k++) + points.set(i + k, lout::misc::max (points.get (i + k), + p->get(k)->getValue())); + } + } else + break; + } + } + + // No hyphens in the first two chars or the last two. + // Characters are not bytes, so UTF-8 characters must be counted. + int numBytes1 = platform->nextGlyph (wordLc + startActualWord, 0); + int numBytes2 = platform->nextGlyph (wordLc + startActualWord, numBytes1); + for (int i = 0; i < numBytes2; i++) + points.set (i + 1, 0); + + // TODO: Characters, not bytes (as above). + points.set (points.size() - 2, 0); + points.set (points.size() - 3, 0); + + // Examine the points to build the pieces list. + SimpleVector <int> breakPos (1); + + int n = lout::misc::min ((int)strlen (word), points.size () - 2); + for (int i = 0; i < n; i++) { + if (points.get(i + 2) % 2) { + breakPos.increase (); + breakPos.set (breakPos.size() - 1, i + 1 + startActualWord); + } + } + + delete wordLc; + + *numBreaks = breakPos.size (); + if (*numBreaks == 0) + return NULL; + else { + // Could save some cycles by directly returning the array in the + // SimpleVector. + int *breakPosArray = new int[*numBreaks]; + for (int i = 0; i < *numBreaks; i++) + breakPosArray[i] = breakPos.get(i); + return breakPosArray; + } +} + +} // namespace dw diff --git a/dw/hyphenator.hh b/dw/hyphenator.hh new file mode 100644 index 00000000..bf123e5a --- /dev/null +++ b/dw/hyphenator.hh @@ -0,0 +1,48 @@ +#ifndef __DW_HYPHENATOR_HH__ +#define __DW_HYPHENATOR_HH__ + +#include "../lout/object.hh" +#include "../lout/container.hh" +#include "../dw/core.hh" + +namespace dw { + +class Hyphenator: public lout::object::Object +{ +private: + static lout::container::typed::HashTable + <lout::object::TypedPair <lout::object::TypedPointer <core::Platform>, + lout::object::ConstString>, + Hyphenator> *hyphenators; + + /* + * Actually, only one method in Platform is needed: + * textToLower(). And, IMO, this method is actually not platform + * independant, but based on UTF-8. Clarify? Change? + */ + core::Platform *platform; + lout::container::typed::HashTable <lout::object::Integer, + lout::container::typed::Collection + <lout::object::Integer> > *tree; + lout::container::typed::HashTable <lout::object::ConstString, + lout::container::typed::Vector + <lout::object::Integer> > *exceptions; + void insertPattern (char *s); + void insertException (char *s); + + bool isCharPartOfActualWord (char *s); + +public: + Hyphenator (core::Platform *platform, + const char *patFile, const char *excFile); + ~Hyphenator(); + + static Hyphenator *getHyphenator (core::Platform *platform, + const char *language); + static bool isHyphenationCandidate (const char *word); + int *hyphenateWord(const char *word, int *numBreaks); +}; + +} // namespace dw + +#endif // __DW_HYPHENATOR_HH__ diff --git a/dw/layout.hh b/dw/layout.hh index d08eb363..4b80456b 100644 --- a/dw/layout.hh +++ b/dw/layout.hh @@ -290,6 +290,11 @@ public: void scrollPosChanged (View *view, int x, int y); void viewportSizeChanged (View *view, int width, int height); + inline Platform *getPlatform () + { + return platform; + } + /* delegated */ inline int textWidth (style::Font *font, const char *text, int len) @@ -297,6 +302,16 @@ public: return platform->textWidth (font, text, len); } + inline char *textToUpper (const char *text, int len) + { + return platform->textToUpper (text, len); + } + + inline char *textToLower (const char *text, int len) + { + return platform->textToLower (text, len); + } + inline int nextGlyph (const char *text, int idx) { return platform->nextGlyph (text, idx); diff --git a/dw/platform.hh b/dw/platform.hh index b79b5346..cb714583 100644 --- a/dw/platform.hh +++ b/dw/platform.hh @@ -58,6 +58,16 @@ public: virtual int textWidth (style::Font *font, const char *text, int len) = 0; /** + * \brief Return the string resulting from transforming text to uppercase. + */ + virtual char *textToUpper (const char *text, int len) = 0; + + /** + * \brief Return the string resulting from transforming text to lowercase. + */ + virtual char *textToLower (const char *text, int len) = 0; + + /** * \brief Return the index of the next glyph in string text. */ virtual int nextGlyph (const char *text, int idx) = 0; diff --git a/dw/style.cc b/dw/style.cc index 4276862a..b0868c1f 100644 --- a/dw/style.cc +++ b/dw/style.cc @@ -35,11 +35,13 @@ namespace style { void StyleAttrs::initValues () { x_link = -1; + x_lang[0] = x_lang[1] = 0; x_img = -1; x_tooltip = NULL; textDecoration = TEXT_DECORATION_NONE; textAlign = TEXT_ALIGN_LEFT; textAlignChar = '.'; + textTransform = TEXT_TRANSFORM_NONE; listStylePosition = LIST_STYLE_POSITION_OUTSIDE; listStyleType = LIST_STYLE_TYPE_DISC; valign = VALIGN_BASELINE; @@ -119,6 +121,7 @@ bool StyleAttrs::equals (object::Object *other) { textAlign == otherAttrs->textAlign && valign == otherAttrs->valign && textAlignChar == otherAttrs->textAlignChar && + textTransform == otherAttrs->textTransform && vloat == otherAttrs->vloat && clear == otherAttrs->clear && hBorderSpacing == otherAttrs->hBorderSpacing && @@ -146,6 +149,8 @@ bool StyleAttrs::equals (object::Object *other) { listStyleType == otherAttrs->listStyleType && cursor == otherAttrs->cursor && x_link == otherAttrs->x_link && + x_lang[0] == otherAttrs->x_lang[0] && + x_lang[1] == otherAttrs->x_lang[1] && x_img == otherAttrs->x_img && x_tooltip == otherAttrs->x_tooltip); } @@ -158,6 +163,7 @@ int StyleAttrs::hashValue () { textAlign + valign + textAlignChar + + textTransform + vloat + clear + hBorderSpacing + @@ -185,6 +191,7 @@ int StyleAttrs::hashValue () { listStyleType + cursor + x_link + + x_lang[0] + x_lang[1] + x_img + (intptr_t) x_tooltip; } @@ -250,6 +257,7 @@ void Style::copyAttrs (StyleAttrs *attrs) textAlign = attrs->textAlign; valign = attrs->valign; textAlignChar = attrs->textAlignChar; + textTransform = attrs->textTransform; vloat = attrs->vloat; clear = attrs->clear; hBorderSpacing = attrs->hBorderSpacing; @@ -271,6 +279,8 @@ void Style::copyAttrs (StyleAttrs *attrs) listStyleType = attrs->listStyleType; cursor = attrs->cursor; x_link = attrs->x_link; + x_lang[0] = attrs->x_lang[0]; + x_lang[1] = attrs->x_lang[1]; x_img = attrs->x_img; x_tooltip = attrs->x_tooltip; } @@ -893,10 +903,10 @@ static const char *const roman_I2[] = { "","C","CC","CCC","CD","D","DC","DCC","DCCC","CM" }, *const roman_I3[] = { "","M","MM","MMM","MMMM" }; -static void strtolower (char *s) +static void strAsciiTolower (char *s) { for ( ; *s; s++) - *s = tolower (*s); + *s = misc::AsciiTolower (*s); } /** @@ -951,7 +961,7 @@ void numtostr (int num, char *buf, int buflen, ListStyleType listStyleType) buf[buflen - 1] = '\0'; if (low) - strtolower(buf); + strAsciiTolower(buf); } diff --git a/dw/style.hh b/dw/style.hh index 370d8d1b..6b492793 100644 --- a/dw/style.hh +++ b/dw/style.hh @@ -247,12 +247,20 @@ enum VAlignType { VALIGN_TEXT_BOTTOM, }; +enum TextTransform { + TEXT_TRANSFORM_NONE, + TEXT_TRANSFORM_CAPITALIZE, + TEXT_TRANSFORM_UPPERCASE, + TEXT_TRANSFORM_LOWERCASE, +}; + /** * \todo Incomplete. Has to be completed for a CSS implementation. */ enum DisplayType { DISPLAY_BLOCK, DISPLAY_INLINE, + DISPLAY_INLINE_BLOCK, DISPLAY_LIST_ITEM, DISPLAY_NONE, DISPLAY_TABLE, @@ -453,6 +461,7 @@ public: TextAlignType textAlign; VAlignType valign; char textAlignChar; /* In future, strings will be supported. */ + TextTransform textTransform; FloatType vloat; /* "float" is a keyword. */ ClearType clear; @@ -474,6 +483,10 @@ public: int x_link; int x_img; Tooltip *x_tooltip; + char x_lang[2]; /* Either x_lang[0] == x_lang[1] == 0 (no language + set), or x_lang contains the RFC 1766 country + code in lower case letters. (Only two letters + allowed, currently.) */ void initValues (); void resetValues (); diff --git a/dw/table.cc b/dw/table.cc index 8a46ce7c..c21b7a09 100644 --- a/dw/table.cc +++ b/dw/table.cc @@ -270,18 +270,19 @@ core::Iterator *Table::iterator (core::Content::Type mask, bool atEnd) void Table::addCell (Widget *widget, int colspan, int rowspan) { + const int maxspan = 100; Child *child; int colspanEff; - // We limit the values for colspan and rowspan to 50, to avoid + // We limit the values for colspan and rowspan to avoid // attacks by malicious web pages. - if (colspan > 50 || colspan < 0) { - MSG_WARN("colspan = %d is set to 50.\n", colspan); - colspan = 50; + if (colspan > maxspan || colspan < 0) { + MSG_WARN("colspan = %d is set to %d.\n", colspan, maxspan); + colspan = maxspan; } - if (rowspan > 50 || rowspan <= 0) { - MSG_WARN("rowspan = %d is set to 50.\n", rowspan); - rowspan = 50; + if (rowspan > maxspan || rowspan <= 0) { + MSG_WARN("rowspan = %d is set to %d.\n", rowspan, maxspan); + rowspan = maxspan; } if (numRows == 0) { diff --git a/dw/tablecell.cc b/dw/tablecell.cc index 5b93fe86..90dc310d 100644 --- a/dw/tablecell.cc +++ b/dw/tablecell.cc @@ -42,12 +42,12 @@ TableCell::~TableCell() { } -void TableCell::wordWrap(int wordIndex) +void TableCell::wordWrap(int wordIndex, bool wrapAll) { Textblock::Word *word; const char *p; - Textblock::wordWrap (wordIndex); + Textblock::wordWrap (wordIndex, wrapAll); if (charWordIndex == -1) { word = words->getRef (wordIndex); diff --git a/dw/tablecell.hh b/dw/tablecell.hh index 318d1f4e..4bb8633c 100644 --- a/dw/tablecell.hh +++ b/dw/tablecell.hh @@ -12,7 +12,7 @@ private: int charWordIndex, charWordPos; protected: - void wordWrap(int wordIndex); + void wordWrap (int wordIndex, bool wrapAll); int getValue (); void setMaxValue (int maxValue, int value); diff --git a/dw/textblock.cc b/dw/textblock.cc index 6ded2413..6aa75a13 100644 --- a/dw/textblock.cc +++ b/dw/textblock.cc @@ -17,20 +17,21 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + #include "textblock.hh" #include "table.hh" // Yes, this is ugly. -- SG #include "../lout/msg.h" #include "../lout/misc.hh" #include <stdio.h> -#include <math.h> // remove again +#include <math.h> // remove again? #include <limits.h> /* * Local variables */ - /* The tooltip under mouse pointer in current textblock. No ref. hold. - * (having one per view looks not worth the extra clutter). */ +/* The tooltip under mouse pointer in current textblock. No ref. hold. + * (having one per view looks not worth the extra clutter). */ static dw::core::style::Tooltip *hoverTooltip = NULL; @@ -51,7 +52,6 @@ Textblock::Textblock (bool limitTextWidth) hasListitemValue = false; innerPadding = 0; line1Offset = 0; - line1OffsetEff = 0; ignoreLine1OffsetSometimes = false; mustQueueResize = false; redrawY = 0; @@ -67,21 +67,19 @@ Textblock::Textblock (bool limitTextWidth) * TODO: Some tests would be useful. */ lines = new misc::SimpleVector <Line> (1); - words = new misc::SimpleVector <Word> (1); - leftFloatSide = rightFloatSide = NULL; + nonTemporaryLines = 0; + words = new misc::NotSoSimpleVector <Word> (1); anchors = new misc::SimpleVector <Anchor> (1); + leftFloatSide = rightFloatSide = NULL; - //DBG_OBJ_SET_NUM(page, "num_lines", num_lines); + //DBG_OBJ_SET_NUM(this, "num_lines", num_lines); - lastLineWidth = 0; - lastLineParMin = 0; - lastLineParMax = 0; wrapRef = -1; - //DBG_OBJ_SET_NUM(page, "last_line_width", last_line_width); - //DBG_OBJ_SET_NUM(page, "last_line_par_min", last_line_par_min); - //DBG_OBJ_SET_NUM(page, "last_line_par_max", last_line_par_max); - //DBG_OBJ_SET_NUM(page, "wrap_ref", wrap_ref); + //DBG_OBJ_SET_NUM(this, "last_line_width", last_line_width); + //DBG_OBJ_SET_NUM(this, "last_line_par_min", last_line_par_min); + //DBG_OBJ_SET_NUM(this, "last_line_par_max", last_line_par_max); + //DBG_OBJ_SET_NUM(this, "wrap_ref", wrap_ref); hoverLink = -1; @@ -125,7 +123,7 @@ Textblock::~Textblock () delete lines; delete words; delete anchors; - + if(leftFloatSide) delete leftFloatSide; if(rightFloatSide) @@ -135,7 +133,7 @@ Textblock::~Textblock () parent class destructor. (???) */ words = NULL; - //DBG_OBJ_SET_NUM(page, "num_lines", page->num_lines); + //DBG_OBJ_SET_NUM(this, "num_lines", lines->size ()); } /** @@ -145,12 +143,27 @@ Textblock::~Textblock () */ void Textblock::sizeRequestImpl (core::Requisition *requisition) { + PRINTF ("[%p] SIZE_REQUEST: ...\n", this); + rewrap (); + showMissingLines (); if (lines->size () > 0) { Line *lastLine = lines->getRef (lines->size () - 1); - requisition->width = - misc::max (lastLine->maxLineWidth, lastLineWidth); + requisition->width = lastLine->maxLineWidth; + + PRINTF ("[%p] SIZE_REQUEST: lastLine->maxLineWidth = %d\n", + this, lastLine->maxLineWidth); + + PRINTF ("[%p] SIZE_REQUEST: lines[0]->boxAscent = %d\n", + this, lines->getRef(0)->boxAscent); + PRINTF ("[%p] SIZE_REQUEST: lines[%d]->top = %d\n", + this, lines->size () - 1, lastLine->top); + PRINTF ("[%p] SIZE_REQUEST: lines[%d]->boxAscent = %d\n", + this, lines->size () - 1, lastLine->boxAscent); + PRINTF ("[%p] SIZE_REQUEST: lines[%d]->boxDescent = %d\n", + this, lines->size () - 1, lastLine->boxDescent); + /* Note: the breakSpace of the last line is ignored, so breaks at the end of a textblock are not visible. */ requisition->ascent = lines->getRef(0)->boxAscent; @@ -158,17 +171,23 @@ void Textblock::sizeRequestImpl (core::Requisition *requisition) + lastLine->boxAscent + lastLine->boxDescent - lines->getRef(0)->boxAscent; } else { - requisition->width = lastLineWidth; + requisition->width = 0; // before: lastLineWidth; requisition->ascent = 0; requisition->descent = 0; } + PRINTF ("[%p] SIZE_REQUEST: inner padding = %d, boxDiffWidth = %d\n", + this, innerPadding, getStyle()->boxDiffWidth ()); + requisition->width += innerPadding + getStyle()->boxDiffWidth (); requisition->ascent += getStyle()->boxOffsetY (); requisition->descent += getStyle()->boxRestHeight (); if (requisition->width < availWidth) requisition->width = availWidth; + + PRINTF ("[%p] SIZE_REQUEST: %d x %d + %d\n", this, requisition->width, + requisition->ascent, requisition->descent); } /** @@ -206,147 +225,125 @@ void Textblock::getWordExtremes (Word *word, core::Extremes *extremes) void Textblock::getExtremesImpl (core::Extremes *extremes) { - core::Extremes wordExtremes; - Line *line; - Word *word; - int wordIndex, lineIndex; - int parMin, parMax; - bool nowrap; + PRINTF ("[%p] GET_EXTREMES: ...\n", this); - //DBG_MSG (widget, "extremes", 0, "Dw_page_get_extremes"); - //DBG_MSG_START (widget); + showMissingLines (); if (lines->size () == 0) { /* empty page */ extremes->minWidth = 0; extremes->maxWidth = 0; + + PRINTF ("GET_EXTREMES: empty (but %d words)\n", words->size()); } else if (wrapRef == -1) { /* no rewrap necessary -> values in lines are up to date */ - line = lines->getRef (lines->size () - 1); - /* Historical note: The former distinction between lines with and without - * words[first_word]->nowrap set is no longer necessary, since - * Dw_page_real_word_wrap sets max_word_min to the correct value in any - * case. */ - extremes->minWidth = line->maxWordMin; - extremes->maxWidth = misc::max (line->maxParMax, lastLineParMax); - //DBG_MSG (widget, "extremes", 0, "simple case"); + Line *lastLine = lines->getRef (lines->size () - 1); + extremes->minWidth = lastLine->maxParMin; + Word *lastWord = words->getRef (lastLine->lastWord); + extremes->maxWidth = + misc::max (lastLine->maxParMax, + // parMax includes the last space, which we ignore here + lastLine->parMax - lastWord->origSpace + + lastWord->hyphenWidth); + + PRINTF ("GET_EXTREMES: no rewrap => %d, %d\n", + lastLine->maxParMin, lastLine->maxParMax); } else { + int parMax; /* Calculate the extremes, based on the values in the line from where a rewrap is necessary. */ - //DBG_MSG (widget, "extremes", 0, "complex case"); + + PRINTF ("GET_EXTREMES: complex case ...\n"); if (wrapRef == 0) { extremes->minWidth = 0; extremes->maxWidth = 0; - parMin = 0; parMax = 0; } else { - line = lines->getRef (wrapRef); - extremes->minWidth = line->maxWordMin; - extremes->maxWidth = line->maxParMax; - parMin = line->parMin; + // Line [wrapRef - 1], not [wrapRef], because maxParMin and + // maxParMax include the respective values *in* any line + // (accumulated up to the *end*), but we start at line + // [wrapRef], so these are not needed. + + Line *line = lines->getRef (wrapRef - 1); + Word *lastWord = words->getRef (line->lastWord); + extremes->minWidth = line->maxParMin; + // consider also accumulated next value of maxParMax: parMax + extremes->maxWidth = + misc::max (line->maxParMax, + // parMax includes the last space, which we ignore here + line->parMax - lastWord->origSpace + + lastWord->hyphenWidth); parMax = line->parMax; - - //DBG_MSGF (widget, "extremes", 0, "parMin = %d", parMin); } - //_MSG ("*** parMin = %d\n", parMin); - - int prevWordSpace = 0; - for (lineIndex = wrapRef; lineIndex < lines->size (); lineIndex++) { - //DBG_MSGF (widget, "extremes", 0, "line %d", lineIndex); - //DBG_MSG_START (widget); - core::style::WhiteSpace ws; - - line = lines->getRef (lineIndex); - ws = words->getRef(line->firstWord)->style->whiteSpace; - nowrap = ws == core::style::WHITE_SPACE_PRE || - ws == core::style::WHITE_SPACE_NOWRAP; - - //DEBUG_MSG (DEBUG_SIZE_LEVEL, " line %d (of %d), nowrap = %d\n", - // lineIndex, page->num_lines, nowrap); - - for (wordIndex = line->firstWord; wordIndex <= line->lastWord; - wordIndex++) { - word = words->getRef (wordIndex); + if (wrapRef < lines->size()) { + int parMin = 0; + + for (int wordIndex = lines->getRef(wrapRef)->firstWord; + wordIndex < words->size(); wordIndex++) { + Word *word = words->getRef (wordIndex); + bool atLastWord = wordIndex == words->size() - 1; + + //printf (" word: "); + //printWord (word); + //printf ("\n"); + + core::Extremes wordExtremes; getWordExtremes (word, &wordExtremes); - if (wordIndex == 0) { - wordExtremes.minWidth += line1OffsetEff; - wordExtremes.maxWidth += line1OffsetEff; - //DEBUG_MSG (DEBUG_SIZE_LEVEL + 1, - // " (next plus %d)\n", page->line1_offset); - } - - if (nowrap) { - parMin += prevWordSpace + wordExtremes.minWidth; - //DBG_MSGF (widget, "extremes", 0, "parMin = %d", parMin); - } else { - if (extremes->minWidth < wordExtremes.minWidth) - extremes->minWidth = wordExtremes.minWidth; + wordExtremes.minWidth += line1Offset; + wordExtremes.maxWidth += line1Offset; } - - _MSG("parMax = %d, wordMaxWidth=%d, prevWordSpace=%d\n", - parMax, wordExtremes.maxWidth, prevWordSpace); - if (word->content.type != core::Content::BREAK) - parMax += prevWordSpace; - parMax += wordExtremes.maxWidth; - prevWordSpace = word->origSpace; - - //DEBUG_MSG (DEBUG_SIZE_LEVEL + 1, - // " word %s: maxWidth = %d\n", - // a_Dw_content_text (&word->content), - // word_extremes.maxWidth); + + // Minimum: between two *possible* breaks (or at the end). + if (word->badnessAndPenalty.lineCanBeBroken () || atLastWord) { + parMin += wordExtremes.minWidth + word->hyphenWidth; + extremes->minWidth = misc::max (extremes->minWidth, parMin); + parMin = 0; + } else + // Shrinkability could be considered, but really does not play a + // role. + parMin += wordExtremes.minWidth + word->origSpace; + + // Maximum: between two *neccessary* breaks (or at the end). + if (word->badnessAndPenalty.lineMustBeBroken () || atLastWord) { + parMax += wordExtremes.maxWidth + word->hyphenWidth; + extremes->maxWidth = misc::max (extremes->maxWidth, parMax); + parMax = 0; + } else + parMax += wordExtremes.maxWidth + word->origSpace; + + PRINTF (" => ... extremes = %d / %d, parMin = %d, parMax = %d\n", + extremes->minWidth, extremes->maxWidth, parMin, parMax); } - - if ((words->getRef(line->lastWord)->content.type - == core::Content::BREAK ) || - lineIndex == lines->size () - 1 ) { - - //DEBUG_MSG (DEBUG_SIZE_LEVEL + 2, - // " parMax = %d, after word %d (%s)\n", - // parMax, line->last_word - 1, - // a_Dw_content_text (&word->content)); - - if (extremes->maxWidth < parMax) - extremes->maxWidth = parMax; - - if (nowrap) { - //DBG_MSGF (widget, "extremes", 0, "parMin = %d", parMin); - if (extremes->minWidth < parMin) - extremes->minWidth = parMin; - - //DEBUG_MSG (DEBUG_SIZE_LEVEL + 2, - // " parMin = %d, after word %d (%s)\n", - // parMin, line->last_word - 1, - // a_Dw_content_text (&word->content)); - } - - prevWordSpace = 0; - parMin = 0; - parMax = 0; - } - - //DBG_MSG_END (widget); } - - //DEBUG_MSG (DEBUG_SIZE_LEVEL + 3, " Result: %d, %d\n", - // extremes->minWidth, extremes->maxWidth); } - //DBG_MSGF (widget, "extremes", 0, "width difference: %d + %d", - // page->inner_padding, p_Dw_style_box_diff_width (widget->style)); - int diff = innerPadding + getStyle()->boxDiffWidth (); extremes->minWidth += diff; extremes->maxWidth += diff; - //DBG_MSG_END (widget); + //printf ("[%p] GET_EXTREMES, on textblock that ", this); + //if (words->size() == 0) + // printf ("is empty\n"); + //else { + // printf ("starts with:\n "); + // printWord (words->getRef(0)); + // printf ("\n"); + //} + //printf ("=> %d / %d\n", extremes->minWidth, extremes->maxWidth); } void Textblock::sizeAllocateImpl (core::Allocation *allocation) { + PRINTF ("[%p] SIZE_ALLOCATE: %d, %d, %d x %d + %d\n", + this, allocation->x, allocation->y, allocation->width, + allocation->ascent, allocation->descent); + + showMissingLines (); + int lineIndex, wordIndex; Line *line; Word *word; @@ -441,7 +438,7 @@ void Textblock::sizeAllocateImpl (core::Allocation *allocation) xCursor += (word->size.width + word->effSpace); } } - + if(leftFloatSide) leftFloatSide->sizeAllocate(allocation); if(rightFloatSide) @@ -450,7 +447,7 @@ void Textblock::sizeAllocateImpl (core::Allocation *allocation) for (int i = 0; i < anchors->size(); i++) { Anchor *anchor = anchors->getRef(i); int y; - + if (anchor->wordIndex >= words->size()) { y = allocation->y + allocation->ascent + allocation->descent; } else { @@ -490,14 +487,17 @@ void Textblock::markExtremesChange (int ref) */ void Textblock::markChange (int ref) { - printf("markChange(%d)\n", ref); + PRINTF ("[%p] MARK_CHANGE (%d): %d => ...\n", this, ref, wrapRef); + + /* By the way: ref == -1 may have two different causes: (i) flush() + calls "queueResize (-1, true)", when no rewrapping is necessary; + and (ii) a word may have parentRef == -1 , when it is not yet + added to a line. In the latter case, nothing has to be done + now, but addLine(...) will do everything neccessary. */ int refEquiv = (ref == 0) ? 1 | (dw::core::style::FLOAT_NONE << 1) : ref; if (refEquiv != -1 && (refEquiv & 1)) { - //DBG_MSGF (page, "wrap", 0, "Dw_page_mark_size_change (ref = %d)", ref); - - // ABC switch((refEquiv >> 1) & 3) { case dw::core::style::FLOAT_NONE: @@ -518,11 +518,13 @@ void Textblock::markChange (int ref) break; } } + + PRINTF (" ... => %d\n", wrapRef); } void Textblock::notifySetAsTopLevel() { - containingBox = this; + containingBox = this; } void Textblock::notifySetParent() @@ -532,21 +534,25 @@ void Textblock::notifySetParent() containingBox = NULL; Textblock *topmostTextblock = this; - for(Widget *widget = getParent(); widget != NULL; widget = widget->getParent()) + for(Widget *widget = getParent(); widget != NULL; + widget = widget->getParent()) { if(widget->instanceOf(Textblock::CLASS_ID)) topmostTextblock = (Textblock*)widget; } - for(Widget *widget = getParent(); containingBox == NULL; widget = widget->getParent()) + for(Widget *widget = getParent(); containingBox == NULL; + widget = widget->getParent()) { if(widget->getParent() == NULL) // No other widget left. containingBox = topmostTextblock; else if(widget->instanceOf(Textblock::CLASS_ID)) { - if(widget->getParent()->instanceOf(Table::CLASS_ID) /* this widget is a table cell */ || - widget->getStyle()->vloat != dw::core::style::FLOAT_NONE /* this widget is a float */) + if(// this widget is a table cell + widget->getParent()->instanceOf(Table::CLASS_ID) || + // this widget is a float + widget->getStyle()->vloat != dw::core::style::FLOAT_NONE) containingBox = (Textblock*)widget; } } @@ -554,13 +560,13 @@ void Textblock::notifySetParent() void Textblock::setWidth (int width) { - /* If limitTextWidth is set to YES, a queue_resize may also be + /* If limitTextWidth is set to YES, a queueResize() may also be * necessary. */ if (availWidth != width || limitTextWidth) { //DEBUG_MSG(DEBUG_REWRAP_LEVEL, - // "Dw_page_set_width: Calling p_Dw_widget_queue_resize, " + // "setWidth: Calling queueResize, " // "in page with %d word(s)\n", - // page->num_words); + // words->size()); availWidth = width; //<<<<<<< textblock.cc @@ -577,9 +583,9 @@ void Textblock::setAscent (int ascent) { if (availAscent != ascent) { //DEBUG_MSG(DEBUG_REWRAP_LEVEL, - // "Dw_page_set_ascent: Calling p_Dw_widget_queue_resize, " + // "setAscent: Calling queueResize, " // "in page with %d word(s)\n", - // page->num_words); + // words->size()); availAscent = ascent; //<<<<<<< textblock.cc @@ -595,9 +601,9 @@ void Textblock::setDescent (int descent) { if (availDescent != descent) { //DEBUG_MSG(DEBUG_REWRAP_LEVEL, - // "Dw_page_set_descent: Calling p_Dw_widget_queue_resize, " + // "setDescent: Calling queueResize, " // "in page with %d word(s)\n", - // page->num_words); + // words->size()); availDescent = descent; //<<<<<<< textblock.cc @@ -694,102 +700,113 @@ bool Textblock::sendSelectionEvent (core::SelectionState::EventType eventType, core::MousePositionEvent *event) { core::Iterator *it; - Line *line, *lastLine; - int nextWordStartX, wordStartX, wordX, nextWordX, yFirst, yLast; - int charPos = 0, link = -1, prevPos, wordIndex, lineIndex; - Word *word; - bool found, r; + int wordIndex; + int charPos = 0, link = -1; + bool r; if (words->size () == 0) { wordIndex = -1; } else { - lastLine = lines->getRef (lines->size () - 1); - yFirst = lineYOffsetCanvasI (0); - yLast = lineYOffsetCanvas (lastLine) + lastLine->boxAscent + - lastLine->boxDescent; + Line *lastLine = lines->getRef (lines->size () - 1); + int yFirst = lineYOffsetCanvasI (0); + int yLast = lineYOffsetCanvas (lastLine) + lastLine->boxAscent + + lastLine->boxDescent; if (event->yCanvas < yFirst) { // Above the first line: take the first word. wordIndex = 0; - charPos = 0; } else if (event->yCanvas >= yLast) { // Below the last line: take the last word. wordIndex = words->size () - 1; - word = words->getRef (wordIndex); - charPos = word->content.type == core::Content::TEXT ? - strlen (word->content.text) : 0; + charPos = core::SelectionState::END_OF_WORD; } else { - lineIndex = findLineIndex (event->yWidget); - line = lines->getRef (lineIndex); + Line *line = lines->getRef (findLineIndex (event->yWidget)); // Pointer within the break space? if (event->yWidget > (lineYOffsetWidget (line) + line->boxAscent + line->boxDescent)) { // Choose this break. wordIndex = line->lastWord; - charPos = 0; + charPos = core::SelectionState::END_OF_WORD; } else if (event->xWidget < lineXOffsetWidget (line)) { // Left of the first word in the line. wordIndex = line->firstWord; - charPos = 0; } else { - nextWordStartX = lineXOffsetWidget (line); - found = false; + int nextWordStartX = lineXOffsetWidget (line); + for (wordIndex = line->firstWord; - !found && wordIndex <= line->lastWord; + wordIndex <= line->lastWord; wordIndex++) { - word = words->getRef (wordIndex); - wordStartX = nextWordStartX; + Word *word = words->getRef (wordIndex); + int wordStartX = nextWordStartX; + nextWordStartX += word->size.width + word->effSpace; if (event->xWidget >= wordStartX && event->xWidget < nextWordStartX) { // We have found the word. - if (word->content.type == core::Content::TEXT) { - // Search the character the mouse pointer is in. - // nextWordX is the right side of this character. - charPos = 0; - while ((nextWordX = wordStartX + - layout->textWidth (word->style->font, - word->content.text, charPos)) - <= event->xWidget) - charPos = layout->nextGlyph (word->content.text, - charPos); - // The left side of this character. - prevPos = layout->prevGlyph (word->content.text, charPos); - wordX = wordStartX + layout->textWidth (word->style->font, - word->content.text, - prevPos); - - // If the mouse pointer is left from the middle, use the - // left position, otherwise, use the right one. - if (event->xWidget <= (wordX + nextWordX) / 2) - charPos = prevPos; + int yWidgetBase = lineYOffsetWidget (line) + line->boxAscent; + + if (event->xWidget >= nextWordStartX - word->effSpace) { + charPos = core::SelectionState::END_OF_WORD; + if (wordIndex < line->lastWord && + (words->getRef(wordIndex + 1)->content.type != + core::Content::BREAK) && + (event->yWidget <= + yWidgetBase + word->spaceStyle->font->descent) && + (event->yWidget > + yWidgetBase - word->spaceStyle->font->ascent)) { + link = word->spaceStyle->x_link; + } } else { - // Depends on whether the pointer is within the left or - // right half of the (non-text) word. - if (event->xWidget >= - (wordStartX + nextWordStartX) / 2) - charPos = core::SelectionState::END_OF_WORD; - else - charPos = 0; + if (event->yWidget <= yWidgetBase + word->size.descent && + event->yWidget > yWidgetBase - word->size.ascent) { + link = word->style->x_link; + } + if (word->content.type == core::Content::TEXT) { + int glyphX = wordStartX; + + while (1) { + int nextCharPos = + layout->nextGlyph (word->content.text, charPos); + int glyphWidth = + textWidth (word->content.text, charPos, + nextCharPos - charPos, word->style); + if (event->xWidget > glyphX + glyphWidth) { + glyphX += glyphWidth; + charPos = nextCharPos; + continue; + } else if (event->xWidget >= glyphX + glyphWidth/2){ + // On the right half of a character; + // now just look for combining chars + charPos = nextCharPos; + while (word->content.text[charPos]) { + nextCharPos = + layout->nextGlyph (word->content.text, + charPos); + if (textWidth (word->content.text, charPos, + nextCharPos - charPos, + word->style)) + break; + charPos = nextCharPos; + } + } + break; + } + } else { + // Depends on whether the pointer is within the left or + // right half of the (non-text) word. + if (event->xWidget >= (wordStartX + nextWordStartX) /2) + charPos = core::SelectionState::END_OF_WORD; + } } - - found = true; - link = word->style ? word->style->x_link : -1; break; } } - - if (!found) { + if (wordIndex > line->lastWord) { // No word found in this line (i.e. we are on the right side), // take the last of this line. wordIndex = line->lastWord; - if (wordIndex >= words->size ()) - wordIndex--; - word = words->getRef (wordIndex); - charPos = word->content.type == core::Content::TEXT ? - strlen (word->content.text) : - (int)core::SelectionState::END_OF_WORD; + charPos = core::SelectionState::END_OF_WORD; } } } @@ -811,391 +828,6 @@ core::Iterator *Textblock::iterator (core::Content::Type mask, bool atEnd) return new TextblockIterator (this, mask, atEnd); } -/* - * ... - * - * availWidth is passed from wordWrap, to avoid calculating it twice. - */ -void Textblock::justifyLine (Line *line, int availWidth) -{ - /* To avoid rounding errors, the calculation is based on accumulated - * values (*_cum). */ - int i; - int origSpaceSum, origSpaceCum; - int effSpaceDiffCum, lastEffSpaceDiffCum; - int diff; - - diff = availWidth - lastLineWidth; - if (diff > 0) { - origSpaceSum = 0; - for (i = line->firstWord; i < line->lastWord; i++) - origSpaceSum += words->getRef(i)->origSpace; - - origSpaceCum = 0; - lastEffSpaceDiffCum = 0; - for (i = line->firstWord; i < line->lastWord; i++) { - origSpaceCum += words->getRef(i)->origSpace; - - if (origSpaceCum == 0) - effSpaceDiffCum = lastEffSpaceDiffCum; - else - effSpaceDiffCum = diff * origSpaceCum / origSpaceSum; - - words->getRef(i)->effSpace = words->getRef(i)->origSpace + - (effSpaceDiffCum - lastEffSpaceDiffCum); - //DBG_OBJ_ARRSET_NUM (page, "words.%d.eff_space", i, - // page->words[i].eff_space); - - lastEffSpaceDiffCum = effSpaceDiffCum; - } - } -} - - -Textblock::Line *Textblock::addLine (int wordIndex, bool newPar) -{ - Line *lastLine; - - //DBG_MSG (page, "wrap", 0, "Dw_page_add_line"); - //DBG_MSG_START (page); - - lines->increase (); - //DBG_OBJ_SET_NUM(page, "num_lines", page->num_lines); - - //DEBUG_MSG (DEBUG_REWRAP_LEVEL, "--- new line %d in %p, with word %d of %d" - // "\n", page->num_lines - 1, page, word_ind, page->num_words); - - lastLine = lines->getRef (lines->size () - 1); - - if (lines->size () == 1) { - lastLine->top = 0; - lastLine->maxLineWidth = line1OffsetEff; - lastLine->maxWordMin = 0; - lastLine->maxParMax = 0; - } else { - Line *prevLine = lines->getRef (lines->size () - 2); - - lastLine->top = prevLine->top + prevLine->boxAscent + - prevLine->boxDescent + prevLine->breakSpace; - lastLine->maxLineWidth = prevLine->maxLineWidth; - lastLine->maxWordMin = prevLine->maxWordMin; - lastLine->maxParMax = prevLine->maxParMax; - } - - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.top", page->num_lines - 1, - // lastLine->top); - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.maxLineWidth", page->num_lines - 1, - // lastLine->maxLineWidth); - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.maxWordMin", page->num_lines - 1, - // lastLine->maxWordMin); - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.maxParMax", page->num_lines - 1, - // lastLine->maxParMax); - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.parMin", page->num_lines - 1, - // lastLine->parMin); - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.parMax", page->num_lines - 1, - // lastLine->parMax); - - lastLine->firstWord = wordIndex; - lastLine->boxAscent = lastLine->contentAscent = 0; - lastLine->boxDescent = lastLine->contentDescent = 0; - lastLine->marginDescent = 0; - lastLine->breakSpace = 0; - lastLine->leftOffset = 0; - lastLine->boxLeft = 0; - lastLine->boxRight = 0; - - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.ascent", page->num_lines - 1, - // lastLine->boxAscent); - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.descent", page->num_lines - 1, - // lastLine->boxDescent); - - /* update values in line */ - lastLine->maxLineWidth = misc::max (lastLine->maxLineWidth, lastLineWidth); - - if (lines->size () > 1) - lastLineWidth = 0; - else - lastLineWidth = line1OffsetEff; - - if (newPar) { - lastLine->maxParMax = misc::max (lastLine->maxParMax, lastLineParMax); - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.maxParMax", page->num_lines - 1, - // lastLine->maxParMax); - - /* The following code looks questionable (especially since the values - * will be overwritten). In any case, line1OffsetEff is probably - * supposed to go into lastLinePar*, not lastLine->par*. - */ - if (lines->size () > 1) { - lastLine->parMin = 0; - lastLine->parMax = 0; - } else { - lastLine->parMin = line1OffsetEff; - lastLine->parMax = line1OffsetEff; - } - lastLineParMin = 0; - lastLineParMax = 0; - - //DBG_OBJ_SET_NUM(page, "lastLineParMin", page->lastLineParMin); - //DBG_OBJ_SET_NUM(page, "lastLineParMax", page->lastLineParMax); - } - - lastLine->parMin = lastLineParMin; - lastLine->parMax = lastLineParMax; - - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.parMin", page->num_lines - 1, - // lastLine->parMin); - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.parMax", page->num_lines - 1, - // lastLine->parMax); - - //DBG_MSG_END (page); - return lastLine; -} - -/* - * This method is called in two cases: (i) when a word is added - * (ii) when a page has to be (partially) rewrapped. It does word wrap, - * and adds new lines if necessary. - */ -void Textblock::wordWrap(int wordIndex) -{ - Line *lastLine; - Word *word; - int availWidth, lastSpace, leftOffset, len; - bool newLine = false, newPar = false; - core::Extremes wordExtremes; - - //DBG_MSGF (page, "wrap", 0, "Dw_page_real_word_wrap (%d): %s, width = %d", - // word_ind, a_Dw_content_html (&page->words[word_ind].content), - // page->words[word_ind].size.width); - //DBG_MSG_START (page); - - availWidth = this->availWidth - getStyle()->boxDiffWidth() - innerPadding; - if (limitTextWidth && - layout->getUsesViewport () && - availWidth > layout->getWidthViewport () - 10) - availWidth = layout->getWidthViewport () - 10; - - word = words->getRef (wordIndex); - word->effSpace = word->origSpace; - - /* Test whether line1Offset can be used. */ - if (wordIndex == 0) { - if (ignoreLine1OffsetSometimes && - line1Offset + word->size.width > availWidth) { - line1OffsetEff = 0; - } else { - int indent = 0; - - if (word->content.type == core::Content::WIDGET && - word->content.widget->blockLevel() == true) { - /* don't use text-indent when nesting blocks */ - } else { - if (core::style::isPerLength(getStyle()->textIndent)) { - indent = misc::roundInt(this->availWidth * - core::style::perLengthVal (getStyle()->textIndent)); - } else { - indent = core::style::absLengthVal (getStyle()->textIndent); - } - } - line1OffsetEff = line1Offset + indent; - } - } - - if(word->content.type == dw::core::Content::FLOAT_REF) - { - Line *line = lines->size() > 0 ? lines->getRef(lines->size() - 1) : NULL; - int y = - allocation.y - containingBox->allocation.y + getStyle()->boxOffsetY() + - (line ? line->top : 0); - int lineHeight = line ? line->boxAscent + line->boxDescent : 0; - - containingBox->handleFloatInContainer(word->content.widget, misc::max(lines->size() - 1, 0), y, lastLineWidth, lineHeight); - } - - - - if (lines->size () == 0) { - //DBG_MSG (page, "wrap", 0, "first line"); - newLine = true; - newPar = true; - lastLine = NULL; - } else { - Word *prevWord = words->getRef (wordIndex - 1); - - lastLine = lines->getRef (lines->size () - 1); - - if (prevWord->content.type == core::Content::BREAK) { - //DBG_MSG (page, "wrap", 0, "after a break"); - /* previous word is a break */ - newLine = true; - newPar = true; - } else if (word->style->whiteSpace == core::style::WHITE_SPACE_NOWRAP || - word->style->whiteSpace == core::style::WHITE_SPACE_PRE) { - //DBG_MSGF (page, "wrap", 0, "no wrap (white_space = %d)", - // word->style->white_space); - newLine = false; - newPar = false; - } else if (lastLine->firstWord != wordIndex) { - /* Does new word fit into the last line? */ - //DBG_MSGF (page, "wrap", 0, - // "word %d (%s) fits? (%d + %d + %d <= %d)...", - // word_ind, a_Dw_content_html (&word->content), - // page->lastLine_width, prevWord->orig_space, - // word->size.width, availWidth); - newLine = lastLineWidth + prevWord->origSpace + word->size.width > - availWidth - (lastLine->boxLeft + lastLine->boxRight); - //DBG_MSGF (page, "wrap", 0, "... %s.", - // newLine ? "No" : "Yes"); - } - } - - if (newLine) { - if (word->style->textAlign == core::style::TEXT_ALIGN_JUSTIFY && - lastLine != NULL && !newPar) { - justifyLine (lastLine, availWidth); - } - lastLine = addLine (wordIndex, newPar); - } - - lastLine->lastWord = wordIndex; - lastLine->boxAscent = misc::max (lastLine->boxAscent, word->size.ascent); - lastLine->boxDescent = misc::max (lastLine->boxDescent, word->size.descent); - - len = word->style->font->ascent; - if (word->style->valign == core::style::VALIGN_SUPER) - len += len / 2; - lastLine->contentAscent = misc::max (lastLine->contentAscent, len); - - len = word->style->font->descent; - if (word->style->valign == core::style::VALIGN_SUB) - len += word->style->font->ascent / 3; - lastLine->contentDescent = misc::max (lastLine->contentDescent, len); - - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.ascent", page->num_lines - 1, - // lastLine->boxAscent); - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.descent", page->num_lines - 1, - // lastLine->boxDescent); - - if (word->content.type == core::Content::WIDGET) { - int collapseMarginTop = 0; - - lastLine->marginDescent = - misc::max (lastLine->marginDescent, - word->size.descent + - word->content.widget->getStyle()->margin.bottom); - - if (lines->size () == 1 && - word->content.widget->blockLevel () && - getStyle ()->borderWidth.top == 0 && - getStyle ()->padding.top == 0) { - // collapse top margin of parent element with top margin of first child - // see: http://www.w3.org/TR/CSS21/box.html#collapsing-margins - collapseMarginTop = getStyle ()->margin.top; - } - - lastLine->boxAscent = - misc::max (lastLine->boxAscent, - word->size.ascent, - word->size.ascent - + word->content.widget->getStyle()->margin.top - - collapseMarginTop); - - } else { - lastLine->marginDescent = - misc::max (lastLine->marginDescent, lastLine->boxDescent); - - if (word->content.type == core::Content::BREAK) - lastLine->breakSpace = - misc::max (word->content.breakSpace, - lastLine->marginDescent - lastLine->boxDescent, - lastLine->breakSpace); - } - - lastSpace = (wordIndex > 0) ? words->getRef(wordIndex - 1)->origSpace : 0; - - if (!newLine) - lastLineWidth += lastSpace; - if (!newPar) { - lastLineParMin += lastSpace; - lastLineParMax += lastSpace; - } - - lastLineWidth += word->size.width; - - getWordExtremes (word, &wordExtremes); - lastLineParMin += wordExtremes.maxWidth; /* Why maxWidth? */ - lastLineParMax += wordExtremes.maxWidth; - - if (word->style->whiteSpace == core::style::WHITE_SPACE_NOWRAP || - word->style->whiteSpace == core::style::WHITE_SPACE_PRE) { - lastLine->parMin += wordExtremes.minWidth + lastSpace; - /* This may also increase the accumulated minimum word width. */ - lastLine->maxWordMin = - misc::max (lastLine->maxWordMin, lastLine->parMin); - /* NOTE: Most code relies on that all values of nowrap are equal for all - * words within one line. */ - } else { - lastLine->maxWordMin = - misc::max (lastLine->maxWordMin, wordExtremes.minWidth); - } - - //DBG_OBJ_SET_NUM(page, "lastLine_par_min", page->lastLine_par_min); - //DBG_OBJ_SET_NUM(page, "lastLine_par_max", page->lastLine_par_max); - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.par_min", page->num_lines - 1, - // lastLine->par_min); - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.par_max", page->num_lines - 1, - // lastLine->par_max); - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.max_word_min", page->num_lines - 1, - // lastLine->max_word_min); - - /* Align the line. - * \todo Use block's style instead once paragraphs become proper blocks. - */ - if (word->content.type != core::Content::BREAK) { - switch (word->style->textAlign) { - case core::style::TEXT_ALIGN_LEFT: - case core::style::TEXT_ALIGN_JUSTIFY: /* see some lines above */ - case core::style::TEXT_ALIGN_STRING: /* handled elsewhere (in the - * future) */ - leftOffset = 0; - break; - case core::style::TEXT_ALIGN_RIGHT: - leftOffset = availWidth - lastLineWidth; - break; - case core::style::TEXT_ALIGN_CENTER: - leftOffset = (availWidth - lastLineWidth) / 2; - break; - default: - /* compiler happiness */ - leftOffset = 0; - } - - /* For large lines (images etc), which do not fit into the viewport: */ - if (leftOffset < 0) - leftOffset = 0; - - if (hasListitemValue && lastLine == lines->getRef (0)) { - /* List item markers are always on the left. */ - lastLine->leftOffset = 0; - words->getRef(0)->effSpace = words->getRef(0)->origSpace + leftOffset; - //DBG_OBJ_ARRSET_NUM (page, "words.%d.eff_space", 0, - // page->words[0].eff_space); - } else { - lastLine->leftOffset = leftOffset; - } - - int y = - allocation.y - containingBox->allocation.y + - getStyle()->boxOffsetY () + lastLine->top; - lastLine->boxLeft = calcLeftFloatBorder(y, this); - lastLine->boxRight = calcRightFloatBorder(y, this); - } - mustQueueResize = true; - - //DBG_MSG_END (page); -} - /** * Calculate the size of a widget within the page. @@ -1254,92 +886,6 @@ void Textblock::calcWidgetSize (core::Widget *widget, core::Requisition *size) size->descent -= wstyle->margin.bottom; } -/** - * Rewrap the page from the line from which this is necessary. - * There are basically two times we'll want to do this: - * either when the viewport is resized, or when the size changes on one - * of the child widgets. - */ -void Textblock::rewrap () -{ - int i, wordIndex; - Word *word; - Line *lastLine; - - if (wrapRef == -1) - /* page does not have to be rewrapped */ - return; - - //DBG_MSGF (page, "wrap", 0, - // "Dw_page_rewrap: page->wrap_ref = %d, in page with %d word(s)", - // page->wrap_ref, page->num_words); - //DBG_MSG_START (page); - - /* All lines up from page->wrap_ref will be rebuild from the word list, - * the line list up from this position is rebuild. */ - lines->setSize (wrapRef); - lastLineWidth = 0; - //DBG_OBJ_SET_NUM(page, "num_lines", page->num_lines); - //DBG_OBJ_SET_NUM(page, "lastLine_width", page->lastLine_width); - - /* In the word list, start at the last word plus one in the line before. */ - if (wrapRef > 0) { - /* Note: In this case, Dw_page_real_word_wrap will immediately find - * the need to rewrap the line, since we start with the last one (plus - * one). This is also the reason, why page->lastLine_width is set - * to the length of the line. */ - lastLine = lines->getRef (lines->size () - 1); - - lastLineParMin = lastLine->parMin; - lastLineParMax = lastLine->parMax; - - wordIndex = lastLine->lastWord + 1; - for (i = lastLine->firstWord; i < lastLine->lastWord; i++) - lastLineWidth += (words->getRef(i)->size.width + - words->getRef(i)->origSpace); - lastLineWidth += words->getRef(lastLine->lastWord)->size.width; - } else { - lastLineParMin = 0; - lastLineParMax = 0; - - wordIndex = 0; - } - - for (; wordIndex < words->size (); wordIndex++) { - word = words->getRef (wordIndex); - - if (word->content.type == core::Content::WIDGET) - calcWidgetSize (word->content.widget, &word->size); - wordWrap (wordIndex); - - if (word->content.type == core::Content::WIDGET) { - // ABC - word->content.widget->parentRef = 1 | (dw::core::style::FLOAT_NONE << 1) | ((lines->size () - 1) << 3); - printf("parentRef = %d\n", word->content.widget->parentRef); - //DBG_OBJ_SET_NUM (word->content.widget, "parent_ref", - // word->content.widget->parent_ref); - } - - //DEBUG_MSG(DEBUG_REWRAP_LEVEL, - // "Assigning parent_ref = %d to rewrapped word %d, " - // "in page with %d word(s)\n", - // page->num_lines - 1, wordIndex, page->num_words); - - /* todo_refactoring: - if (word->content.type == DW_CONTENT_ANCHOR) - p_Dw_gtk_viewport_change_anchor - (widget, word->content.anchor, - Dw_page_line_total_y_offset (page, - &page->lines[page->num_lines - 1])); - */ - } - - /* Next time, the page will not have to be rewrapped. */ - wrapRef = -1; - - //DBG_MSG_END (page); -} - /* * Draw the decorations on a word. */ @@ -1366,14 +912,115 @@ void Textblock::decorateText(core::View *view, core::style::Style *style, } /* - * Draw a word of text. + * Draw a string of text */ -void Textblock::drawText(int wordIndex, core::View *view,core::Rectangle *area, - int xWidget, int yWidgetBase) +void Textblock::drawText(core::View *view, core::style::Style *style, + core::style::Color::Shading shading, int x, int y, + const char *text, int start, int len) +{ + if (len > 0) { + char *str = NULL; + + switch (style->textTransform) { + case core::style::TEXT_TRANSFORM_NONE: + default: + break; + case core::style::TEXT_TRANSFORM_UPPERCASE: + str = layout->textToUpper(text + start, len); + break; + case core::style::TEXT_TRANSFORM_LOWERCASE: + str = layout->textToLower(text + start, len); + break; + case core::style::TEXT_TRANSFORM_CAPITALIZE: + { + /* \bug No way to know about non-ASCII punctuation. */ + bool initial_seen = false; + + for (int i = 0; i < start; i++) + if (!ispunct(text[i])) + initial_seen = true; + if (initial_seen) + break; + + int after = 0; + text += start; + while (ispunct(text[after])) + after++; + if (text[after]) + after = layout->nextGlyph(text, after); + if (after > len) + after = len; + + char *initial = layout->textToUpper(text, after); + int newlen = strlen(initial) + len-after; + str = (char *)malloc(newlen + 1); + strcpy(str, initial); + strncpy(str + strlen(str), text+after, len-after); + str[newlen] = '\0'; + free(initial); + break; + } + } + view->drawText(style->font, style->color, shading, x, y, + str ? str : text + start, str ? strlen(str) : len); + if (str) + free(str); + } +} + +/* + * Draw a word of text. TODO New description; + */ +void Textblock::drawWord (Line *line, int wordIndex1, int wordIndex2, + core::View *view, core::Rectangle *area, + int xWidget, int yWidgetBase) +{ + core::style::Style *style = words->getRef(wordIndex1)->style; + bool drawHyphen = wordIndex2 == line->lastWord + && words->getRef(wordIndex2)->hyphenWidth > 0; + + if (wordIndex1 == wordIndex2 && !drawHyphen) { + // Simple case, where copying in one buffer is not needed. + Word *word = words->getRef (wordIndex1); + drawWord0 (wordIndex1, wordIndex2, word->content.text, word->size.width, + style, view, area, xWidget, yWidgetBase); + } else { + // Concaternate all words in a new buffer. + int l = 0, totalWidth = 0; + for (int i = wordIndex1; i <= wordIndex2; i++) { + Word *w = words->getRef (i); + l += strlen (w->content.text); + totalWidth += w->size.width; + } + + char text[l + (drawHyphen ? 2 : 0) + 1]; + int p = 0; + for (int i = wordIndex1; i <= wordIndex2; i++) { + const char * t = words->getRef(i)->content.text; + strcpy (text + p, t); + p += strlen (t); + } + + if(drawHyphen) { + text[p++] = 0xc2; + text[p++] = 0xad; + text[p++] = 0; + } + + drawWord0 (wordIndex1, wordIndex2, text, totalWidth, + style, view, area, xWidget, yWidgetBase); + } +} + +/** + * TODO Comment + */ +void Textblock::drawWord0 (int wordIndex1, int wordIndex2, + const char *text, int totalWidth, + core::style::Style *style, core::View *view, + core::Rectangle *area, int xWidget, int yWidgetBase) { - Word *word = words->getRef(wordIndex); int xWorld = allocation.x + xWidget; - core::style::Style *style = word->style; int yWorldBase; /* Adjust the text baseline if the word is <SUP>-ed or <SUB>-ed. */ @@ -1384,38 +1031,55 @@ void Textblock::drawText(int wordIndex, core::View *view,core::Rectangle *area, } yWorldBase = yWidgetBase + allocation.y; - view->drawText (style->font, style->color, - core::style::Color::SHADING_NORMAL, xWorld, yWorldBase, - word->content.text, strlen (word->content.text)); + drawText (view, style, core::style::Color::SHADING_NORMAL, xWorld, + yWorldBase, text, 0, strlen (text)); if (style->textDecoration) decorateText(view, style, core::style::Color::SHADING_NORMAL, xWorld, - yWorldBase, word->size.width); + yWorldBase, totalWidth); for (int layer = 0; layer < core::HIGHLIGHT_NUM_LAYERS; layer++) { - if (hlStart[layer].index <= wordIndex && - hlEnd[layer].index >= wordIndex) { - const int wordLen = strlen (word->content.text); + if (wordIndex1 <= hlEnd[layer].index && + wordIndex2 >= hlStart[layer].index) { + const int wordLen = strlen (text); int xStart, width; - int firstCharIdx = 0; - int lastCharIdx = wordLen; - - if (wordIndex == hlStart[layer].index) - firstCharIdx = misc::min (hlStart[layer].nChar, wordLen); + int firstCharIdx; + int lastCharIdx; + + if (hlStart[layer].index < wordIndex1) + firstCharIdx = 0; + else { + firstCharIdx = + misc::min (hlStart[layer].nChar, + (int)strlen (words->getRef(hlStart[layer].index) + ->content.text)); + for (int i = wordIndex1; i < hlStart[layer].index; i++) + // It can be assumed that all words from wordIndex1 to + // wordIndex2 have content type TEXT. + firstCharIdx += strlen (words->getRef(i)->content.text); + } - if (wordIndex == hlEnd[layer].index) - lastCharIdx = misc::min (hlEnd[layer].nChar, wordLen); + if (hlEnd[layer].index > wordIndex2) + lastCharIdx = wordLen; + else { + lastCharIdx = + misc::min (hlEnd[layer].nChar, + (int)strlen (words->getRef(hlEnd[layer].index) + ->content.text)); + for (int i = wordIndex1; i < hlEnd[layer].index; i++) + // It can be assumed that all words from wordIndex1 to + // wordIndex2 have content type TEXT. + lastCharIdx += strlen (words->getRef(i)->content.text); + } xStart = xWorld; if (firstCharIdx) - xStart += layout->textWidth (style->font, word->content.text, - firstCharIdx); + xStart += textWidth (text, 0, firstCharIdx, style); if (firstCharIdx == 0 && lastCharIdx == wordLen) - width = word->size.width; + width = totalWidth; else - width = layout->textWidth (style->font, - word->content.text + firstCharIdx, - lastCharIdx - firstCharIdx); + width = textWidth (text, firstCharIdx, + lastCharIdx - firstCharIdx, style); if (width > 0) { /* Highlight text */ core::style::Color *wordBgColor; @@ -1430,10 +1094,9 @@ void Textblock::drawText(int wordIndex, core::View *view,core::Rectangle *area, style->font->ascent + style->font->descent); /* Highlight the text. */ - view->drawText (style->font, style->color, - core::style::Color::SHADING_INVERSE, xStart, - yWorldBase, word->content.text + firstCharIdx, - lastCharIdx - firstCharIdx); + drawText (view, style, core::style::Color::SHADING_INVERSE, xStart, + yWorldBase, text, firstCharIdx, + lastCharIdx - firstCharIdx); if (style->textDecoration) decorateText(view, style, core::style::Color::SHADING_INVERSE, @@ -1501,18 +1164,11 @@ void Textblock::drawLine (Line *line, core::View *view, core::Rectangle *area) int xWidget = lineXOffsetWidget(line); int yWidgetBase = lineYOffsetWidget (line) + line->boxAscent; - /* Here's an idea on how to optimize this routine to minimize the number - * of drawing calls: - * - * Copy the text from the words into a buffer, adding a new word - * only if: the attributes match, and the spacing is either zero or - * equal to the width of ' '. In the latter case, copy a " " into - * the buffer. Then draw the buffer. */ - for (int wordIndex = line->firstWord; wordIndex <= line->lastWord && xWidget < area->x + area->width; wordIndex++) { Word *word = words->getRef(wordIndex); + int wordSize = word->size.width; if (xWidget + word->size.width + word->effSpace >= area->x) { if (word->content.type == core::Content::TEXT || @@ -1526,12 +1182,26 @@ void Textblock::drawLine (Line *line, core::View *view, core::Rectangle *area) if (child->intersects (area, &childArea)) child->draw (view, &childArea); } else { + /* TODO: include in drawWord: if (word->style->hasBackground ()) { drawBox (view, word->style, area, xWidget, yWidgetBase - line->boxAscent, word->size.width, line->boxAscent + line->boxDescent, false); - } - drawText(wordIndex, view, area, xWidget, yWidgetBase); + }*/ + int wordIndex2 = wordIndex; + while (wordIndex2 < line->lastWord && + words->getRef(wordIndex2)->hyphenWidth > 0 && + word->style == words->getRef(wordIndex2 + 1)->style) + wordIndex2++; + + drawWord(line, wordIndex, wordIndex2, view, area, + xWidget, yWidgetBase); + wordSize = 0; + for (int i = wordIndex; i <= wordIndex2; i++) + wordSize += words->getRef(i)->size.width; + + wordIndex = wordIndex2; + word = words->getRef(wordIndex); } } if (word->effSpace > 0 && wordIndex < line->lastWord && @@ -1539,16 +1209,16 @@ void Textblock::drawLine (Line *line, core::View *view, core::Rectangle *area) core::Content::BREAK) { if (word->spaceStyle->hasBackground ()) drawBox (view, word->spaceStyle, area, - xWidget + word->size.width, + xWidget + wordSize, yWidgetBase - line->boxAscent, word->effSpace, line->boxAscent + line->boxDescent, false); - drawSpace(wordIndex, view, area, xWidget + word->size.width, + drawSpace(wordIndex, view, area, xWidget + wordSize, yWidgetBase); } } } - xWidget += word->size.width + word->effSpace; + xWidget += wordSize + word->effSpace; } } @@ -1592,6 +1262,7 @@ int Textblock::findLineOfWord (int wordIndex) { int high = lines->size () - 1, index, low = 0; + // TODO regard also not-yet-existing lines? if (wordIndex < 0 || wordIndex >= words->size ()) return -1; @@ -1631,11 +1302,22 @@ Textblock::Word *Textblock::findWord (int x, int y, bool *inSpace) word = words->getRef (wordIndex); lastXCursor = xCursor; xCursor += word->size.width + word->effSpace; - if (lastXCursor <= x && xCursor > x && - y > yWidgetBase - word->size.ascent && - y <= yWidgetBase + word->size.descent) { - *inSpace = x >= xCursor - word->effSpace; - return word; + if (lastXCursor <= x && xCursor > x) { + if (x >= xCursor - word->effSpace) { + if (wordIndex < line->lastWord && + (words->getRef(wordIndex + 1)->content.type != + core::Content::BREAK) && + y > yWidgetBase - word->spaceStyle->font->ascent && + y <= yWidgetBase + word->spaceStyle->font->descent) { + *inSpace = true; + return word; + } + } else { + if (y > yWidgetBase - word->size.ascent && + y <= yWidgetBase + word->size.descent) + return word; + } + break; } } @@ -1644,6 +1326,9 @@ Textblock::Word *Textblock::findWord (int x, int y, bool *inSpace) void Textblock::draw (core::View *view, core::Rectangle *area) { + PRINTF ("DRAW: %d, %d, %d x %d\n", + area->x, area->y, area->width, area->height); + int lineIndex; Line *line; @@ -1669,39 +1354,89 @@ void Textblock::draw (core::View *view, core::Rectangle *area) * Add a new word (text, widget etc.) to a page. */ Textblock::Word *Textblock::addWord (int width, int ascent, int descent, + bool canBeHyphenated, core::style::Style *style) { - Word *word; - words->increase (); + Word *word = words->getLastRef (); + fillWord (word, width, ascent, descent, canBeHyphenated, style); + return word; +} - word = words->getRef (words->size() - 1); +void Textblock::fillWord (Word *word, int width, int ascent, int descent, + bool canBeHyphenated, core::style::Style *style) +{ word->size.width = width; word->size.ascent = ascent; word->size.descent = descent; - word->origSpace = 0; - word->effSpace = 0; + word->origSpace = word->effSpace = word->stretchability = + word->shrinkability = 0; + word->hyphenWidth = 0; + word->badnessAndPenalty.setPenaltyProhibitBreak (); word->content.space = false; - - //DBG_OBJ_ARRSET_NUM (page, "words.%d.size.width", page->num_words - 1, - // word->size.width); - //DBG_OBJ_ARRSET_NUM (page, "words.%d.size.descent", page->num_words - 1, - // word->size.descent); - //DBG_OBJ_ARRSET_NUM (page, "words.%d.size.ascent", page->num_words - 1, - // word->size.ascent); - //DBG_OBJ_ARRSET_NUM (page, "words.%d.orig_space", page->num_words - 1, - // word->orig_space); - //DBG_OBJ_ARRSET_NUM (page, "words.%d.eff_space", page->num_words - 1, - // word->eff_space); - //DBG_OBJ_ARRSET_NUM (page, "words.%d.content.space", page->num_words - 1, - // word->content.space); + word->canBeHyphenated = canBeHyphenated; word->style = style; word->spaceStyle = style; style->ref (); style->ref (); +} - return word; +/* + * Get the width of a string of text. + */ +int Textblock::textWidth(const char *text, int start, int len, + core::style::Style *style) +{ + int ret = 0; + + if (len > 0) { + char *str = NULL; + + switch (style->textTransform) { + case core::style::TEXT_TRANSFORM_NONE: + default: + ret = layout->textWidth(style->font, text+start, len); + break; + case core::style::TEXT_TRANSFORM_UPPERCASE: + str = layout->textToUpper(text+start, len); + ret = layout->textWidth(style->font, str, strlen(str)); + break; + case core::style::TEXT_TRANSFORM_LOWERCASE: + str = layout->textToLower(text+start, len); + ret = layout->textWidth(style->font, str, strlen(str)); + break; + case core::style::TEXT_TRANSFORM_CAPITALIZE: + { + /* \bug No way to know about non-ASCII punctuation. */ + bool initial_seen = false; + + for (int i = 0; i < start; i++) + if (!ispunct(text[i])) + initial_seen = true; + if (initial_seen) { + ret = layout->textWidth(style->font, text+start, len); + } else { + int after = 0; + + text += start; + while (ispunct(text[after])) + after++; + if (text[after]) + after = layout->nextGlyph(text, after); + if (after > len) + after = len; + str = layout->textToUpper(text, after); + ret = layout->textWidth(style->font, str, strlen(str)) + + layout->textWidth(style->font, text+after, len-after); + } + break; + } + } + if (str) + free(str); + } + return ret; } /** @@ -1711,7 +1446,7 @@ void Textblock::calcTextSize (const char *text, size_t len, core::style::Style *style, core::Requisition *size) { - size->width = layout->textWidth (style->font, text, len); + size->width = textWidth (text, 0, len, style); size->ascent = style->font->ascent; size->descent = style->font->descent; @@ -1755,25 +1490,129 @@ void Textblock::calcTextSize (const char *text, size_t len, size->ascent += (style->font->ascent / 2); } - /** - * Add a word to the page structure. + * Add a word to the page structure. If it contains soft hyphens, it is + * divided. */ void Textblock::addText (const char *text, size_t len, core::style::Style *style) { - Word *word; - core::Requisition size; + PRINTF ("[%p] ADD_TEXT (%d characters)\n", this, (int)len); + + // Count hyphens. + int numHyphens = 0; + for (int i = 0; i < (int)len - 1; i++) + // (0xc2, 0xad) is the UTF-8 representation of a soft hyphen (Unicode + // 0xc2). + if((unsigned char)text[i] == 0xc2 && (unsigned char)text[i + 1] == 0xad) + numHyphens++; + + if (numHyphens == 0) { + // Simple (and often) case: no soft hyphens. May still be hyphenated + // automatically. + core::Requisition size; + calcTextSize (text, len, style, &size); + addText0 (text, len, true, style, &size); + } else { + PRINTF("HYPHENATION: '"); + for (size_t i = 0; i < len; i++) + PUTCHAR(text[i]); + PRINTF("', with %d hyphen(s)\n", numHyphens); + + // Store hyphen positions. + int n = 0, hyphenPos[numHyphens], breakPos[numHyphens]; + for (size_t i = 0; i < len - 1; i++) + if((unsigned char)text[i] == 0xc2 && + (unsigned char)text[i + 1] == 0xad) { + hyphenPos[n] = i; + breakPos[n] = i - 2 * n; + n++; + } + + // Get text without hyphens. (There are numHyphens + 1 parts in the word, + // and 2 * numHyphens bytes less, 2 for each hyphen, are needed.) + char textWithoutHyphens[len - 2 * numHyphens]; + int start = 0; // related to "text" + for (int i = 0; i < numHyphens + 1; i++) { + int end = (i == numHyphens) ? len : hyphenPos[i]; + memmove (textWithoutHyphens + start - 2 * i, text + start, + end - start); + start = end + 2; + } - calcTextSize (text, len, style, &size); - word = addWord (size.width, size.ascent, size.descent, style); + PRINTF("H... without hyphens: '"); + for (size_t i = 0; i < len - 2 * numHyphens; i++) + PUTCHAR(textWithoutHyphens[i]); + PRINTF("'\n"); + + core::Requisition wordSize[numHyphens + 1]; + calcTextSizes (textWithoutHyphens, len - 2 * numHyphens, style, + numHyphens, breakPos, wordSize); + + // Finished! + for (int i = 0; i < numHyphens + 1; i++) { + int start = (i == 0) ? 0 : hyphenPos[i - 1] + 2; + int end = (i == numHyphens) ? len : hyphenPos[i]; + // Do not anymore hyphen automatically. + addText0 (text + start, end - start, false, style, &wordSize[i]); + + PRINTF("H... [%d] '", i); + for (int j = start; j < end; j++) + PUTCHAR(text[j]); + PRINTF("' added\n"); + + if(i < numHyphens) { + addHyphen (); + PRINTF("H... yphen added\n"); + } + } + } +} + +void Textblock::calcTextSizes (const char *text, size_t textLen, + core::style::Style *style, + int numBreaks, int *breakPos, + core::Requisition *wordSize) +{ + // The size of the last part is calculated in a simple way. + int lastStart = breakPos[numBreaks - 1]; + calcTextSize (text + lastStart, textLen - lastStart, style, + &wordSize[numBreaks]); + + PRINTF("H... [%d] '", numBreaks); + for (size_t i = 0; i < textLen - lastStart; i++) + PUTCHAR(text[i + lastStart]); + PRINTF("' -> %d\n", wordSize[numBreaks].width); + + // The rest is more complicated. TODO Documentation. + for (int i = numBreaks - 1; i >= 0; i--) { + int start = (i == 0) ? 0 : breakPos[i - 1]; + calcTextSize (text + start, textLen - start, style, &wordSize[i]); + + PRINTF("H... [%d] '", i); + for (size_t j = 0; j < textLen - start; j++) + PUTCHAR(text[j + start]); + PRINTF("' -> %d\n", wordSize[i].width); + + for (int j = i + 1; j < numBreaks + 1; j++) { + wordSize[i].width -= wordSize[j].width; + PRINTF("H... - %d = %d\n", wordSize[j].width, wordSize[i].width); + } + } +} + +/** + * Add a word (without hyphens) to the page structure. + */ +void Textblock::addText0 (const char *text, size_t len, bool canBeHyphenated, + core::style::Style *style, core::Requisition *size) +{ + Word *word = addWord (size->width, size->ascent, size->descent, + canBeHyphenated, style); word->content.type = core::Content::TEXT; word->content.text = layout->textZone->strndup(text, len); - //DBG_OBJ_ARRSET_STR (page, "words.%d.content.text", page->num_words - 1, - // word->content.text); - - wordWrap (words->size () - 1); + wordWrap (words->size () - 1, false); } /** @@ -1790,19 +1629,21 @@ void Textblock::addWidget (core::Widget *widget, core::style::Style *style) * end of this function, the correct value is assigned. */ widget->parentRef = -1; + PRINTF ("%p becomes child of %p\n", widget, this); + widget->setParent (this); widget->setStyle (style); calcWidgetSize (widget, &size); - word = addWord (size.width, size.ascent, size.descent, style); + word = addWord (size.width, size.ascent, size.descent, false, style); word->content.type = core::Content::WIDGET; word->content.widget = widget; - //DBG_OBJ_ARRSET_PTR (page, "words.%d.content.widget", page->num_words - 1, + //DBG_OBJ_ARRSET_PTR (page, "words.%d.content.widget", words->size() - 1, // word->content.widget); - wordWrap (words->size () - 1); + wordWrap (words->size () - 1, false); // ABC word->content.widget->parentRef = 1 | (dw::core::style::FLOAT_NONE << 1) | ((lines->size () - 1) << 3); printf("parentRef = %d\n", word->content.widget->parentRef); @@ -1812,10 +1653,9 @@ void Textblock::addWidget (core::Widget *widget, core::style::Style *style) //DEBUG_MSG(DEBUG_REWRAP_LEVEL, // "Assigning parent_ref = %d to added word %d, " // "in page with %d word(s)\n", - // page->num_lines - 1, page->num_words - 1, page->num_words); + // lines->size () - 1, words->size() - 1, words->size()); } - /** * Add an anchor to the page. "name" is copied, so no strdup is necessary for * the caller. @@ -1863,29 +1703,84 @@ bool Textblock::addAnchor (const char *name, core::style::Style *style) void Textblock::addSpace (core::style::Style *style) { int wordIndex = words->size () - 1; + if (wordIndex >= 0) { + fillSpace (words->getRef(wordIndex), style); + accumulateWordData (wordIndex); + } +} + +void Textblock::fillSpace (Word *word, core::style::Style *style) +{ + // Old comment: + // + // According to + // http://www.w3.org/TR/CSS2/text.html#white-space-model: "line + // breaking opportunities are determined based on the text + // prior to the white space collapsing steps". + // + // So we call addBreakOption () for each Textblock::addSpace () + // call. This is important e.g. to be able to break between + // foo and bar in: <span style="white-space:nowrap">foo </span> + // bar + // + // TODO: Re-evaluate again. + + // TODO: This line does not work: addBreakOption (word, style); + + if (!word->content.space) { + // Do not override a previously set break penalty. + if (!word->badnessAndPenalty.lineMustBeBroken()) { + switch (style->whiteSpace) { + case core::style::WHITE_SPACE_NORMAL: + case core::style::WHITE_SPACE_PRE_LINE: + word->badnessAndPenalty.setPenalty (0); + break; + + case core::style::WHITE_SPACE_PRE: + case core::style::WHITE_SPACE_NOWRAP: + case core::style::WHITE_SPACE_PRE_WRAP: + word->badnessAndPenalty.setPenaltyProhibitBreak (); + break; + } + } + + word->content.space = true; + word->effSpace = word->origSpace = style->font->spaceWidth + + style->wordSpacing; + word->stretchability = word->origSpace / 2; + if(style->textAlign == core::style::TEXT_ALIGN_JUSTIFY) + word->shrinkability = word->origSpace / 3; + else + word->shrinkability = 0; + + //DBG_OBJ_ARRSET_NUM (this, "words.%d.origSpace", wordIndex, + // word->origSpace); + //DBG_OBJ_ARRSET_NUM (this, "words.%d.effSpace", wordIndex, + // word->effSpace); + //DBG_OBJ_ARRSET_NUM (this, "words.%d.content.space", wordIndex, + // word->content.space); + + word->spaceStyle->unref (); + word->spaceStyle = style; + style->ref (); + } +} + +void Textblock::addHyphen () +{ + int wordIndex = words->size () - 1; if (wordIndex >= 0) { Word *word = words->getRef(wordIndex); + + word->badnessAndPenalty.setPenalty (HYPHEN_BREAK); + // TODO Optimize? Like spaces? + word->hyphenWidth = layout->textWidth (word->style->font, "\xc2\xad", 2); - if (!word->content.space) { - word->content.space = true; - word->effSpace = word->origSpace = style->font->spaceWidth + - style->wordSpacing; - - //DBG_OBJ_ARRSET_NUM (page, "words.%d.orig_space", nw, - // page->words[nw].orig_space); - //DBG_OBJ_ARRSET_NUM (page, "words.%d.eff_space", nw, - // page->words[nw].eff_space); - //DBG_OBJ_ARRSET_NUM (page, "words.%d.content.space", nw, - // page->words[nw].content.space); - word->spaceStyle->unref (); - word->spaceStyle = style; - style->ref (); - } + accumulateWordData (wordIndex); } } - /** * Cause a paragraph break */ @@ -1959,10 +1854,11 @@ void Textblock::addParbreak (int space, core::style::Style *style) return; } - word = addWord (0, 0, 0, style); + word = addWord (0, 0, 0, false, style); word->content.type = core::Content::BREAK; + word->badnessAndPenalty.setPenaltyForceBreak (); word->content.breakSpace = space; - wordWrap (words->size () - 1); + wordWrap (words->size () - 1, false); } /* @@ -1976,14 +1872,16 @@ void Textblock::addLinebreak (core::style::Style *style) words->getRef(words->size () - 1)->content.type == core::Content::BREAK) // An <BR> in an empty line gets the height of the current font // (why would someone else place it here?), ... - word = addWord (0, style->font->ascent, style->font->descent, style); + word = + addWord (0, style->font->ascent, style->font->descent, false, style); else // ... otherwise, it has no size (and does not enlarge the line). - word = addWord (0, 0, 0, style); + word = addWord (0, 0, 0, false, style); word->content.type = core::Content::BREAK; + word->badnessAndPenalty.setPenaltyForceBreak (); word->content.breakSpace = 0; - wordWrap (words->size () - 1); + wordWrap (words->size () - 1, false); } /** \todo This MUST be commented! */ @@ -1994,12 +1892,12 @@ void Textblock::addFloatIntoGenerator (core::Widget *widget, core::style::Style widget->setStyle (style); containingBox->addFloatIntoContainer(widget, this); - word = addWord (0, 0, 0, style); + word = addWord (0, 0, 0, false, style); word->content.type = core::Content::FLOAT_REF; word->content.breakSpace = 0; word->content.widget = widget; word->style = style; - wordWrap (words->size () - 1); + wordWrap (words->size () - 1, false); } @@ -2065,9 +1963,9 @@ void Textblock::handOverBreak (core::style::Style *style) } /* - * Any words added by a_Dw_page_add_... are not immediately (queued to + * Any words added by addWord() are not immediately (queued to * be) drawn, instead, this function must be called. This saves some - * calls to p_Dw_widget_queue_resize. + * calls to queueResize(). * */ void Textblock::flush () @@ -2077,9 +1975,11 @@ void Textblock::flush () // printf("queueResize(%s, -1, true)\n", asap ? "true" : "false"); // queueResize (asap, -1, true); //======= + PRINTF ("[%p] FLUSH => %s (parentRef = %d)\n", + this, mustQueueResize ? "true" : "false", parentRef); + if (mustQueueResize) { queueResize (-1, true); -//>>>>>>> 1.24 mustQueueResize = false; } } @@ -2522,9 +2422,8 @@ void Textblock::TextblockIterator::getAllocation (int start, int end, allocation->x += w->size.width + w->effSpace; } if (start > 0 && word->content.type == core::Content::TEXT) { - allocation->x += textblock->layout->textWidth (word->style->font, - word->content.text, - start); + allocation->x += textblock->textWidth (word->content.text, 0, start, + word->style); } allocation->y = textblock->lineYOffsetCanvas (line) + line->boxAscent - word->size.ascent; @@ -2536,9 +2435,8 @@ void Textblock::TextblockIterator::getAllocation (int start, int end, if (start > 0 || end < wordEnd) { end = misc::min(end, wordEnd); /* end could be INT_MAX */ allocation->width = - textblock->layout->textWidth (word->style->font, - word->content.text + start, - end - start); + textblock->textWidth (word->content.text, start, end - start, + word->style); } } allocation->ascent = word->size.ascent; diff --git a/dw/textblock.hh b/dw/textblock.hh index ea7795b8..8d8f91cc 100644 --- a/dw/textblock.hh +++ b/dw/textblock.hh @@ -1,18 +1,29 @@ #ifndef __DW_TEXTBLOCK_HH__ #define __DW_TEXTBLOCK_HH__ +#include <limits.h> + #include "core.hh" #include "../lout/misc.hh" +// These were used when improved line breaking and hyphenation were +// implemented. Should be cleaned up; perhaps reactivate RTFL again. +#define PRINTF(fmt, ...) +#define PUTCHAR(ch) + namespace dw { /** * \brief A Widget for rendering text blocks, i.e. paragraphs or sequences * of paragraphs. * - * <strong>Important Note:</strong>: This documentation is out of date, since - * floats have been implementet. Will be updated and extended soon. - * + * <div style="border: 2px solid #ffff00; margin-top: 0.5em; + * margin-bottom: 0.5em; padding: 0.5em 1em; background-color: + * #ffffe0"><b>Info:</b> The recent changes (line breaking and + * hyphenation on one hand, floats on the other hand) have not yet + * been incorporated into this documentation. See \ref + * dw-line-breaking and \ref dw-special-textflow.</div> + * * <h3>Signals</h3> * * dw::Textblock uses the signals defined in @@ -134,6 +145,76 @@ namespace dw { class Textblock: public core::Widget { private: + // Hint: the following is somewhat chaotic, as a result of the merge + // of dillo_hyphen and dillo_floats + + // Part 1 -- Line-Breaking and Hyphenation + + /** + * This class encapsulates the badness/penalty calculation, and so + * (i) makes changes (hopefully) simpler, and (ii) hides the + * integer arithmetics (floating point arithmetics avoided for + * performance reasons). Unfortunately, the value range of the + * badness is not well defined, so fiddling with the penalties is a + * bit difficult. + */ + class BadnessAndPenalty + { + private: + enum { NOT_STRETCHABLE, QUITE_LOOSE, BADNESS_VALUE, TOO_TIGHT } + badnessState; + enum { FORCE_BREAK, PROHIBIT_BREAK, PENALTY_VALUE } penaltyState; + int ratio; // ratio is only defined when badness is defined + int badness, penalty; + + // for debugging: + int totalWidth, idealWidth, totalStretchability, totalShrinkability; + + // "Infinity levels" are used to represent very large numbers, + // including "quasi-infinite" numbers. A couple of infinity + // level and number can be mathematically represented as + // + // number * N ^ (infinity level) + // + // where N is a number which is large enough. Practically, + // infinity levels are used to circumvent limited ranges for + // integer numbers. + + // Here, all infinity levels have got special meanings. + enum { + INF_VALUE = 0, /* simple values */ + INF_LARGE, /* large values, like QUITE_LOOSE */ + INF_NOT_STRETCHABLE, /* reserved for NOT_STRECTHABLE */ + INF_PENALTIES, /* used for penalties */ + INF_TOO_TIGHT, /* used for lines, which are too tight; + that this is the last value means + that lines, which are too tight, are + regarded as the worst case */ + INF_MAX = INF_TOO_TIGHT + }; + + int badnessValue (int infLevel); + int penaltyValue (int infLevel); + + public: + void calcBadness (int totalWidth, int idealWidth, + int totalStretchability, int totalShrinkability); + void setPenalty (int penalty); + void setPenaltyProhibitBreak (); + void setPenaltyForceBreak (); + + bool lineLoose (); + bool lineTight (); + bool lineTooTight (); + bool lineMustBeBroken (); + bool lineCanBeBroken (); + int compareTo (BadnessAndPenalty *other); + + void print (); + }; + + // Part 2 -- Floats + Textblock *containingBox; class FloatSide @@ -192,8 +273,19 @@ private: }; FloatSide *leftFloatSide, *rightFloatSide; - + + // End of merge chaos. + protected: + enum { + /** + * The penalty for hyphens, multiplied with 100. So, 100 means + * 1.0. See dw::Textblock::BadnessAndPenalty::setPenalty for + * more details. + */ + HYPHEN_BREAK = 100 + }; + struct Line { int firstWord; /* first word's index in word vector */ @@ -209,17 +301,35 @@ protected: * widgets within this line. */ int marginDescent; - /* The following members contain accumulated values, from the top - * down to the line before. */ - int maxLineWidth; /* maximum of all line widths */ - int maxWordMin; /* maximum of all word minima */ - int maxParMax; /* maximum of all paragraph maxima */ - int parMin; /* the minimal total width down from the last - * paragraph start, to the *beginning* of the - * line */ - int parMax; /* the maximal total width down from the last - * paragraph start, to the *beginning* of the - * line */ + /* The following members contain accumulated values, from the + * top down to this line. Please notice a change: until + * recently, the values were accumulated up to the last line, + * not this line. + * + * Also, keep in mind that at the end of a line, the space of + * the last word is ignored, but instead, the hyphen width must + * be considered.*/ + + int maxLineWidth; /* Maximum of all line widths, including this + * line. Does not include the last space, but + * the last hyphen width. */ + int maxParMin; /* Maximum of all paragraph minima, including + * this line. */ + int maxParMax; /* Maximum of all paragraph maxima. This line + * is only included, if it is the last line of + * the paragraph (last word is a forced + * break); otherwise, it is the value of the + * last paragraph. For this reason, consider + * also parMax. */ + int parMax; /* The maximal total width down from the last + * paragraph start, to the *end* of this line. + * The space at the end of this line is + * included, but not the hyphen width (as + * opposed to the other values). So, in some + * cases, the space has to be subtracted and + * the hyphen width to be added, to compare it + * to maxParMax. (Search the code for + * occurances.) */ }; struct Word @@ -228,15 +338,39 @@ protected: core::Requisition size; /* Space after the word, only if it's not a break: */ short origSpace; /* from font, set by addSpace */ + short stretchability, shrinkability; short effSpace; /* effective space, set by wordWrap, * used for drawing etc. */ + short hyphenWidth; /* Additional width, when a word is part + * (except the last part) of a hyphenationed + * word. Has to be added to the width, when + * this is the last word of the line, and + * "hyphenWidth > 0" is also used to decide + * weather to draw a hyphen. */ core::Content content; + bool canBeHyphenated; + + // accumulated values, relative to the beginning of the line + int totalWidth; /* The sum of all word widths; plus all + spaces, excluding the one of this + word; plus the hypthen width of this + word (but of course, no hyphen + widths of previous words. In other + words: the value compared to the + ideal width of the line, if the line + would be broken after this word. */ + int totalStretchability; // includes all *before* current word + int totalShrinkability; // includes all *before* current word + BadnessAndPenalty badnessAndPenalty; /* when line is broken after this + * word */ core::style::Style *style; core::style::Style *spaceStyle; /* initially the same as of the word, later set by a_Dw_page_add_space */ }; + void printWord (Word *word); + struct Anchor { char *name; @@ -303,13 +437,11 @@ protected: /* These values are set by set_... */ int availWidth, availAscent, availDescent; - int lastLineWidth; - int lastLineParMin; - int lastLineParMax; int wrapRef; /* [0 based] */ lout::misc::SimpleVector <Line> *lines; - lout::misc::SimpleVector <Word> *words; + int nonTemporaryLines; + lout::misc::NotSoSimpleVector <Word> *words; lout::misc::SimpleVector <Anchor> *anchors; struct {int index, nChar;} @@ -321,23 +453,38 @@ protected: void queueDrawRange (int index1, int index2); void getWordExtremes (Word *word, core::Extremes *extremes); void markChange (int ref); - void justifyLine (Line *line, int availWidth); - Line *addLine (int wordInd, bool newPar); + void justifyLine (Line *line, int diff); + Line *addLine (int firstWord, int lastWord, bool temporary); void calcWidgetSize (core::Widget *widget, core::Requisition *size); void rewrap (); - void decorateText(core::View *view, core::style::Style *style, - core::style::Color::Shading shading, - int x, int yBase, int width); - void drawText(int wordIndex, core::View *view, core::Rectangle *area, - int xWidget, int yWidgetBase); - void drawSpace(int wordIndex, core::View *view, core::Rectangle *area, - int xWidget, int yWidgetBase); + void showMissingLines (); + void removeTemporaryLines (); + + void decorateText (core::View *view, core::style::Style *style, + core::style::Color::Shading shading, + int x, int yBase, int width); + void drawText (core::View *view, core::style::Style *style, + core::style::Color::Shading shading, int x, int y, + const char *text, int start, int len); + void drawWord (Line *line, int wordIndex1, int wordIndex2, core::View *view, + core::Rectangle *area, int xWidget, int yWidgetBase); + void drawWord0 (int wordIndex1, int wordIndex2, + const char *text, int totalWidth, + core::style::Style *style, core::View *view, + core::Rectangle *area, int xWidget, int yWidgetBase); + void drawSpace (int wordIndex, core::View *view, core::Rectangle *area, + int xWidget, int yWidgetBase); void drawLine (Line *line, core::View *view, core::Rectangle *area); int findLineIndex (int y); int findLineOfWord (int wordIndex); Word *findWord (int x, int y, bool *inSpace); - Word *addWord (int width, int ascent, int descent, + Word *addWord (int width, int ascent, int descent, bool canBeHyphenated, + core::style::Style *style); + void fillWord (Word *word, int width, int ascent, int descent, + bool canBeHyphenated, core::style::Style *style); + void fillSpace (Word *word, core::style::Style *style); + int textWidth (const char *text, int start, int len, core::style::Style *style); void calcTextSize (const char *text, size_t len, core::style::Style *style, core::Requisition *size); @@ -361,7 +508,7 @@ protected: inline int lineXOffsetContents (Line *line) { return innerPadding + line->leftOffset + line->boxLeft + - (line == lines->getRef (0) ? line1OffsetEff : 0); + (line == lines->getFirstRef() ? line1OffsetEff : 0); } /** @@ -414,7 +561,15 @@ protected: bool sendSelectionEvent (core::SelectionState::EventType eventType, core::MousePositionEvent *event); - virtual void wordWrap(int wordIndex); + void accumulateWordExtremes (int firstWord, int lastWord, + int *maxOfMinWidth, int *sumOfMaxWidth); + virtual void wordWrap (int wordIndex, bool wrapAll); + int hyphenateWord (int wordIndex); + void accumulateWordForLine (int lineIndex, int wordIndex); + void accumulateWordData(int wordIndex); + int calcAvailWidth (int lineIndex); + void initLine1Offset (int wordIndex); + void alignLine (int lineIndex); void sizeRequestImpl (core::Requisition *requisition); void getExtremesImpl (core::Extremes *extremes); @@ -438,6 +593,13 @@ protected: void removeChild (Widget *child); + void addText0 (const char *text, size_t len, bool canBeHyphenated, + core::style::Style *style, core::Requisition *size); + void calcTextSizes (const char *text, size_t textLen, + core::style::Style *style, + int numBreaks, int *breakPos, + core::Requisition *wordSize); + public: static int CLASS_ID; @@ -455,7 +617,25 @@ public: } void addWidget (core::Widget *widget, core::style::Style *style); bool addAnchor (const char *name, core::style::Style *style); - void addSpace(core::style::Style *style); + void addSpace (core::style::Style *style); + + inline void addBreakOption (core::style::Style *style) + { + int wordIndex = words->size () - 1; + if (wordIndex >= 0) + addBreakOption (words->getRef(wordIndex), style); + } + + // TODO Re-evaluate again. When needed, which penalty values, etc. + inline void addBreakOption (Word *word, core::style::Style *style) + { + if (style->whiteSpace != core::style::WHITE_SPACE_NOWRAP && + style->whiteSpace != core::style::WHITE_SPACE_PRE) + if (word->badnessAndPenalty.lineMustBeBroken()) + word->badnessAndPenalty.setPenalty (0); + } + + void addHyphen(); void addParbreak (int space, core::style::Style *style); void addLinebreak (core::style::Style *style); diff --git a/dw/textblock_linebreaking.cc b/dw/textblock_linebreaking.cc new file mode 100644 index 00000000..7947d22f --- /dev/null +++ b/dw/textblock_linebreaking.cc @@ -0,0 +1,962 @@ +#include "textblock.hh" +#include "hyphenator.hh" +#include "../lout/msg.h" +#include "../lout/misc.hh" + +#include <stdio.h> +#include <math.h> + +using namespace lout; + +namespace dw { + +int Textblock::BadnessAndPenalty::badnessValue (int infLevel) +{ + switch (badnessState) { + case NOT_STRETCHABLE: + return infLevel == INF_NOT_STRETCHABLE ? 1 : 0; + + case QUITE_LOOSE: + return infLevel == INF_LARGE ? 1 : 0; + + case BADNESS_VALUE: + return infLevel == INF_VALUE ? badness : 0; + + case TOO_TIGHT: + return infLevel == INF_TOO_TIGHT ? 1 : 0; + } + + // compiler happiness + lout::misc::assertNotReached (); + return 0; +} + +int Textblock::BadnessAndPenalty::penaltyValue (int infLevel) +{ + switch (penaltyState) { + case FORCE_BREAK: + return infLevel == INF_PENALTIES ? -1 : 0; + + case PROHIBIT_BREAK: + return infLevel == INF_PENALTIES ? 1 : 0; + + case PENALTY_VALUE: + return infLevel == INF_VALUE ? penalty : 0; + } + + // compiler happiness + lout::misc::assertNotReached (); + return 0; +} + +void Textblock::BadnessAndPenalty::calcBadness (int totalWidth, int idealWidth, + int totalStretchability, + int totalShrinkability) +{ + this->totalWidth = totalWidth; + this->idealWidth = idealWidth; + this->totalStretchability = totalStretchability; + this->totalShrinkability = totalShrinkability; + + ratio = 0; // because this is used in print() + + if (totalWidth == idealWidth) { + badnessState = BADNESS_VALUE; + badness = 0; + } else if (totalWidth < idealWidth) { + if (totalStretchability == 0) + badnessState = NOT_STRETCHABLE; + else { + ratio = 100 * (idealWidth - totalWidth) / totalStretchability; + if (ratio > 1024) + badnessState = QUITE_LOOSE; + else { + badnessState = BADNESS_VALUE; + badness = ratio * ratio * ratio; + } + } + } else { // if (word->totalWidth > availWidth) + if (totalShrinkability == 0) + badnessState = TOO_TIGHT; + else { + // ratio is negative here + ratio = 100 * (idealWidth - totalWidth) / totalShrinkability; + if (ratio <= - 100) + badnessState = TOO_TIGHT; + else { + badnessState = BADNESS_VALUE; + badness = - ratio * ratio * ratio; + } + } + } +} + +/** + * Sets the penalty, multiplied with 100. Multiplication is necessary + * to deal with fractional numbers, without having to use floating + * point numbers. So, to set a penalty to 0.5, pass 50. + * + * The definition of penalties depends on the definition of badness, + * which adheres to the description in \ref dw-line-breaking, section + * "Criteria for Line-Breaking". The exact calculation may vary, but + * this definition of should be rather stable: (i) A perfectly + * fitting line has a badness of 0. (ii) A line, where all spaces + * are extended by exactly the stretchabilty, as well as a line, where + * all spaces are reduced by the shrinkability, have a badness of 1. + */ +void Textblock::BadnessAndPenalty::setPenalty (int penalty) +{ + // This factor consists of: (i) 100^3, since in calcBadness(), the + // ratio is multiplied with 100 (again, to use integer numbers for + // fractional numbers), and the badness (which has to be compared + // to the penalty!) is the third power or it; (ii) the denominator + // 100, of course, since 100 times the penalty is passed to this + // method. + this->penalty = penalty * (100 * 100 * 100 / 100); + penaltyState = PENALTY_VALUE; +} + +void Textblock::BadnessAndPenalty::setPenaltyProhibitBreak () +{ + penaltyState = PROHIBIT_BREAK; +} + +void Textblock::BadnessAndPenalty::setPenaltyForceBreak () +{ + penaltyState = FORCE_BREAK; +} + +bool Textblock::BadnessAndPenalty::lineLoose () +{ + return + badnessState == NOT_STRETCHABLE || badnessState == QUITE_LOOSE || + (badnessState == BADNESS_VALUE && ratio > 0); +} + +bool Textblock::BadnessAndPenalty::lineTight () +{ + return + badnessState == TOO_TIGHT || (badnessState == BADNESS_VALUE && ratio < 0); +} + +bool Textblock::BadnessAndPenalty::lineTooTight () +{ + return badnessState == TOO_TIGHT; +} + + +bool Textblock::BadnessAndPenalty::lineMustBeBroken () +{ + return penaltyState == FORCE_BREAK; +} + +bool Textblock::BadnessAndPenalty::lineCanBeBroken () +{ + return penaltyState != PROHIBIT_BREAK; +} + +int Textblock::BadnessAndPenalty::compareTo (BadnessAndPenalty *other) +{ + for (int l = INF_MAX; l >= 0; l--) { + int thisValue = badnessValue (l) + penaltyValue (l); + int otherValue = other->badnessValue (l) + other->penaltyValue (l); + + if (thisValue != otherValue) + return thisValue - otherValue; + } + + return 0; +} + +void Textblock::BadnessAndPenalty::print () +{ + switch (badnessState) { + case NOT_STRETCHABLE: + printf ("not stretchable"); + break; + + case TOO_TIGHT: + printf ("too tight"); + break; + + case QUITE_LOOSE: + printf ("quite loose (ratio = %d)", ratio); + break; + + case BADNESS_VALUE: + printf ("%d", badness); + break; + } + + printf (" [%d + %d - %d vs. %d] + ", + totalWidth, totalStretchability, totalShrinkability, idealWidth); + + switch (penaltyState) { + case FORCE_BREAK: + printf ("-inf"); + break; + + case PROHIBIT_BREAK: + printf ("inf"); + break; + + case PENALTY_VALUE: + printf ("%d", penalty); + break; + } +} + +void Textblock::printWord (Word *word) +{ + switch(word->content.type) { + case core::Content::TEXT: + printf ("\"%s\"", word->content.text); + break; + case core::Content::WIDGET: + printf ("<widget: %p>\n", word->content.widget); + break; + case core::Content::BREAK: + printf ("<break>\n"); + break; + default: + printf ("<?>\n"); + break; + } + + printf (" [%d / %d + %d - %d => %d + %d - %d] => ", + word->size.width, word->origSpace, word->stretchability, + word->shrinkability, word->totalWidth, word->totalStretchability, + word->totalShrinkability); + word->badnessAndPenalty.print (); +} + +/* + * ... + * + * diff ... + */ +void Textblock::justifyLine (Line *line, int diff) +{ + /* To avoid rounding errors, the calculation is based on accumulated + * values. */ + + if (diff > 0) { + int stretchabilitySum = 0; + for (int i = line->firstWord; i < line->lastWord; i++) + stretchabilitySum += words->getRef(i)->stretchability; + + if (stretchabilitySum > 0) { + int stretchabilityCum = 0; + int spaceDiffCum = 0; + for (int i = line->firstWord; i < line->lastWord; i++) { + Word *word = words->getRef (i); + stretchabilityCum += word->stretchability; + int spaceDiff = + stretchabilityCum * diff / stretchabilitySum - spaceDiffCum; + spaceDiffCum += spaceDiff; + + PRINTF (" %d (of %d): diff = %d\n", i, words->size (), + spaceDiff); + + word->effSpace = word->origSpace + spaceDiff; + } + } + } else if (diff < 0) { + int shrinkabilitySum = 0; + for (int i = line->firstWord; i < line->lastWord; i++) + shrinkabilitySum += words->getRef(i)->shrinkability; + + if (shrinkabilitySum > 0) { + int shrinkabilityCum = 0; + int spaceDiffCum = 0; + for (int i = line->firstWord; i < line->lastWord; i++) { + Word *word = words->getRef (i); + shrinkabilityCum += word->shrinkability; + int spaceDiff = + shrinkabilityCum * diff / shrinkabilitySum - spaceDiffCum; + spaceDiffCum += spaceDiff; + + word->effSpace = word->origSpace + spaceDiff; + } + } + } +} + + +Textblock::Line *Textblock::addLine (int firstWord, int lastWord, + bool temporary) +{ + PRINTF ("[%p] ADD_LINE (%d, %d) => %d\n", + this, firstWord, lastWord, lines->size ()); + + Word *lastWordOfLine = words->getRef(lastWord); + // Word::totalWidth includes the hyphen (which is what we want here). + int lineWidth = lastWordOfLine->totalWidth; + int maxOfMinWidth, sumOfMaxWidth; + accumulateWordExtremes (firstWord, lastWord, &maxOfMinWidth, + &sumOfMaxWidth); + + PRINTF (" words[%d]->totalWidth = %d\n", lastWord, + lastWordOfLine->totalWidth); + + PRINTF ("[%p] ##### LINE ADDED: %d, from %d to %d #####\n", + this, lines->size (), firstWord, lastWord); + + lines->increase (); + if(!temporary) { + // If the last line was temporary, this will be temporary, too, even + // if not requested. + if (lines->size () == 1 || nonTemporaryLines == lines->size () -1) + nonTemporaryLines = lines->size (); + } + + PRINTF ("nonTemporaryLines = %d\n", nonTemporaryLines); + + int lineIndex = lines->size () - 1; + Line *line = lines->getRef (lineIndex); + + line->firstWord = firstWord; + line->lastWord = lastWord; + line->boxAscent = line->contentAscent = 0; + line->boxDescent = line->contentDescent = 0; + line->marginDescent = 0; + line->breakSpace = 0; + line->leftOffset = 0; + + alignLine (lineIndex); + for (int i = line->firstWord; i < line->lastWord; i++) { + Word *word = words->getRef (i); + lineWidth += (word->effSpace - word->origSpace); + } + + int lastMaxParMax; // maxParMax of the last line + + if (lines->size () == 1) { + // first line + line->top = 0; + + line->maxLineWidth = lineWidth; + line->maxParMin = maxOfMinWidth; + line->parMax = sumOfMaxWidth; + + lastMaxParMax = 0; + } else { + Line *prevLine = lines->getRef (lines->size () - 2); + + line->top = prevLine->top + prevLine->boxAscent + + prevLine->boxDescent + prevLine->breakSpace; + + line->maxLineWidth = misc::max (lineWidth, prevLine->maxLineWidth); + line->maxParMin = misc::max (maxOfMinWidth, prevLine->maxParMin); + + Word *lastWordOfPrevLine = words->getRef (prevLine->lastWord); + if (lastWordOfPrevLine->badnessAndPenalty.lineMustBeBroken ()) + // This line starts a new paragraph. + line->parMax = sumOfMaxWidth; + else + // This line continues the paragraph from prevLine. + line->parMax = prevLine->parMax + sumOfMaxWidth; + + lastMaxParMax = prevLine->maxParMax; + } + + // "maxParMax" is only set, when this line is the last line of the + // paragraph. + Word *lastWordOfThisLine = words->getRef (line->lastWord); + if (lastWordOfThisLine->badnessAndPenalty.lineMustBeBroken ()) + // Paragraph ends here. + line->maxParMax = + misc::max (lastMaxParMax, + // parMax includes the last space, which we ignore here + line->parMax - lastWordOfThisLine->origSpace + + lastWordOfThisLine->hyphenWidth); + else + // Paragraph continues: simply copy the last value of "maxParMax". + line->maxParMax = lastMaxParMax; + + for(int i = line->firstWord; i <= line->lastWord; i++) + accumulateWordForLine (lineIndex, i); + + PRINTF (" line[%d].top = %d\n", lines->size () - 1, line->top); + PRINTF (" line[%d].boxAscent = %d\n", lines->size () - 1, line->boxAscent); + PRINTF (" line[%d].boxDescent = %d\n", + lines->size () - 1, line->boxDescent); + PRINTF (" line[%d].contentAscent = %d\n", lines->size () - 1, + line->contentAscent); + PRINTF (" line[%d].contentDescent = %d\n", + lines->size () - 1, line->contentDescent); + + PRINTF (" line[%d].maxLineWidth = %d\n", + lines->size () - 1, line->maxLineWidth); + PRINTF (" line[%d].maxParMin = %d\n", + lines->size () - 1, line->maxParMin); + PRINTF (" line[%d].maxParMax = %d\n", + lines->size () - 1, line->maxParMax); + PRINTF (" line[%d].parMax = %d\n", lines->size () - 1, line->parMax); + + mustQueueResize = true; + + return line; +} + +void Textblock::accumulateWordExtremes (int firstWord, int lastWord, + int *maxOfMinWidth, int *sumOfMaxWidth) +{ + int parMin = 0; + *maxOfMinWidth = *sumOfMaxWidth = 0; + + for (int i = firstWord; i <= lastWord; i++) { + Word *word = words->getRef (i); + bool atLastWord = i == lastWord; + + core::Extremes extremes; + getWordExtremes (word, &extremes); + + // Minimum: between two *possible* breaks (or at the end). + // TODO This is redundant to getExtremesImpl(). + if (word->badnessAndPenalty.lineCanBeBroken () || atLastWord) { + parMin += extremes.minWidth + word->hyphenWidth; + *maxOfMinWidth = misc::max (*maxOfMinWidth, parMin); + parMin = 0; + } else + // Shrinkability could be considered, but really does not play a + // role. + parMin += extremes.minWidth + word->origSpace; + + //printf ("[%p] after word: ", this); + //printWord (word); + //printf ("\n"); + + //printf ("[%p] (%d / %d) => parMin = %d, maxOfMinWidth = %d\n", + // this, extremes.minWidth, extremes.maxWidth, parMin, + // *maxOfMinWidth); + + *sumOfMaxWidth += (extremes.maxWidth + word->origSpace); + // Notice that the last space is added. See also: Line::parMax. + } +} + +/* + * This method is called in two cases: (i) when a word is added + * (ii) when a page has to be (partially) rewrapped. It does word wrap, + * and adds new lines if necessary. + */ +void Textblock::wordWrap (int wordIndex, bool wrapAll) +{ + PRINTF ("[%p] WORD_WRAP (%d, %s)\n", + this, wordIndex, wrapAll ? "true" : "false"); + + Word *word; + //core::Extremes wordExtremes; + + if (!wrapAll) + removeTemporaryLines (); + + initLine1Offset (wordIndex); + + word = words->getRef (wordIndex); + word->effSpace = word->origSpace; + + accumulateWordData (wordIndex); + + bool newLine; + do { + bool tempNewLine = false; + int firstIndex = + lines->size() == 0 ? 0 : lines->getLastRef()->lastWord + 1; + int searchUntil; + + if (wrapAll && wordIndex >= firstIndex && wordIndex == words->size() -1) { + newLine = true; + searchUntil = wordIndex; + tempNewLine = true; + PRINTF (" NEW LINE: last word\n"); + } else if (wordIndex >= firstIndex && + word->badnessAndPenalty.lineMustBeBroken ()) { + newLine = true; + searchUntil = wordIndex; + PRINTF (" NEW LINE: forced break\n"); + } else if (wordIndex > firstIndex && + word->badnessAndPenalty.lineTooTight () && + words->getRef(wordIndex- 1) + ->badnessAndPenalty.lineCanBeBroken ()) { + // TODO Comment the last condition (also below where the minimun is + // searched for) + newLine = true; + searchUntil = wordIndex - 1; + PRINTF (" NEW LINE: line too tight\n"); + } else + newLine = false; + + if(!newLine && !wrapAll) + // No new line is added. "mustQueueResize" must, + // nevertheless, be set, so that flush() will call + // queueResize(), and later sizeRequestImpl() is called, + // which then calls showMissingLines(), which eventually + // calls this method again, with wrapAll == true, so that + // newLine is calculated as "true". + mustQueueResize = true; + + if(newLine) { + accumulateWordData (wordIndex); + int wordIndexEnd = wordIndex; + + bool lineAdded; + do { + PRINTF (" searching from %d to %d\n", firstIndex, searchUntil); + + int breakPos = -1; + for (int i = firstIndex; i <= searchUntil; i++) { + Word *w = words->getRef(i); + + //printf (" %d (of %d): ", i, words->size ()); + //printWord (w); + //printf ("\n"); + + // TODO: is this condition needed: + // if(w->badnessAndPenalty.lineCanBeBroken ()) ? + + if (breakPos == -1 || + w->badnessAndPenalty.compareTo + (&words->getRef(breakPos)->badnessAndPenalty) <= 0) + // "<=" instead of "<" in the next lines tends to result in + // more words per line -- theoretically. Practically, the + // case "==" will never occur. + breakPos = i; + } + + PRINTF (" breakPos = %d\n", breakPos); + + if (wrapAll && searchUntil == words->size () - 1) { + // Since no break and no space is added, the last word + // will have a penalty of inf. Actually, it should be -inf, + // since it is the last word. However, since more words may + // follow, the penalty is not changesd, but here, the search + // is corrected (maybe only temporary). + Word *lastWord = words->getRef (searchUntil); + BadnessAndPenalty correctedBap = lastWord->badnessAndPenalty; + correctedBap.setPenaltyForceBreak (); + if (correctedBap.compareTo + (&words->getRef(breakPos)->badnessAndPenalty) <= 0) { + breakPos = searchUntil; + PRINTF (" corrected: breakPos = %d\n", breakPos); + } + } + + int hyphenatedWord = -1; + Word *word1 = words->getRef(breakPos); + PRINTF ("[%p] line (broken at word %d): ", this, breakPos); + //word1->badnessAndPenalty.print (); + PRINTF ("\n"); + + if (word1->badnessAndPenalty.lineTight () && + word1->canBeHyphenated && + word1->style->x_lang[0] && + word1->content.type == core::Content::TEXT && + Hyphenator::isHyphenationCandidate (word1->content.text)) + hyphenatedWord = breakPos; + + if (word1->badnessAndPenalty.lineLoose () && + breakPos + 1 < words->size ()) { + Word *word2 = words->getRef(breakPos + 1); + if (word2->canBeHyphenated && + word2->style->x_lang[0] && + word2->content.type == core::Content::TEXT && + Hyphenator::isHyphenationCandidate (word2->content.text)) + hyphenatedWord = breakPos + 1; + } + + PRINTF ("[%p] breakPos = %d, hyphenatedWord = %d\n", + this, breakPos, hyphenatedWord); + + if(hyphenatedWord == -1) { + addLine (firstIndex, breakPos, tempNewLine); + PRINTF ("[%p] new line %d (%s), from %d to %d\n", + this, lines->size() - 1, + tempNewLine ? "temporally" : "permanently", + firstIndex, breakPos); + lineAdded = true; + } else { + // TODO hyphenateWord() should return weather something has + // changed at all. So that a second run, with + // !word->canBeHyphenated, is unneccessary. + // TODO Update: for this, searchUntil == 0 should be checked. + PRINTF ("[%p] old searchUntil = %d ...\n", this, searchUntil); + int n = hyphenateWord (hyphenatedWord); + searchUntil += n; + if (hyphenatedWord >= wordIndex) + wordIndexEnd += n; + PRINTF ("[%p] -> new searchUntil = %d ...\n", this, searchUntil); + lineAdded = false; + + // update word pointer as hyphenateWord() can trigger a + // reorganization of the words structure + word = words->getRef (wordIndex); + } + + PRINTF ("[%p] accumulating again from %d to %d\n", + this, breakPos + 1, wordIndexEnd); + for(int i = breakPos + 1; i <= wordIndexEnd; i++) + accumulateWordData (i); + + } while(!lineAdded); + } + } while (newLine); +} + +int Textblock::hyphenateWord (int wordIndex) +{ + Word *hyphenatedWord = words->getRef(wordIndex); + char lang[3] = { hyphenatedWord->style->x_lang[0], + hyphenatedWord->style->x_lang[1], 0 }; + Hyphenator *hyphenator = + Hyphenator::getHyphenator (layout->getPlatform (), lang); + PRINTF ("[%p] considering to hyphenate word %d, '%s', in language '%s'\n", + this, wordIndex, words->getRef(wordIndex)->content.text, lang); + int numBreaks; + int *breakPos = + hyphenator->hyphenateWord (hyphenatedWord->content.text, &numBreaks); + + if (numBreaks > 0) { + Word origWord = *hyphenatedWord; + + core::Requisition wordSize[numBreaks + 1]; + calcTextSizes (origWord.content.text, strlen (origWord.content.text), + origWord.style, numBreaks, breakPos, wordSize); + + PRINTF ("[%p] %d words ...\n", this, words->size ()); + words->insert (wordIndex, numBreaks); + PRINTF ("[%p] ... => %d words\n", this, words->size ()); + + // Adjust anchor indexes. + for (int i = 0; i < anchors->size (); i++) { + Anchor *anchor = anchors->getRef (i); + if (anchor->wordIndex > wordIndex) + anchor->wordIndex += numBreaks; + } + + for (int i = 0; i < numBreaks + 1; i++) { + Word *w = words->getRef (wordIndex + i); + + fillWord (w, wordSize[i].width, wordSize[i].ascent, + wordSize[i].descent, false, origWord.style); + + // TODO There should be a method fillText0. + w->content.type = core::Content::TEXT; + + int start = (i == 0 ? 0 : breakPos[i - 1]); + int end = (i == numBreaks ? + strlen (origWord.content.text) : breakPos[i]); + w->content.text = + layout->textZone->strndup (origWord.content.text + start, + end - start); + PRINTF (" [%d] -> '%s'\n", wordIndex + i, w->content.text); + + // Note: there are numBreaks + 1 word parts. + if (i < numBreaks) { + // TODO There should be a method fillHyphen. + w->badnessAndPenalty.setPenalty (HYPHEN_BREAK); + w->hyphenWidth = + layout->textWidth (origWord.style->font, "\xc2\xad", 2); + PRINTF (" [%d] + hyphen\n", wordIndex + i); + } else { + if (origWord.content.space) { + fillSpace (w, origWord.spaceStyle); + PRINTF (" [%d] + space\n", wordIndex + i); + } else { + PRINTF (" [%d] + nothing\n", wordIndex + i); + } + } + + accumulateWordData (wordIndex + i); + } + + PRINTF (" finished\n"); + + //delete origword->content.text; TODO: Via textZone? + origWord.style->unref (); + origWord.spaceStyle->unref (); + + delete breakPos; + } else { + words->getRef(wordIndex)->canBeHyphenated = false; + } + + return numBreaks; +} + +void Textblock::accumulateWordForLine (int lineIndex, int wordIndex) +{ + Line *line = lines->getRef (lineIndex); + Word *word = words->getRef (wordIndex); + + PRINTF (" %d + %d / %d + %d\n", line->boxAscent, line->boxDescent, + word->size.ascent, word->size.descent); + + line->boxAscent = misc::max (line->boxAscent, word->size.ascent); + line->boxDescent = misc::max (line->boxDescent, word->size.descent); + + int len = word->style->font->ascent; + if (word->style->valign == core::style::VALIGN_SUPER) + len += len / 2; + line->contentAscent = misc::max (line->contentAscent, len); + + len = word->style->font->descent; + if (word->style->valign == core::style::VALIGN_SUB) + len += word->style->font->ascent / 3; + line->contentDescent = misc::max (line->contentDescent, len); + + if (word->content.type == core::Content::WIDGET) { + int collapseMarginTop = 0; + + line->marginDescent = + misc::max (line->marginDescent, + word->size.descent + + word->content.widget->getStyle()->margin.bottom); + + if (lines->size () == 1 && + word->content.widget->blockLevel () && + getStyle ()->borderWidth.top == 0 && + getStyle ()->padding.top == 0) { + // collapse top margins of parent element and its first child + // see: http://www.w3.org/TR/CSS21/box.html#collapsing-margins + collapseMarginTop = getStyle ()->margin.top; + } + + line->boxAscent = + misc::max (line->boxAscent, + word->size.ascent, + word->size.ascent + + word->content.widget->getStyle()->margin.top + - collapseMarginTop); + + word->content.widget->parentRef = lineIndex; + } else { + line->marginDescent = + misc::max (line->marginDescent, line->boxDescent); + + if (word->content.type == core::Content::BREAK) + line->breakSpace = + misc::max (word->content.breakSpace, + line->marginDescent - line->boxDescent, + line->breakSpace); + } +} + +void Textblock::accumulateWordData (int wordIndex) +{ + // Typically, the word in question is in the last line; in any case + // quite at the end of the text, so that linear search is actually + // the fastest option. + int lineIndex = lines->size (); + while (lineIndex > 0 && wordIndex <= lines->getRef(lineIndex - 1)->lastWord) + lineIndex--; + + int firstWordOfLine; + if (lineIndex == 0) + firstWordOfLine = 0; + else + firstWordOfLine = lines->getRef(lineIndex - 1)->lastWord + 1; + + Word *word = words->getRef (wordIndex); + PRINTF ("[%p] ACCUMULATE_WORD_DATA (%d); lineIndex = %d: ...\n", + this, wordIndex, lineIndex); + + int availWidth = calcAvailWidth (lineIndex); + + PRINTF (" (%s existing line %d starts with word %d)\n", + lineIndex < lines->size () ? "already" : "not yet", + lineIndex, firstWordOfLine); + + if (wordIndex == firstWordOfLine) { + // first word of the (not neccessarily yet existing) line + word->totalWidth = word->size.width + word->hyphenWidth; + word->totalStretchability = 0; + word->totalShrinkability = 0; + } else { + Word *prevWord = words->getRef (wordIndex - 1); + + word->totalWidth = prevWord->totalWidth + + prevWord->origSpace - prevWord->hyphenWidth + + word->size.width + word->hyphenWidth; + word->totalStretchability = + prevWord->totalStretchability + prevWord->stretchability; + word->totalShrinkability = + prevWord->totalShrinkability + prevWord->shrinkability; + } + + word->badnessAndPenalty.calcBadness (word->totalWidth, availWidth, + word->totalStretchability, + word->totalShrinkability); + + //printf (" => "); + //printWord (word); + //printf ("\n"); +} + +int Textblock::calcAvailWidth (int lineIndex) +{ + // BUG: This method must also include Line::boxLeft and Line::boxRight + // (introduced by floats), but since the recent changes in line breaking + // (together with hyphenation), this line is often not yet created, so + // these values cannot be determined. + + int availWidth = + this->availWidth - getStyle()->boxDiffWidth() - innerPadding; + if (limitTextWidth && + layout->getUsesViewport () && + availWidth > layout->getWidthViewport () - 10) + availWidth = layout->getWidthViewport () - 10; + if (lineIndex == 0) + availWidth -= line1OffsetEff; + + PRINTF ("[%p] CALC_AVAIL_WIDTH (%d of %d) => %d - %d - %d = %d\n", + this, lineIndex, lines->size(), this->availWidth, + getStyle()->boxDiffWidth(), innerPadding, availWidth); + + return availWidth; +} + +void Textblock::initLine1Offset (int wordIndex) +{ + Word *word = words->getRef (wordIndex); + + /* Test whether line1Offset can be used. */ + if (wordIndex == 0) { + if (ignoreLine1OffsetSometimes && + line1Offset + word->size.width > availWidth) { + line1OffsetEff = 0; + } else { + int indent = 0; + + if (word->content.type == core::Content::WIDGET && + word->content.widget->blockLevel() == true) { + /* don't use text-indent when nesting blocks */ + } else { + if (core::style::isPerLength(getStyle()->textIndent)) { + indent = misc::roundInt(this->availWidth * + core::style::perLengthVal (getStyle()->textIndent)); + } else { + indent = core::style::absLengthVal (getStyle()->textIndent); + } + } + line1OffsetEff = line1Offset + indent; + } + } +} + +/** + * Align the line. + * + * \todo Use block's style instead once paragraphs become proper blocks. + */ +void Textblock::alignLine (int lineIndex) +{ + Line *line = lines->getRef (lineIndex); + int availWidth = calcAvailWidth (lineIndex); + Word *firstWord = words->getRef (line->firstWord); + Word *lastWord = words->getRef (line->lastWord); + + for (int i = line->firstWord; i < line->lastWord; i++) + words->getRef(i)->origSpace = words->getRef(i)->effSpace; + + if (firstWord->content.type != core::Content::BREAK) { + switch (firstWord->style->textAlign) { + case core::style::TEXT_ALIGN_LEFT: + case core::style::TEXT_ALIGN_STRING: /* handled elsewhere (in the + * future)? */ + line->leftOffset = 0; + break; + case core::style::TEXT_ALIGN_JUSTIFY: /* see some lines above */ + line->leftOffset = 0; + if(lastWord->content.type != core::Content::BREAK && + line->lastWord != words->size () - 1) { + PRINTF (" justifyLine => %d vs. %d\n", + lastWord->totalWidth, availWidth); + justifyLine (line, availWidth - lastWord->totalWidth); + } + break; + case core::style::TEXT_ALIGN_RIGHT: + line->leftOffset = availWidth - lastWord->totalWidth; + break; + case core::style::TEXT_ALIGN_CENTER: + line->leftOffset = (availWidth - lastWord->totalWidth) / 2; + break; + default: + /* compiler happiness */ + line->leftOffset = 0; + } + + /* For large lines (images etc), which do not fit into the viewport: */ + if (line->leftOffset < 0) + line->leftOffset = 0; + } +} + +/** + * Rewrap the page from the line from which this is necessary. + * There are basically two times we'll want to do this: + * either when the viewport is resized, or when the size changes on one + * of the child widgets. + */ +void Textblock::rewrap () +{ + PRINTF ("[%p] REWRAP: wrapRef = %d\n", this, wrapRef); + + if (wrapRef == -1) + /* page does not have to be rewrapped */ + return; + + /* All lines up from wrapRef will be rebuild from the word list, + * the line list up from this position is rebuild. */ + lines->setSize (wrapRef); + nonTemporaryLines = misc::min (nonTemporaryLines, wrapRef); + + int firstWord; + if (lines->size () > 0) + firstWord = lines->getLastRef()->lastWord + 1; + else + firstWord = 0; + + for (int i = firstWord; i < words->size (); i++) { + Word *word = words->getRef (i); + + if (word->content.type == core::Content::WIDGET && + // ABC + word->content.widget->parentRef == + (1 | (dw::core::style::FLOAT_NONE << 1) | ((lines->size () - 1) << 3))) + calcWidgetSize (word->content.widget, &word->size); + + wordWrap (i, false); + + // wordWrap() may insert some new words; since NotSoSimpleVector + // is used for the words list, the internal structure may have + // changed, so getRef() must be called again. + word = words->getRef (i); + + if (word->content.type == core::Content::WIDGET) { + word->content.widget->parentRef = lines->size () - 1; + } + } + + /* Next time, the page will not have to be rewrapped. */ + wrapRef = -1; +} + +void Textblock::showMissingLines () +{ + int firstWordToWrap = lines->size () > 0 ? + lines->getRef(lines->size () - 1)->lastWord + 1 : 0; + PRINTF ("[%p] SHOW_MISSING_LINES: wrap from %d to %d\n", + this, firstWordToWrap, words->size () - 1); + for (int i = firstWordToWrap; i < words->size (); i++) + wordWrap (i, true); +} + + +void Textblock::removeTemporaryLines () +{ + lines->setSize (nonTemporaryLines); +} + +} // namespace dw diff --git a/dw/types.hh b/dw/types.hh index b5204331..abed38e6 100644 --- a/dw/types.hh +++ b/dw/types.hh @@ -195,6 +195,7 @@ struct Content REAL_CONTENT = 0xff ^ (START | END | FLOAT_REF), SELECTION_CONTENT = TEXT | WIDGET | BREAK }; + /* Content is embedded in struct Word therefore we * try to be space efficient. */ diff --git a/dw/widget.cc b/dw/widget.cc index cc73b32b..193c5aac 100644 --- a/dw/widget.cc +++ b/dw/widget.cc @@ -111,6 +111,7 @@ void Widget::setParent (Widget *parent) notifySetParent(); //DBG_OBJ_ASSOC (widget, parent); + //printf ("%p becomes a child of %p\n", this, parent); } void Widget::queueDrawArea (int x, int y, int width, int height) |