aboutsummaryrefslogtreecommitdiff
path: root/dw
diff options
context:
space:
mode:
authorSebastian Geerken <devnull@localhost>2012-09-14 11:34:19 +0200
committerSebastian Geerken <devnull@localhost>2012-09-14 11:34:19 +0200
commite4367b16dc131f34936bbb8fd09557b5aa5acbd7 (patch)
tree487a35941bf20bbc95a3d0b1dee420b00771f5b6 /dw
parentabd446c2eebe1f96764b6d95f1c6c61ae9bc40b2 (diff)
parent94e451ffa5ece79a3b071ee553650bf8bd869a46 (diff)
Merge of <http://hg.dillo.org/dillo>.
Diffstat (limited to 'dw')
-rw-r--r--dw/Makefile.am7
-rw-r--r--dw/fltkcomplexbutton.cc8
-rw-r--r--dw/fltkcomplexbutton.hh5
-rw-r--r--dw/fltkplatform.cc48
-rw-r--r--dw/fltkplatform.hh2
-rw-r--r--dw/fltkui.cc43
-rw-r--r--dw/fltkviewbase.cc26
-rw-r--r--dw/hyphenator.cc335
-rw-r--r--dw/hyphenator.hh48
-rw-r--r--dw/layout.hh15
-rw-r--r--dw/platform.hh10
-rw-r--r--dw/style.cc16
-rw-r--r--dw/style.hh13
-rw-r--r--dw/table.cc15
-rw-r--r--dw/tablecell.cc4
-rw-r--r--dw/tablecell.hh2
-rw-r--r--dw/textblock.cc1472
-rw-r--r--dw/textblock.hh244
-rw-r--r--dw/textblock_linebreaking.cc962
-rw-r--r--dw/types.hh1
-rw-r--r--dw/widget.cc1
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 &lt;= %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)&nbsp;A perfectly
+ * fitting line has a badness of 0. (ii)&nbsp;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)