diff options
135 files changed, 15409 insertions, 3103 deletions
@@ -27,6 +27,7 @@ ^test/dw-border-test$ ^test/dw-example$ ^test/dw-find-test$ +^test/dw-float-test$ ^test/dw-image-background$ ^test/dw-images-scaled$ ^test/dw-images-scaled2$ @@ -36,6 +37,7 @@ ^test/dw-links2$ ^test/dw-lists$ ^test/dw-resource-test$ +^test/dw-simple-container-test$ ^test/dw-table$ ^test/dw-table-aligned$ ^test/dw-ui-test$ @@ -6,6 +6,40 @@ Here we list changes that are relatively significant and/or visible to the user. For a history of changes in full detail, see our Mercurial repository at http://hg.dillo.org/dillo + +dillo-3.1 [not released yet] + ++- Floating elements. + - Redesign of widget sizes ("GROWS"). + - Applied CSS attribute 'width' to all elements, 'height' is now also + supported. + - Suport for 'min-width', 'max-width', 'min-height' and 'max-height'. + - Suport for 'display: inline-block'. + - <BUTTON>'s are now inline. + - Image aspect ratio is preserved when one dimension is specified by a + percentage value. + - New dillorc options 'adjust_min_width' and 'adjust_table_min_width'. + - Make building of test/ files more robust. + - Work on collapsing spaces: more cases supported. + - Fix crash that's possible searching for text while page still being built. + Patches: Sebastian Geerken ++- Image buffer/cache improvements. + Patch: Jorge Arellano Cid ++- Crosscompile/buildroot-friendly fltk-config test. + Patch: Peter Seiderer ++- HTML5 character references. + - Give images lower priority when requesting resources (responsiveness). + - Reuse of connections for HTTP (enable w/ http_persistent_conns in dillorc). + - Fix X11 icon name. + - Abort failed queries. + - In location bar, tend toward showing beginning of URL instead of end. + - Handle irix's version of vsnprintf(). + Patches: corvid ++- Avoid requesting background images if an ancestor has display:none. + Patch: Johannes Hofmann + +----------------------------------------------------------------------------- + dillo-3.0.4.1 [December 24, 2014] +- Avoid a corner case segfault when no search URL is found in dillorc. @@ -599,8 +599,7 @@ EXCLUDE = dlib \ dpi \ dpid \ dpip \ - src \ - test + src # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix filesystem feature) are excluded diff --git a/configure.ac b/configure.ac index 325411fc..04df681a 100644 --- a/configure.ac +++ b/configure.ac @@ -56,6 +56,14 @@ AC_TYPE_UINT16_T AC_TYPE_INT32_T AC_TYPE_UINT32_T +dnl ----------------------------------------------------------------- +dnl Check for absolute path of working directory. +dnl This is needed for RTFL, to get full the full paths of the source +dnl file names +dnl ----------------------------------------------------------------- +dnl +BASE_CUR_WORKING_DIR=`pwd` + dnl -------------------------------------- dnl Check whether to add /usr/local or not dnl (this is somewhat a religious problem) @@ -112,12 +120,13 @@ dnl ------------------------- dnl dnl For debugging and to be user friendly AC_MSG_CHECKING([FLTK 1.3]) -fltk_version="`fltk-config --version 2>/dev/null`" +AC_PATH_PROG(FLTK_CONFIG,fltk-config) +fltk_version="`$FLTK_CONFIG --version 2>/dev/null`" case $fltk_version in 1.3.*) AC_MSG_RESULT(yes) - LIBFLTK_CXXFLAGS=`fltk-config --cxxflags` - LIBFLTK_CFLAGS=`fltk-config --cflags` - LIBFLTK_LIBS=`fltk-config --ldflags`;; + LIBFLTK_CXXFLAGS=`$FLTK_CONFIG --cxxflags` + LIBFLTK_CFLAGS=`$FLTK_CONFIG --cflags` + LIBFLTK_LIBS=`$FLTK_CONFIG --ldflags`;; ?*) AC_MSG_RESULT(no) AC_MSG_ERROR(FLTK 1.3 required; version found: $fltk_version);; *) AC_MSG_RESULT(no) @@ -482,6 +491,7 @@ if eval "test x$GCC = xyes"; then CXXFLAGS="$CXXFLAGS -Wall -W -Wno-unused-parameter -fno-rtti -fno-exceptions" fi +AC_SUBST(BASE_CUR_WORKING_DIR) AC_SUBST(LIBJPEG_LIBS) AC_SUBST(LIBJPEG_LDFLAGS) AC_SUBST(LIBJPEG_CPPFLAGS) @@ -21,7 +21,7 @@ # Change this if you want background images to be loaded initially. # (While browsing, this can be changed from the tools/settings menu.) -#load_background_images=FALSE +#load_background_images=NO # Change this if you want to disable loading of CSS stylesheets initially. # (While browsing, this can be changed from the tools/settings menu.) @@ -76,10 +76,18 @@ # Show tooltip popups for HTML title attributes #show_tooltip=YES -# Set this to YES if you want to limit the word wrap width to the viewport -# width (may be useful for iPAQ) +# Set this to YES to limit the word wrap width to the viewport width #limit_text_width=NO +# If this is set to YES, all CSS size specifications are adjusted so that +# all contents can be displayed. (Except for tables, see below.) +#adjust_min_width=NO + +# If this is set to YES, all CSS size specifications for tables are +# adjusted so that all contents can be displayed. This is seperated +# from "adjust_min_width" (with another standard value) to mimic +# Firefox, which differenciates between tables and, say, textblocks. +#adjust_table_min_width=YES #------------------------------------------------------------------------- # PENALTIES @@ -161,6 +169,7 @@ search_url="dd DuckDuckGo http://duckduckgo.com/lite/?kp=-1&q=%s" search_url="Wikipedia http://www.wikipedia.org/w/index.php?search=%s&go=Go" search_url="Free Dictionary http://www.thefreedictionary.com/%s" +search_url="Startpage http://www.startpage.com/do/search?query=%s" search_url="Google http://www.google.com/search?ie=UTF-8&oe=UTF-8&q=%s" # If set, dillo will ask web servers to send pages in this language. @@ -175,6 +184,11 @@ search_url="Google http://www.google.com/search?ie=UTF-8&oe=UTF-8&q=%s" # Maximum number of simultaneous TCP connections to a single server or proxy. # http_max_conns=6 +# Change this if you want Dillo to reuse HTTP connections to a server or proxy +# when possible instead of making a new connection for every request for a new +# page/image/stylesheet. Currently, HTTPS connections are never reused. +#http_persistent_conns=NO + # Set the proxy information for http. # Note that the http_proxy environment variable overrides this setting. # WARNING: FTP and downloads plugins use wget. To use a proxy with them, diff --git a/dlib/dlib.c b/dlib/dlib.c index d60f1bc6..23534730 100644 --- a/dlib/dlib.c +++ b/dlib/dlib.c @@ -406,6 +406,17 @@ void dStr_vsprintfa (Dstr *ds, const char *format, va_list argp) va_copy(argp2, argp); n = vsnprintf(ds->str + ds->len, ds->sz - ds->len, format, argp2); va_end(argp2); +#if defined(__sgi) + /* IRIX does not conform to C99; if the entire argument did not fit + * into the buffer, n = buffer space used (minus 1 for terminator) + */ + if (n > -1 && n + 1 < ds->sz - ds->len) { + ds->len += n; /* Success! */ + break; + } else { + n_sz = ds->sz * 2; + } +#else if (n > -1 && n < ds->sz - ds->len) { ds->len += n; /* Success! */ break; @@ -414,6 +425,7 @@ void dStr_vsprintfa (Dstr *ds, const char *format, va_list argp) } else { /* old glibc */ n_sz = ds->sz * 2; } +#endif dStr_resize(ds, n_sz, (ds->len > 0) ? 1 : 0); } } diff --git a/doc/Makefile.am b/doc/Makefile.am index d48e3e73..8ade3d15 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -11,6 +11,8 @@ EXTRA_DIST = \ dw-widget-sizes.doc \ dw-changes.doc \ dw-images-and-backgrounds.doc \ + dw-dw-out-of-flow.doc \ + dw-dw-out-of-flow-2.doc \ fltk-problems.doc \ rounding-errors.doc \ uml-legend.doc \ @@ -27,6 +29,7 @@ EXTRA_DIST = \ dw-textblock-collapsing-spaces-1-2.png \ dw-textblock-collapsing-spaces-2-1.png \ dw-textblock-collapsing-spaces-2-2.png \ + dw-floats-01.png \ not-so-simple-container.png \ Cache.txt \ Cookies.txt \ diff --git a/doc/dw-example-screenshot.png b/doc/dw-example-screenshot.png Binary files differindex a4d37903..94f272ab 100644 --- a/doc/dw-example-screenshot.png +++ b/doc/dw-example-screenshot.png diff --git a/doc/dw-floats-01.png b/doc/dw-floats-01.png Binary files differnew file mode 100644 index 00000000..116d36b3 --- /dev/null +++ b/doc/dw-floats-01.png diff --git a/doc/dw-grows.doc b/doc/dw-grows.doc new file mode 100644 index 00000000..15150338 --- /dev/null +++ b/doc/dw-grows.doc @@ -0,0 +1,184 @@ +/** \page dw-grows GROWS - Grand Redesign Of Widget Sizes + +This paper describes (will describe) some design changes to +calculating widget sizes. Goals are: + +- Simplification of widget size calculation by the parent widget; + dw::Textblock::calcWidgetSize, dw::OutOfFlowMgr::ensureFloatSize + etc. should become simpler or perhaps even obsolete. + +- Making the implementation of some features possible: + + - *max-width*, *max-height*, *min-width*, *min-height*; + - correct aspect ratio for images with only one percentage size defined; + - *display: inline-block*; + - <button>. + + +A short sketch +============== + +**dw::core::Widget::sizeRequest and dw::core::Widget::getExtremes will +return final results.** The caller does not have to correct the size, +e. g. when percentages are defined. As an example, +dw::Textblock::calcWidgetSize has already become much simpler. + +**A new hierarchy, *container*:** Aside from dw::core::Widget::parent +and dw::core::Widget::generator, there is a third hierarchy +dw::core::Widget::container, which is (unlike *generator*) always a +direct ancestor, and represents what in CSS is called *containing +block*. Containers are important to define the "context size", which +is (not solely) used for percentage sizes. + +(There is another "containing block", dw::Textblock::containingBlock; +these may be consolidated some day.) + +**The process of size calculation is split between the widget itself +and its container:** + +- The container provides some abstract methods: + dw::core::Widget::getAvailWidthOfChild, + dw::core::Widget::getAvailHeightOfChild, + dw::core::Widget::correctRequisitionOfChild, and + dw::core::Widget::correctExtremesOfChild, which can be used in the + actual implementation of dw::core::Widget::sizeRequestImpl; + different containers with different ways how to arrange their + children will implement these methods in a different way. (Simple + example: the *available width* for children within a textblock is + the *available width* for the textblock itself, minus + margin/border/padding; on the other hand, it is completely different + for children of tables, for which a complex column width calculation + is used.) + +- The actual size calculation is, however, controlled by the widget + itself, which only *uses* these methods above. + +<div style="border: 2px solid #ffff00; margin-top: 0.5em; + margin-bottom: 0.5em; padding: 0.5em 1em; background-color: #ffffe0"> + <b>Update:</b> This is not fully correct; the parents are also involved + for calculating available widths and heights, at least when CSS 'width' + and 'height' are not set.</div> + +**Size hints are removed.** Instead, the container methods in the +previous paragraph are used. Changes of container sizes (especially +viewport the size) are handled in a different way. + +**Extremes are extended by intrinsic values.** In some cases (see +dw::Table::forceCalcCellSizes, case *minWidth* > *totalWidth*, for an +example) it is useful to know about minimal and maximal width of a +widget independent of CSS attributes. For this, dw::core::Extremes is +extended by: + +- dw::core::Extremes::minWidthIntrinsic and +- dw::core::Extremes::maxWidthIntrinsic. + +The rules for the calculation: + +1. If a widget has no children, it calculates *minWidthIntrinsic* and + *maxWidthIntrinsic* as those values not affected by CSS hints. + (dw::core::Widget::correctExtremes will not change these values.) +2. A widget must calculate *minWidthIntrinsic* and *maxWidthIntrinsic* + from *minWidthIntrinsic* and *maxWidthIntrinsic* of its children, + and *minWidth* and *maxWidth* from *minWidth* and *maxWidth* of its + children. +3. At the end, *minWidth* and *maxWidth* of a widget are corrected by + CSS attributes. (dw::core::Widget::correctExtremes will do this.) + +<div style="border: 2px solid #ffff00; margin-top: 0.5em; + margin-bottom: 0.5em; padding: 0.5em 1em; background-color: #ffffe0"> + <b>Notice:</b> Currently, dw::core::Widget::getExtremesImpl must + set all four members in dw::core::Extremes; this may change.</div> + + +Rules for *new* methods related to resizing +=========================================== + +- Of course, *sizeRequestImpl* may (should) call *correctRequisition*, + and *getExtremesImpl* may (should) call *correctExtremes*. + +- *sizeRequestImpl* (and *correctRequisition*) is allowed to call + *getAvailWidth* and *getAvailHeight* with *forceValue* set, but + *getExtremesImpl* (and *correctExtremes*) is allowed to call these + only with *forceValue* unset. + +- For this reason, *sizeRequestImpl* is indeed allowed to call + *getExtremes* (dw::Table does so), but the opposite + (*getExtremesImpl* calling *sizeRequest*) is not allowed + anymore. (Before GROWS, the standard implementation + dw::core::Widget::getExtremesImpl did so.) + +- Finally, *getAvailWidth* and *getAvailHeight* may call + *getExtremes*, if and only if *forceValue* is set. + +Here is a diagram showing all permitted dependencies: + +\dot +digraph G { + node [shape=record, fontname=Helvetica, fontsize=10, color="#c0c0c0"]; + edge [arrowhead="open", arrowtail="none", color="#404040"]; + + "sizeRequest[Impl]" -> "getExtremes[Impl]"; + "sizeRequest[Impl]" -> correctRequisition; + "getExtremes[Impl]" -> correctExtremes; + "sizeRequest[Impl]" -> "getAvail[Width|Height] (true)"; + "getExtremes[Impl]" -> "getAvail[Width|Height] (false)"; + correctRequisition -> "getAvail[Width|Height] (true)"; + correctExtremes -> "getAvail[Width|Height] (false)"; + "getAvail[Width|Height] (true)" -> "getExtremes[Impl]"; +} +\enddot + +Open issues +=========== + +**Do CSS size dimensions override intrinsic sizes in all cases?** If a +textblock needs at least, say, 100 pixels width so that the text can +be read, but has a specification "width: 50px", should half of the +text be invisible? Or should the width be corrected again to 100 +pixels? + +Currently, in the CSS size specification is honoured in all cases, +with one exception: see dw::Textblock::sizeRequestImpl and see +dw::Textblock::getExtremesImpl (the time when +dw::core::Widget::correctRequisition and +dw::core::Widget::correctExtremes, respectively, is called). + +*Not* honouring the CSS size specification in all cases could improve +readability in some cases, so this could depend on a user preference. + +**Update:** There is now a dillorc option <tt>adjust_min_width</tt>, +which is implemented for widths, but not heights (since it is based on +width extremes, but there are currently no height extremes). + +Another problem is that in most cases, there is no clippping, so that +contents may exceed the allocation of the widget, but redrawing is not +necessarily triggered. + +**Percentage values for margins and paddings, as well as negative +margins** are interesting applications, but have not been considered +yet. For negative margins, a new attribute +dw::core::Widget::extraSpace could solve the problem of widgets +sticking out of the allocation of parent. + +**Clarify percentage heights.** Search in widget.cc, and compare +section 10.5 ('height') of the CSS 2.1 specification to section 10.2 +('width'). + +**Fast queue resize does not work fully.** Example: run +*test/dw-simple-container-test* (dw_simple_container_test.cc), resize +(best maximize) the window and follow (e. g. by using RTFL) what +happens in consequence of dw::core::Layout::viewportSizeChanged. The +dw::SimpleContainer in the middle is not affected, so only the two +dw::Textblock's (at the top and at the bottom) call queueResize with +*fast = true*, and so get *NEEDS_RESIZE* set; but since it is not set +for the dw::SimpleContainer, *sizeRequest* is never called for the +bottom dw::Textblock. + +There does not seem to be a real case for this problem in dillo, since +all widgets which may contain other widgets (except +dw::SimpleContainer, which is not used outside tests) use the +available width and height (dw::core::Widget::usesAvailWidth and +dw::core::Widget::usesAvailHeight), and so are always affected by +viewport size changes. + +*/ diff --git a/doc/dw-out-of-flow-2.doc b/doc/dw-out-of-flow-2.doc new file mode 100644 index 00000000..d9d70565 --- /dev/null +++ b/doc/dw-out-of-flow-2.doc @@ -0,0 +1,69 @@ +/** \page dw-out-of-flow-2 Handling Elements Out Of Flow (notes 2) + +This has to be integrated into \ref dw-out-of-flow. + +Constructing a page with floats +------------------------------- +When a page is constructed (dw::Textblock::addWord), the *generating* +block tells the positions of floats (or, generally, widgets out of +flow) via dw::OutOfFlowMgr::tellPosition. This method considers +already collisions with other floats (only previous floats; floats +following this float are not considered); after the call, +dw::OutOfFlowMgr::getBorder will return correct values. + +dw::OutOfFlowMgr::tellPosition also checks for overlaps of this float +with other textblocks, except this textblock (the *generator*, which +is just constructed, so nothing has to be done). The fact that the +position of the float is the top, and so the float has only an +allocation below this position, leads to the effect that only the +textblocks following the generator are affected. (**Check:** Can the +search be limited here?) When a page is constructed, no textblocks +should be following the generating block, so no textblocks are +affected. + +**Todo:** Clarify details of line breaking (\ref dw-line-breaking). + +Float changes its size +---------------------- +The float itself will call queueResize, which will result in a call of +markSizeChange for the *containing* block, which will then call +dw::OutOfFlowMgr::markSizeChange. Here, the vloat is only *marked* as +dirty; the size will be calculated later (in +dw::OutOfFlowMgr::ensureFloatSize). + +This will trigger the resize idle function, so sizeRequest and +sizeAllocate for all floats and textblocks. In this run, +dw::OutOfFlowMgr::hasRelationChanged will return *true*, and so result +in a call of dw::Textblock::borderChanged, and trigger a second run of +the resize idle function, dealing correctly with the new size. + +(This case is handles in a not perfectly optimal way, since two runs +of the resize idle function are neccessary; but size changes of floats +is not a very common case. + +When a page is constructed (see above), a changing size of a float +currently constructed typically only affects the most bottom +textblock; the other textblocks are not covered by this float.) + +**Error:** In this case, new collisions are not yet considered. + + +Changing the width of the page +------------------------------ + +When the page width is changed, this will result in a reconstruction +of the page; see *Constructing a page with floats*. Anyway, checking +for overlaps will play a more important role. This is handled in an +optimal way by dw::OutOfFlowMgr::hasRelationChanged. + +**Check:** Are "cascades" avoided, like this: + +1. All textblocks are constructed. A float in textblock 1 overlaps + with textblock 2, so dw::Textblock::borderChanged is called for + textblock 2. +2. In another resize idle run, textblock 2 is constructed again. A + float in textblock 2 overlaps with textblock 3, so that + dw::Textblock::borderChanged is called for textblock 3. +3. Etc. + +*/
\ No newline at end of file diff --git a/doc/dw-out-of-flow.doc b/doc/dw-out-of-flow.doc new file mode 100644 index 00000000..ea4a52bc --- /dev/null +++ b/doc/dw-out-of-flow.doc @@ -0,0 +1,214 @@ +/** \page dw-out-of-flow Handling Elements Out Of Flow + + +<div style="border: 2px solid #ffff00; margin-bottom: 0.5em; +padding: 0.5em 1em; background-color: #ffffe0"><b>Info:</b> +Should be incorporated into dw::Textblock.</div> + +Introduction +============ + +This texts deals with both floats and absolute positions, which have +in common that there is a distinction between generating block and +containing block (we are here using the same notation as in the +CSS 2 specification). Consider this snippet (regarding floats): + + + <ul> + <li>Some text.</li> + <li> + <div style="float:right; width=50%">Some longer text, so + that the effect described in this passage can be + demonstrated. + </div> + Some more and longer text.</li> + <li>Final text. Plus some more to demonstrate how text flows + around the float on the right side.</li> + </ul> + +which may be rendered like this + +\image html dw-floats-01.png + +The float (the DIV section, yellow in the image) is defined +("generated") within the list item (blue), so, in CSS 2 terms, the +list item is the generating block of the float. However, as the image +shows, the float is not contained by the list item, but another block, +several levels above (not shown here). In terms of ::dw, this means +that the dw::Textblock representing the float cannot be a child of the +dw::Textblock representing the generating block, the list item, since +the allocation of a child widget must be within the allocation of the +parent widget. Instead, to each dw::Textblock, another dw::Textblock +is assigned as the containing box. + +(Notice also that other text blocks must regard floats to calculate +their borders, and so their size. In this example, the following list +item (green) must consider the position of the float. This is +discussed in detail in the next section.) + +Both in this text and the code, generating and containing block are +abbreviated with **GB** and **CB**, respectively. + + +Implementation overview +======================= + +Widget level +------------ +The terms *generating block* and *containing block* have been raised +to a higher level, the one of dw::core::Widget, and are here called +*generating widget* and *containing widget*. To represent the +distinction, the type of dw::core::Content has been split into three +parts: + +- If a widget is out of flow, the generating widget keeps a reference + with the type dw::core::Content::WIDGET_OOF_REF, while the + containing block refers to it as dw::core::Content::WIDGET_OOF_CONT. +- For widgets within flow, dw::core::Content::WIDGET_IN_FLOW is used. + +Notice that in the first case, there are two pieces of content +referring to the same widget. + +An application of this distinction is iterators. [TODO: more. And +still missing: DeepIterator may need the generating parent widget in +some cases.] + + +Textblock level +--------------- +Both dw::Textblock::notifySetAsTopLevel and +dw::Textblock::notifySetParent set the member +dw::Textblock::containingBlock appropriately, (according to rules +which should be defined in this document). + +Handling widgets out of flow is partly the task of the new class +dw::OutOfFlowMgr, which is stored by dw::Textblock::outOfFlowMgr, but +only for containing blocks. Generating blocks should refer to +*containingBlock->outOfFlowMgr*. (Perhaps dw::OutOfFlowMgr may become +independent of dw::Textblock.) + +dw::Textblock::addWidget is extended, so that floats and absolutely +positioned elements can be added. Notice that not *this* widget, but +the containing block becomes the parent of the newly added child, if +it is out of flow. dw::Textblock::addWidget decides this by calling +dw::OutOfFlowMgr::isOutOfFlow. (See new content types above.) + +dw::core::Widget::parentRef has become a new representation. Before, +it represented the line numer. Now (least signifant bit left): + + +---+ - - - +---+---+- - - - - -+---+---+---+---+ + | line number | 0 | + +---+ - - - +---+---+- - - - - -+---+---+---+---+ + + +---+ - - - +---+---+- - - - - -+---+---+---+---+ + | left float index | 0 | 0 | 1 | + +---+ - - - +---+---+- - - - - -+---+---+---+---+ + + +---+ - - - +---+---+- - - - - -+---+---+---+---+ + | right float index | 1 | 0 | 1 | + +---+ - - - +---+---+- - - - - -+---+---+---+---+ + + +---+ - - - +---+---+- - - - - -+---+---+---+---+ + | absolutely positioned index | 1 | 1 | + +---+ - - - +---+---+- - - - - -+---+---+---+---+ + +Details are hidden by static inline methods of dw::OutOfFlowMgr. + + +The sizeRequest/sizeAllocate problem +======================================== + +*See also:* \ref dw-widget-sizes, especially the section *Rules for +Methods Related to Resizing*. + +The size/position model of ::dw consists mainly of the following two +steps: + +1. First, the size of the toplevel widget is calculated. Size + calculation typically depends on the sizes of the widgets, which + are calculated recursively, but not more. +2. After this, the toplevel widget is allocated at position (0, 0), + with the previosly calculated size. Each widget must allocate its + children; here, the condition for the toplevel widget (allocated + size equals requested size) is not necessary; instead, each widget + may be allocated at every size. + +Especially for floats, this model becomes a bit difficult, for reasons +described below. For the solutions, much is centralized at the level +of the containing block, which delegates most to an instance of +dw::OutOfFlowMgr (details below). + +**The size of a widget depends on the size not only of the children.** +In the example above, the last list item (green, following the +generating list item) must know the size of the the float (which is +not a child or, generally, descendant) to determine the borders, which +is done in dw::Textblock::sizeRequestImpl. + +For this, the size model has been extended (see \ref dw-widget-sizes, +section *Rules for Methods Related to Resizing*): *sizeRequest* can be +called within *sizeRequestImpl* for other widgets that children (with +some caution). Namely, dw::Textblock::sizeRequestImpl calls +dw::core::Widget::sizeRequest for the float, via +dw::OutOfFlowMgr::getBorder and dw::OutOfFlowMgr::ensureFloatSize. + +**The size of a widget depends on the allocation of another widget.** +In the example above, both list items (blue and green) must know the +position of the float widget, within dw::Textblock::sizeRequestImpl, +to calculate the borders. The position, however, is stored in the +allocation, which is typically calculated later. + +Here, two cases must be distinguished. The position of a float is +always **relative to its generating block**, so for calculating the +borders for the generating block, the allocation needs not to be +know. For other textblocks, it needs to be known, so the calculation +of the borders will ignore floats generated by other textblocks, until +all widgets are allocated. The latter will call (when neccessary) +dw::core::Widget::queueResize, so that all border calculations are +repeated. See below (*hasRelationChanged*) for details. + +Generally, this pattern (distinguishing between GB and CB) can be +found everywhere in dw::OutOfFlowMgr. + +For details see: + +- dw::OutOfFlowMgr::getLeftBorder, dw::OutOfFlowMgr::getRightBorder, + dw::OutOfFlowMgr::getBorder (called by the first two), and + especially, dw::OutOfFlowMgr::getFloatsListForTextblock (called by + the latter), where these three cases are distinguished; +- dw::OutOfFlowMgr::sizeAllocateStart, + dw::OutOfFlowMgr::sizeAllocateEnd which are called by the containing + block. + +(This could be solved in a more simple, elegant way, when +*sizeRequest* would depend on the position. This is, however, only a +vague idea, perhaps not even feasible, and for which there are no +concrete plans, certainly not in \ref dw-grows.) + + +Implementation details +====================== + +- CB and GB lists (general pattern) (see previous section) +- binary search; different search criteria, how they accord +- lastLeftTBIndex, lastRightTBIndex etc. +- limitiation of search; extIndex etc. + + +How *hasRelationChanged* works +============================== + +... + + +Integration of line breaking and floats +======================================= + +(Positioning of floats, loop, recent works.) + + +Absolute and fixed positiones +============================= + +See <http://flpsed.org/hgweb/dillo_grows>. + +*/
\ No newline at end of file diff --git a/doc/dw-size-of-widget.png b/doc/dw-size-of-widget.png Binary files differindex eda93ee1..dbdbe0c4 100644 --- a/doc/dw-size-of-widget.png +++ b/doc/dw-size-of-widget.png diff --git a/doc/dw-style-box-model.png b/doc/dw-style-box-model.png Binary files differindex aa65ecb7..bf2fb1f1 100644 --- a/doc/dw-style-box-model.png +++ b/doc/dw-style-box-model.png diff --git a/doc/dw-style-length-absolute.png b/doc/dw-style-length-absolute.png Binary files differindex 6b4d8389..9ea28cad 100644 --- a/doc/dw-style-length-absolute.png +++ b/doc/dw-style-length-absolute.png diff --git a/doc/dw-style-length-percentage.png b/doc/dw-style-length-percentage.png Binary files differindex 15b36958..b1ad79c9 100644 --- a/doc/dw-style-length-percentage.png +++ b/doc/dw-style-length-percentage.png diff --git a/doc/dw-style-length-relative.png b/doc/dw-style-length-relative.png Binary files differindex d54d99b5..ee79b1a9 100644 --- a/doc/dw-style-length-relative.png +++ b/doc/dw-style-length-relative.png diff --git a/doc/dw-widget-sizes.doc b/doc/dw-widget-sizes.doc index 419a4a73..a82d3b99 100644 --- a/doc/dw-widget-sizes.doc +++ b/doc/dw-widget-sizes.doc @@ -1,22 +1,24 @@ /** \page dw-widget-sizes Sizes of Dillo Widgets -<h2>Allocation</h2> +<div style="border: 2px solid #ff4040; margin-bottom: 0.5em; +padding: 0.5em 1em; background-color: #fff0f0"><b>Info:</b> +Not up to date, see \ref dw-grows.</div> + +Allocation +========== Each widget has an \em allocation at a given time, this includes -<ul> -<li> the position (\em x, \em y) relative to the upper left corner of the - canvas, and -<li> the size (\em width, \em ascent, \em descent). -</ul> +- the position (\em x, \em y) relative to the upper left corner of the + canvas, and +- the size (\em width, \em ascent, \em descent). The \em canvas is the whole area available for the widgets, in most -cases, only a part is seen in a viewport. The allocation of the toplevel widget is exactly the allocation of the canvas, i.e. +cases, only a part is seen in a viewport. The allocation of the +toplevel widget is exactly the allocation of the canvas, i.e. -<ul> -<li> the position of the toplevel widget is always (0, 0), and -<li> the canvas size is defined by the size of the toplevel widget. -</ul> +- the position of the toplevel widget is always (0, 0), and +- the canvas size is defined by the size of the toplevel widget. The size of a widget is not simply defined by the width and the height, instead, widgets may have a base line, and so are vertically @@ -31,13 +33,11 @@ defined by the limits of the C++ type \em int. In the example in the image, the widget has the following allocation: -<ul> -<li>\em x = 50 -<li>\em y = 50 -<li>\em width = 150 -<li>\em ascent = 150 -<li>\em descent = 100 -</ul> +- \em x = 50 +- \em y = 50 +- \em width = 150 +- \em ascent = 150 +- \em descent = 100 The current allocation of a widget is hold in dw::core::Widget::allocation. It can be set from outside by @@ -54,7 +54,9 @@ appropriate child allocations. dw::core::Widget::allocation should not be changed here, this is already done in dw::core::Widget::sizeAllocate. -<h2>Requisitions</h2> + +Requisitions +============ A widget may prefer a given size for the allocation. This size, the \em requisition, should be returned by the method @@ -62,14 +64,12 @@ dw::core::Widget::sizeRequestImpl. In the simplest case, this is independent of the context, e.g. for an image. dw::Image::sizeRequestImpl returns the following size: -<ul> -<li> If no buffer has yet been assigned (see dw::Image for more details), - the size necessary for the alternative text is returned. If no - alternative text has been set, zero is returned. +- If no buffer has yet been assigned (see dw::Image for more details), + the size necessary for the alternative text is returned. If no + alternative text has been set, zero is returned. -<li> If a buffer has been assigned (by dw::Image::setBuffer), the root - size is returned (i.e. the original size of the image to display). -</ul> +- If a buffer has been assigned (by dw::Image::setBuffer), the root + size is returned (i.e. the original size of the image to display). This is a bit simplified, dw::Image::sizeRequestImpl should also deal with margins, borders and paddings, see dw::core::style. @@ -87,64 +87,49 @@ widget), may, but also may not consider the requisition. Instead, a widget must deal with any allocation. (For example, dw::Image scales the image buffer when allocated at another size.) -<h2>Size Hints</h2> - -Some widgets do not have an inherent size, but depend on the context, -e.g. the viewport size. These widgets should adhere to <i>size hints</i>, -i.e. implement the methods dw::core::Widget::setWidth, -dw::core::Widget::setAscent and dw::core::Widget::setDescent. The values -passed to the callees are - -<ul> -<li> the viewport size (ascent is the heigt here, while descent is 0) for - the toplevel widget, and -<li> determined by the parent for its child widgets. -</ul> -Generally, the values should define the available space for the -widget. +Size Hints +========== -A widget, which depends on size hints, should call -dw::core::Widget::queueResize, when apropriate. +<div style="border: 2px solid #ff4040; margin-bottom: 0.5em; +padding: 0.5em 1em; background-color: #fff0f0"><b>Info:</b> +Size hints have been removed, see \ref dw-grows.</div> -\todo There should be a definition of "available space". -<h2>Width Extremes</h2> +Width Extremes +============== dw::Table uses width extremes for fast calculation of column widths. The structure dw::core::Extremes represents the minimal and maximal width of a widget, as defined by: -<ul> -<li> the minimal width is the smallest width, at which a widget can still - display contents, and -<li> the maximal width is the largest width, above which increasing the width - does not make any sense. -</ul> +- the minimal width is the smallest width, at which a widget can still + display contents, and +- the maximal width is the largest width, above which increasing the + width- does not make any sense. Especially the latter is vaguely defined, here are some examples: -<ul> -<li> For those widgets, which do not depend on size hints, the minimal and - the maximal width is the inherent width (the one returned by - dw::core::Widget::sizeRequest). +- For those widgets, which do not depend on size hints, the minimal + and the maximal width is the inherent width (the one returned by + dw::core::Widget::sizeRequest). -<li> For a textblock, the minimal width is the width of the widest - (unbreakable) word, the maximal width is the width of the total - paragraph (stretching a paragraph further would only waste space). - Actually, the implementation of dw::Textblock::getExtremesImpl is - a bit more complex. +- For a textblock, the minimal width is the width of the widest + (unbreakable) word, the maximal width is the width of the total + paragraph (stretching a paragraph further would only waste space). + Actually, the implementation of dw::Textblock::getExtremesImpl is a + bit more complex. -<li> dw::Table is an example, where the width extremes are calculated - from the width extremes of the children. -</ul> +- dw::Table is an example, where the width extremes are calculated + from the width extremes of the children. Handling width extremes is similar to handling requisitions, a widget must implement dw::core::Widget::getExtremesImpl, but a caller will use dw::core::Widget::getExtremes. -<h2>Resizing</h2> +Resizing +======== When the widget changes its size (requisition), it should call dw::core::Widget::queueResize. The next call of @@ -160,27 +145,133 @@ done before. In this case, a widget must exactly know the reasons, why a call of dw::core::Widget::sizeRequestImpl is necessary. To make use of this, a widget must implement the following: -<ol> -<li> There is a member dw::core::Widget::parentRef, which is - totally under control of the parent widget (and so sometimes not - used at all). It is necessary to define how parentRef is used - by a specific parent widget, and it has to be set to the correct - value whenever necessary. - -<li> The widget must implement dw::core::Widget::markSizeChange and +1. There is a member dw::core::Widget::parentRef, which is totally + under control of the parent widget (and so sometimes not used at + all). It is necessary to define how parentRef is used by a specific + parent widget, and it has to be set to the correct value whenever + necessary. +2. The widget must implement dw::core::Widget::markSizeChange and dw::core::Widget::markExtremesChange, these methods are called in two cases: - - <ol> - <li> directly after dw::core::Widget::queueResize, with the argument - ref was passed to dw::core::Widget::queueResize, and - <li> if a child widget has called dw::core::Widget::queueResize, - with the value of the parent_ref member of this child. - </ol> -</ol> + 1. directly after dw::core::Widget::queueResize, with the + argument ref was passed to dw::core::Widget::queueResize, + and + 2. if a child widget has called dw::core::Widget::queueResize, + with the value of the parent_ref member of this child. This way, a widget can exactly keep track on size changes, and so implement resizing in a faster way. A good example on how to use this is dw::Textblock. + +Rules for Methods Related to Resizing +===================================== + +Which method can be called, when the call of another method is not +finished? These rules are important in two circumstances: + +1. To know which method can be called, and, especially, which methods + *must not* be called, within the implementation of + *sizeRequestImpl* (called by *sizeRequest*), *markSizeChange*, and + *markExtremesChange* (the latter two are called by *queueResize*). +2. On the other hand, to make sure that the calls, which are allowed, + are handled correctly, especially in implementations of + *sizeRequestImpl*, *markSizeChange*, *markExtremesChange* + +Generally, the rules defined below are, in case of doubt, rather +strict; when changing the rules, loosening is simpler than to tighten +them, since this will make it neccessary to review old code for calls +previously allowed but now forbidden. + +Short recap: + +- *QueueResize* directly calls *markSizeChange* and + *markExtremesChanges*, and queues an idle function for the actual + resizing (dw::core::Layout::resizeIdle). (The idle function is + called some time after *queueResize* is finished.) +- The resize idle function first calls *sizeRequest*, then + *sizeAllocate*, for the toplevel widget. + +In the following table, the rules are defined in detail. "Within call +of ..." includes all methods called from the original method: the +first row (*queueResize*) defines also the rules for +*markExtremesChanges* and *markExtremesChanges*, and in the second row +(*sizeAllocate*), even *sizeRequest* has to be considered. + +<div style="border: 2px solid #ff4040; margin-bottom: 0.5em; +padding: 0.5em 1em; background-color: #fff0f0"><b>Info:</b> +Not up to date: *queueResize* can now be called recursively (so to +speak). See code there.</div> + +<table> + <tr> + <th>Within call of ... ↓ + <th>... is call allowed of ... ? → + <th>queueResize + <th>sizeAllocate + <th>sizeRequest + <th>getExtremes + <tr> + <th colspan=2>queueResize + <td>No + <td>No<sup>1</sup> + <td>No<sup>1</sup> + <td>No<sup>1</sup> + <tr> + <th colspan=2>sizeAllocate + <td>Yes + <td>Only for children<sup>2</sup> + <td>Yes(?) + <td>Yes(?) + <tr> + <th colspan=2>sizeRequest + <td>Yes<sup>3</sup> + <td>No + <td>Limited<sup>4</sup> + <td>Limited<sup>4</sup> + <tr> + <th colspan=2>getExtremes + <td>Yes<sup>3</sup> + <td>No + <td>Limited<sup>4</sup> + <td>Limited<sup>4</sup> + <tr> + <td colspan=6><sup>1</sup>) Otherwise, since these other methods +may be call *queueResize*, the limitation that *queueResize* must not +call *queueResize* can be violated. + +<sup>2</sup>) Could perhaps be loosened as for *sizeRequest* and +*getExtremes*, but there is probably no need. + +<sup>3</sup>) Therefore the distinction between *RESIZE_QUEUED* and +*NEEDS_RESIZE*, and *EXTREMES_QUEUED* and *EXTREMES_CHANGED*, +respectively. + +<sup>4</sup>) Calls only for children are safe. In other cases, you +take a large responsibility to prevent endless recursions by +(typically indirectly) calling *sizeRequest* / *getExtremes* for +direct ancestors. +</table> + +Furthermore, *sizeAllocate* can only be called within a call of +dw::core::Layout::resizeIdleId, so (if you do not touch dw::core) do +not call it outside of *sizeAllocateImpl*. The other methods can be +called outsize; e. g. *sizeRequest* is called in +dw::Textblock::addWidget. + +To avoid painful debugging, there are some tests for the cases that +one method call is strictly forbidden while another method is called. + +This could be done furthermore: + +- The tests could be refined. +- Is it possible to define exacter rules, along with a proof that no + problems (like endless recursion) can occur? + + +See also +======== + +- \ref dw-grows + */ diff --git a/doc/not-so-simple-container.png b/doc/not-so-simple-container.png Binary files differindex 886b72a8..0af067b5 100644 --- a/doc/not-so-simple-container.png +++ b/doc/not-so-simple-container.png diff --git a/doc/user_help.html b/doc/user_help.html index 9de2056f..88218f70 100644 --- a/doc/user_help.html +++ b/doc/user_help.html @@ -314,7 +314,7 @@ <p> Example, in dillorc:<br> <blockquote> - <code>search_url="dd Duckduckgo http://duckduckgo.com/lite/?kp=-1&q=%s"</code> + <code>search_url="dd Duckduckgo http://duckduckgo.com/lite/?kp=-1&q=%s"</code> </blockquote> means you can reach the location bar (CTRL+L), then type: <blockquote> diff --git a/dpi/cookies.c b/dpi/cookies.c index 6c5e958e..b858bd53 100644 --- a/dpi/cookies.c +++ b/dpi/cookies.c @@ -1042,14 +1042,13 @@ static uint_t Cookies_internal_dots_required(const char *host) if (tld_len > 0) { /* These TLDs were chosen by examining the current publicsuffix list - * in February 2014 and picking out those where it was simplest for + * in October 2014 and picking out those where it was simplest for * them to describe the situation by beginning with a "*.[tld]" rule * or every rule was "[something].[tld]". */ - const char *const tlds[] = {"bd","bn","ck","cy","er","et","fj","fk", + const char *const tlds[] = {"bd","bn","ck","cy","er","fj","fk", "gu","il","jm","ke","kh","kw","mm","mz", - "ni","np","nz","pg","tr","uk","ye","za", - "zm","zw"}; + "ni","np","pg","ye","za","zm","zw"}; uint_t i, tld_num = sizeof(tlds) / sizeof(tlds[0]); for (i = 0; i < tld_num; i++) { diff --git a/dpi/downloads.cc b/dpi/downloads.cc index 70acaa8a..771098e5 100644 --- a/dpi/downloads.cc +++ b/dpi/downloads.cc @@ -68,7 +68,7 @@ protected: void draw(); public: ProgressBar(int x, int y, int w, int h, const char *lbl = 0); - void range(double min, double max, double step = 1) { + void range(double min, double max, double step = 1) { mMin = min; mMax = max; mStep = step; }; void step(double step) { mPresent += step; redraw(); }; @@ -346,10 +346,10 @@ static void File_info2html(ClientInfo *client, FileInfo *finfo, int n) sizeunits = "bytes"; } else if (finfo->size / 1024 <= 9999) { size = finfo->size / 1024 + (finfo->size % 1024 >= 1024 / 2); - sizeunits = "Kb"; + sizeunits = "KB"; } else { size = finfo->size / 1048576 + (finfo->size % 1048576 >= 1048576 / 2); - sizeunits = "Mb"; + sizeunits = "MB"; } /* we could note if it's a symlink... */ diff --git a/dpi/https.c b/dpi/https.c index e6d2b0e9..da75b9e8 100644 --- a/dpi/https.c +++ b/dpi/https.c @@ -156,9 +156,12 @@ static void yes_ssl_support(void) } } - /* Do not use the SSLv2 protocol. */ + /* SSL2 has been known to be insecure forever, disabling SSL3 is in response + * to POODLE, and disabling compression is in response to CRIME. + */ if (exit_error == 0){ - SSL_CTX_set_options(ssl_context, SSL_OP_NO_SSLv2); + SSL_CTX_set_options(ssl_context, + SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_COMPRESSION); } /*Set directory to load certificates from*/ @@ -188,10 +191,11 @@ static void yes_ssl_support(void) } if (exit_error == 0){ - /* Need to do the following if we want to deal with all - * possible ciphers + /* Don't want: eNULL, which has no encryption; aNULL, which has no + * authentication; LOW, which as of 2014 use 64 or 56-bit encryption; + * EXPORT40, which uses 40-bit encryption. */ - SSL_set_cipher_list(ssl_connection, "ALL"); + SSL_CTX_set_cipher_list(ssl_context, "ALL:!aNULL:!eNULL:!LOW:!EXPORT40"); /* Need to do this if we want to have the option of dealing * with self-signed certs diff --git a/dw/Makefile.am b/dw/Makefile.am index d0d56d2a..0b4b7bb4 100644 --- a/dw/Makefile.am +++ b/dw/Makefile.am @@ -1,7 +1,7 @@ AM_CPPFLAGS = \ -I$(top_srcdir) \ - -DDILLO_LIBDIR='"$(pkglibdir)/"' - + -DDILLO_LIBDIR='"$(pkglibdir)/"' \ + -DCUR_WORKING_DIR='"@BASE_CUR_WORKING_DIR@/dw"' noinst_LIBRARIES = \ libDw-core.a \ @@ -57,6 +57,8 @@ libDw_fltk_a_SOURCES = \ libDw_fltk_a_CXXFLAGS = @LIBFLTK_CXXFLAGS@ libDw_widgets_a_SOURCES = \ + alignedtablecell.cc \ + alignedtablecell.hh \ alignedtextblock.cc \ alignedtextblock.hh \ bullet.cc \ @@ -67,9 +69,14 @@ libDw_widgets_a_SOURCES = \ image.hh \ listitem.cc \ listitem.hh \ + outofflowmgr.cc \ + outofflowmgr.hh \ ruler.cc \ ruler.hh \ + simpletablecell.cc \ + simpletablecell.hh \ table.cc \ + table_iterator.cc \ table.hh \ tablecell.cc \ tablecell.hh \ diff --git a/dw/alignedtablecell.cc b/dw/alignedtablecell.cc new file mode 100644 index 00000000..633c4e68 --- /dev/null +++ b/dw/alignedtablecell.cc @@ -0,0 +1,197 @@ +/* + * Dillo Widget + * + * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + + +#include "alignedtablecell.hh" +#include "table.hh" +#include "tablecell.hh" +#include "../lout/debug.hh" +#include <stdio.h> + +namespace dw { + +int AlignedTableCell::CLASS_ID = -1; + +AlignedTableCell::AlignedTableCell (AlignedTableCell *ref, bool limitTextWidth): + AlignedTextblock (limitTextWidth) +{ + DBG_OBJ_CREATE ("dw::AlignedTableCell"); + registerName ("dw::AlignedTableCell", &CLASS_ID); + + /** \bug ignoreLine1OffsetSometimes does not work? */ + //ignoreLine1OffsetSometimes = true; + charWordIndex = -1; + setRefTextblock (ref); + setButtonSensitive(true); +} + +AlignedTableCell::~AlignedTableCell() +{ + DBG_OBJ_DELETE (); +} + + +bool AlignedTableCell::getAdjustMinWidth () +{ + return tablecell::getAdjustMinWidth (); +} + +bool AlignedTableCell::isBlockLevel () +{ + return tablecell::isBlockLevel (); +} + +int AlignedTableCell::getAvailWidthOfChild (Widget *child, bool forceValue) +{ + DBG_OBJ_ENTER ("resize", 0, "AlignedTableCell/getAvailWidthOfChild", + "%p, %s", child, forceValue ? "true" : "false"); + + int width = tablecell::correctAvailWidthOfChild + (this, child, Textblock::getAvailWidthOfChild (child, forceValue), + forceValue); + + DBG_OBJ_LEAVE (); + return width; +} + +int AlignedTableCell::getAvailHeightOfChild (Widget *child, bool forceValue) +{ + DBG_OBJ_ENTER ("resize", 0, "AlignedTableCell/getAvailHeightOfChild", + "%p, %s", child, forceValue ? "true" : "false"); + + int height = tablecell::correctAvailHeightOfChild + (this, child, Textblock::getAvailHeightOfChild (child, forceValue), + forceValue); + + DBG_OBJ_LEAVE (); + return height; +} + +void AlignedTableCell::correctRequisitionOfChild (Widget *child, + core::Requisition + *requisition, + void (*splitHeightFun) (int, + int*, + int*)) +{ + DBG_OBJ_ENTER ("resize", 0, "AlignedTableCell/correctRequisitionOfChild", + "%p, %d * (%d + %d), ...", child, requisition->width, + requisition->ascent, requisition->descent); + + AlignedTextblock::correctRequisitionOfChild (child, requisition, + splitHeightFun); + tablecell::correctCorrectedRequisitionOfChild (this, child, requisition, + splitHeightFun); + + DBG_OBJ_LEAVE (); +} + +void AlignedTableCell::correctExtremesOfChild (Widget *child, + core::Extremes *extremes) +{ + DBG_OBJ_ENTER ("resize", 0, "AlignedTableCell/correctExtremesOfChild", + "%p, %d (%d) / %d (%d)", + child, extremes->minWidth, extremes->minWidthIntrinsic, + extremes->maxWidth, extremes->maxWidthIntrinsic); + + AlignedTextblock::correctExtremesOfChild (child, extremes); + tablecell::correctCorrectedExtremesOfChild (this, child, extremes); + + DBG_OBJ_LEAVE (); +} + +int AlignedTableCell::applyPerWidth (int containerWidth, + core::style::Length perWidth) +{ + return tablecell::applyPerWidth (this, containerWidth, perWidth); +} + +int AlignedTableCell::applyPerHeight (int containerHeight, + core::style::Length perHeight) +{ + return tablecell::applyPerHeight (this, containerHeight, perHeight); +} + +int AlignedTableCell::wordWrap(int wordIndex, bool wrapAll) +{ + Textblock::Word *word; + const char *p; + + int ret = Textblock::wordWrap (wordIndex, wrapAll); + + if (charWordIndex == -1) { + word = words->getRef (wordIndex); + if (word->content.type == core::Content::TEXT) { + if ((p = strchr (word->content.text, + word->style->textAlignChar))) { + charWordIndex = wordIndex; + charWordPos = p - word->content.text + 1; + } else if (word->style->textAlignChar == ' ' && + word->content.space) { + charWordIndex = wordIndex + 1; + charWordPos = 0; + } + } + } + + if (wordIndex == charWordIndex) + updateValue (); + + return ret; +} + +int AlignedTableCell::getValue () +{ + Textblock::Word *word; + int i, wordIndex; + int w; + + if (charWordIndex == -1) + wordIndex = words->size () -1; + else + wordIndex = charWordIndex; + + w = 0; + for (i = 0; i < wordIndex; i++) { + word = words->getRef (i); + w += word->size.width + word->origSpace; + } + + if (charWordIndex == -1) { + if (words->size () > 0) { + word = words->getRef (words->size () - 1); + w += word->size.width; + } + } else { + word = words->getRef (charWordIndex); + w += layout->textWidth (word->style->font, word->content.text, + charWordPos); + } + + return w; +} + +void AlignedTableCell::setMaxValue (int maxValue, int value) +{ + line1Offset = maxValue - value; + queueResize (OutOfFlowMgr::createRefNormalFlow (0), true); +} + +} // namespace dw diff --git a/dw/alignedtablecell.hh b/dw/alignedtablecell.hh new file mode 100644 index 00000000..5ea606d7 --- /dev/null +++ b/dw/alignedtablecell.hh @@ -0,0 +1,44 @@ +#ifndef __DW_ALIGNEDTABLECELL_HH__ +#define __DW_ALIGNEDTABLECELL_HH__ + +#include "core.hh" +#include "alignedtextblock.hh" + +namespace dw { + +class AlignedTableCell: public AlignedTextblock +{ +private: + int charWordIndex, charWordPos; + +protected: + int getAvailWidthOfChild (Widget *child, bool forceValue); + int getAvailHeightOfChild (Widget *child, bool forceValue); + + void correctRequisitionOfChild (Widget *child, + core::Requisition *requisition, + void (*splitHeightFun) (int, int*, int*)); + void correctExtremesOfChild (Widget *child, core::Extremes *extremes); + + bool getAdjustMinWidth (); + + int wordWrap (int wordIndex, bool wrapAll); + + int getValue (); + void setMaxValue (int maxValue, int value); + +public: + static int CLASS_ID; + + AlignedTableCell(AlignedTableCell *ref, bool limitTextWidth); + ~AlignedTableCell(); + + int applyPerWidth (int containerWidth, core::style::Length perWidth); + int applyPerHeight (int containerHeight, core::style::Length perHeight); + + bool isBlockLevel (); +}; + +} // namespace dw + +#endif // __DW_ALIGNEDTABLECELL_HH__ diff --git a/dw/bullet.cc b/dw/bullet.cc index af7f5451..acaf81cc 100644 --- a/dw/bullet.cc +++ b/dw/bullet.cc @@ -27,6 +27,12 @@ namespace dw { Bullet::Bullet () { + DBG_OBJ_CREATE ("dw::Bullet"); +} + +Bullet::~Bullet () +{ + DBG_OBJ_DELETE (); } void Bullet::sizeRequestImpl (core::Requisition *requisition) @@ -36,6 +42,21 @@ void Bullet::sizeRequestImpl (core::Requisition *requisition) requisition->descent = 0; } +void Bullet::getExtremesImpl (core::Extremes *extremes) +{ + extremes->minWidth = extremes->maxWidth = + lout::misc::max (getStyle()->font->xHeight * 4 / 5, 1); + extremes->minWidthIntrinsic = extremes->minWidth; + extremes->maxWidthIntrinsic = extremes->maxWidth; +} + +void Bullet::containerSizeChangedForChildren () +{ + DBG_OBJ_ENTER0 ("resize", 0, "containerSizeChangedForChildren"); + // Nothing to do. + DBG_OBJ_LEAVE (); +} + void Bullet::draw (core::View *view, core::Rectangle *area) { int x, y, l; diff --git a/dw/bullet.hh b/dw/bullet.hh index 98854abb..004187cd 100644 --- a/dw/bullet.hh +++ b/dw/bullet.hh @@ -15,11 +15,14 @@ class Bullet: public core::Widget { protected: void sizeRequestImpl (core::Requisition *requisition); + void getExtremesImpl (core::Extremes *extremes); + void containerSizeChangedForChildren (); void draw (core::View *view, core::Rectangle *area); core::Iterator *iterator (core::Content::Type mask, bool atEnd); public: Bullet (); + ~Bullet (); }; } // namespace dw diff --git a/dw/findtext.cc b/dw/findtext.cc index cc57e991..94b963ea 100644 --- a/dw/findtext.cc +++ b/dw/findtext.cc @@ -96,7 +96,7 @@ FindtextState::Result FindtextState::search (const char *key, bool caseSens, if (iterator) delete iterator; - iterator = new CharIterator (widget); + iterator = new CharIterator (widget, true); if (backwards) { /* Go to end */ @@ -128,7 +128,7 @@ FindtextState::Result FindtextState::search (const char *key, bool caseSens, } else { // Nothing found anymore, reset the state for the next trial. delete iterator; - iterator = new CharIterator (widget); + iterator = new CharIterator (widget, true); if (backwards) { /* Go to end */ while (iterator->next ()) ; @@ -221,7 +221,7 @@ bool FindtextState::unhighlight () return false; } -bool FindtextState::search0 (bool backwards, bool firstTrial) +bool FindtextState::search0 (bool backwards, bool firstTrial) { if (iterator->getChar () == CharIterator::END) return false; diff --git a/dw/fltkimgbuf.cc b/dw/fltkimgbuf.cc index 26b46969..01bce455 100644 --- a/dw/fltkimgbuf.cc +++ b/dw/fltkimgbuf.cc @@ -80,7 +80,7 @@ FltkImgbuf::FltkImgbuf (Type type, int width, int height, double gamma) { DBG_OBJ_CREATE ("dw::fltk::FltkImgbuf"); - _MSG("FltkImgbuf: new root %p\n", this); + _MSG ("FltkImgbuf::FltkImgbuf: new root %p\n", this); init (type, width, height, gamma, NULL); } @@ -89,7 +89,7 @@ FltkImgbuf::FltkImgbuf (Type type, int width, int height, double gamma, { DBG_OBJ_CREATE ("dw::fltk::FltkImgbuf"); - _MSG("FltkImgbuf: new scaled %p, root is %p\n", this, root); + _MSG ("FltkImgbuf::FltkImgbuf: new scaled %p, root is %p\n", this, root); init (type, width, height, gamma, root); } @@ -135,8 +135,8 @@ void FltkImgbuf::init (Type type, int width, int height, double gamma, case RGB: bpp = 3; break; default: bpp = 1; break; } - _MSG("FltkImgbuf::init width=%d height=%d bpp=%d gamma=%g\n", - width, height, bpp, gamma); + _MSG("FltkImgbuf::init this=%p width=%d height=%d bpp=%d gamma=%g\n", + this, width, height, bpp, gamma); rawdata = new uchar[bpp * width * height]; // Set light-gray as interim background color. memset(rawdata, 222, width*height*bpp); @@ -163,6 +163,8 @@ void FltkImgbuf::init (Type type, int width, int height, double gamma, FltkImgbuf::~FltkImgbuf () { + _MSG ("FltkImgbuf::~FltkImgbuf\n"); + if (!isRoot()) root->detachScaledBuf (this); diff --git a/dw/fltkplatform.hh b/dw/fltkplatform.hh index 2fb95633..cbf3c6f9 100644 --- a/dw/fltkplatform.hh +++ b/dw/fltkplatform.hh @@ -149,7 +149,7 @@ public: void attachView (core::View *view); - void detachView (core::View *view); + void detachView (core::View *view); int textWidth (core::style::Font *font, const char *text, int len); char *textToUpper (const char *text, int len); diff --git a/dw/fltkui.cc b/dw/fltkui.cc index 58bb2c6f..29008055 100644 --- a/dw/fltkui.cc +++ b/dw/fltkui.cc @@ -145,6 +145,8 @@ using namespace lout::container::typed; FltkResource::FltkResource (FltkPlatform *platform) { + DBG_OBJ_CREATE ("dw::fltk::ui::FltkResource"); + this->platform = platform; allocation.x = 0; @@ -180,6 +182,8 @@ FltkResource::~FltkResource () } if (style) style->unref (); + + DBG_OBJ_DELETE (); } void FltkResource::attachView (FltkView *view) @@ -209,8 +213,14 @@ void FltkResource::detachView (FltkView *view) void FltkResource::sizeAllocate (core::Allocation *allocation) { + DBG_OBJ_ENTER ("resize", 0, "sizeAllocate", "%d, %d; %d * (%d + %d)", + allocation->x, allocation->y, allocation->width, + allocation->ascent, allocation->descent); + this->allocation = *allocation; view->allocateFltkWidget (widget, allocation); + + DBG_OBJ_LEAVE (); } void FltkResource::draw (core::View *view, core::Rectangle *area) @@ -293,6 +303,20 @@ void FltkResource::setEnabled (bool enabled) // ---------------------------------------------------------------------- +template <class I> FltkSpecificResource<I>::FltkSpecificResource (FltkPlatform + *platform) : + FltkResource (platform) +{ + DBG_OBJ_CREATE ("dw::fltk::ui::FltkSpecificResource<>"); + DBG_OBJ_BASECLASS (I); + DBG_OBJ_BASECLASS (FltkResource); +} + +template <class I> FltkSpecificResource<I>::~FltkSpecificResource () +{ + DBG_OBJ_DELETE (); +} + template <class I> void FltkSpecificResource<I>::sizeAllocate (core::Allocation *allocation) { @@ -367,6 +391,8 @@ Fl_Widget *FltkLabelButtonResource::createNewWidget (core::Allocation void FltkLabelButtonResource::sizeRequest (core::Requisition *requisition) { + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest"); + if (style) { FltkFont *font = (FltkFont*)style->font; fl_font(font->font,font->size); @@ -380,6 +406,10 @@ void FltkLabelButtonResource::sizeRequest (core::Requisition *requisition) requisition->ascent = 1; requisition->descent = 0; } + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_LEAVE (); } /* @@ -513,6 +543,17 @@ void FltkComplexButtonResource::sizeAllocate (core::Allocation *allocation) { FltkResource::sizeAllocate (allocation); + DBG_OBJ_MSGF_O ("resize", 0, flatView, + "<b>resize</b> (%d %d, <i>%d - 2 * %d =</i> %d, " + "<i>%d + %d - 2 * %d =</i> %d)", + reliefXThickness (), reliefYThickness (), + allocation->width, reliefXThickness (), + allocation->width - 2 * reliefXThickness (), + allocation->ascent, allocation->descent, + reliefYThickness (), + allocation->ascent + allocation->descent + - 2 * reliefYThickness ()); + ((FltkFlatView*)flatView)->resize ( reliefXThickness (), reliefYThickness (), allocation->width - 2 * reliefXThickness (), @@ -634,6 +675,8 @@ void FltkEntryResource::setDisplayed(bool displayed) void FltkEntryResource::sizeRequest (core::Requisition *requisition) { + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest"); + if (displayed() && style) { FltkFont *font = (FltkFont*)style->font; fl_font(font->font,font->size); @@ -650,6 +693,10 @@ void FltkEntryResource::sizeRequest (core::Requisition *requisition) requisition->ascent = 0; requisition->descent = 0; } + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_LEAVE (); } void FltkEntryResource::sizeAllocate (core::Allocation *allocation) @@ -657,6 +704,11 @@ void FltkEntryResource::sizeAllocate (core::Allocation *allocation) if (!label) { FltkResource::sizeAllocate(allocation); } else { + DBG_OBJ_MSGF ("resize", 0, + "<b>sizeAllocate</b> (%d, %d; %d * (%d + %d))", + allocation->x, allocation->y, allocation->width, + allocation->ascent, allocation->descent); + this->allocation = *allocation; /* push the Fl_Input over to the right of the label */ @@ -779,6 +831,8 @@ void FltkMultiLineTextResource::setWidgetStyle (Fl_Widget *widget, void FltkMultiLineTextResource::sizeRequest (core::Requisition *requisition) { + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest"); + if (style) { FltkFont *font = (FltkFont*)style->font; fl_font(font->font,font->size); @@ -797,6 +851,10 @@ void FltkMultiLineTextResource::sizeRequest (core::Requisition *requisition) requisition->ascent = 1; requisition->descent = 0; } + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_LEAVE (); } const char *FltkMultiLineTextResource::getText () @@ -864,6 +922,8 @@ void FltkToggleButtonResource<I>::setWidgetStyle (Fl_Widget *widget, template <class I> void FltkToggleButtonResource<I>::sizeRequest (core::Requisition *requisition) { + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest"); + FltkFont *font = (FltkFont *) (this->FltkResource::style ? this->FltkResource::style->font : NULL); @@ -877,6 +937,10 @@ void FltkToggleButtonResource<I>::sizeRequest (core::Requisition *requisition) requisition->ascent = 1; requisition->descent = 0; } + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_LEAVE (); } @@ -1116,6 +1180,8 @@ int FltkOptionMenuResource::getMaxItemWidth() void FltkOptionMenuResource::sizeRequest (core::Requisition *requisition) { + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest"); + if (style) { FltkFont *font = (FltkFont*)style->font; fl_font(font->font, font->size); @@ -1130,6 +1196,10 @@ void FltkOptionMenuResource::sizeRequest (core::Requisition *requisition) requisition->ascent = 1; requisition->descent = 0; } + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_LEAVE (); } void FltkOptionMenuResource::enlargeMenu () @@ -1405,6 +1475,8 @@ int FltkListResource::getMaxItemWidth() void FltkListResource::sizeRequest (core::Requisition *requisition) { + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest"); + if (style) { CustBrowser *b = (CustBrowser *) widget; int height = b->full_height(); @@ -1425,6 +1497,10 @@ void FltkListResource::sizeRequest (core::Requisition *requisition) requisition->ascent = 1; requisition->descent = 0; } + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_LEAVE (); } int FltkListResource::getNumberOfItems() diff --git a/dw/fltkui.hh b/dw/fltkui.hh index 8667a0cb..a0d2d7b7 100644 --- a/dw/fltkui.hh +++ b/dw/fltkui.hh @@ -223,8 +223,8 @@ public: template <class I> class FltkSpecificResource: public I, public FltkResource { public: - inline FltkSpecificResource (FltkPlatform *platform) : - FltkResource (platform) { } + FltkSpecificResource (FltkPlatform *platform); + ~FltkSpecificResource (); void sizeAllocate (core::Allocation *allocation); void draw (core::View *view, core::Rectangle *area); diff --git a/dw/hyphenator.cc b/dw/hyphenator.cc index 819cc9b0..2811a818 100644 --- a/dw/hyphenator.cc +++ b/dw/hyphenator.cc @@ -244,7 +244,7 @@ bool Hyphenator::isCharPartOfActualWord (char *s) int *Hyphenator::hyphenateWord(core::Platform *platform, const char *word, int *numBreaks) { - if ((trie == NULL && exceptions ==NULL) || !isHyphenationCandidate (word)) { + if ((trie == NULL && exceptions == NULL) || !isHyphenationCandidate (word)) { *numBreaks = 0; return NULL; } diff --git a/dw/image.cc b/dw/image.cc index 74f96e57..f6bf6434 100644 --- a/dw/image.cc +++ b/dw/image.cc @@ -149,11 +149,15 @@ Image::Image(const char *altText) this->altText = altText ? strdup (altText) : NULL; altTextWidth = -1; // not yet calculated buffer = NULL; + bufWidth = bufHeight = -1; clicking = false; currLink = -1; mapList = NULL; mapKey = NULL; isMap = false; + + DBG_OBJ_SET_NUM ("bufWidth", bufWidth); + DBG_OBJ_SET_NUM ("bufHeight", bufHeight); } Image::~Image() @@ -170,25 +174,11 @@ Image::~Image() void Image::sizeRequestImpl (core::Requisition *requisition) { + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequestImpl"); + if (buffer) { - if (getStyle ()->height == core::style::LENGTH_AUTO && - core::style::isAbsLength (getStyle ()->width) && - buffer->getRootWidth () > 0) { - // preserve aspect ratio when only width is given - requisition->width = core::style::absLengthVal (getStyle ()->width); - requisition->ascent = buffer->getRootHeight () * - requisition->width / buffer->getRootWidth (); - } else if (getStyle ()->width == core::style::LENGTH_AUTO && - core::style::isAbsLength (getStyle ()->height) && - buffer->getRootHeight () > 0) { - // preserve aspect ratio when only height is given - requisition->ascent = core::style::absLengthVal (getStyle ()->height); - requisition->width = buffer->getRootWidth () * - requisition->ascent / buffer->getRootHeight (); - } else { - requisition->width = buffer->getRootWidth (); - requisition->ascent = buffer->getRootHeight (); - } + requisition->width = buffer->getRootWidth (); + requisition->ascent = buffer->getRootHeight (); requisition->descent = 0; } else { if (altText && altText[0]) { @@ -206,45 +196,113 @@ void Image::sizeRequestImpl (core::Requisition *requisition) } } - requisition->width += getStyle()->boxDiffWidth (); - requisition->ascent += getStyle()->boxOffsetY (); - requisition->descent += getStyle()->boxRestHeight (); + requisition->width += boxDiffWidth (); + requisition->ascent += boxOffsetY (); + requisition->descent += boxRestHeight (); + + correctRequisition (requisition, core::splitHeightPreserveDescent); + + if (buffer) { + // If one dimension is set, preserve the aspect ratio (without + // extraSpace/margin/border/padding). Notice that + // requisition->descent could have been changed in + // core::splitHeightPreserveDescent, so we do not make any + // assumtions here about it (and requisition->ascent). + + // TODO Check again possible overflows. (Aren't buffer + // dimensions limited to 2^15?) + + bool widthSpecified = getStyle()->width != core::style::LENGTH_AUTO || + getStyle()->minWidth != core::style::LENGTH_AUTO || + getStyle()->maxWidth != core::style::LENGTH_AUTO; + bool heightSpecified = getStyle()->height != core::style::LENGTH_AUTO || + getStyle()->minHeight != core::style::LENGTH_AUTO || + getStyle()->maxHeight != core::style::LENGTH_AUTO; + + if (!widthSpecified && heightSpecified) + requisition->width = + (requisition->ascent + requisition->descent - boxDiffHeight ()) + * buffer->getRootWidth () / buffer->getRootHeight () + + boxDiffWidth (); + else if (widthSpecified && !heightSpecified) { + requisition->ascent = (requisition->width + boxDiffWidth ()) + * buffer->getRootHeight () / buffer->getRootWidth () + + boxOffsetY (); + requisition->descent = boxRestHeight (); + } + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_LEAVE (); +} + +void Image::getExtremesImpl (core::Extremes *extremes) +{ + int contentWidth; + if (buffer) + contentWidth = buffer->getRootWidth (); + else { + if (altText && altText[0]) { + if (altTextWidth == -1) + altTextWidth = + layout->textWidth (getStyle()->font, altText, strlen (altText)); + contentWidth = altTextWidth; + } else + contentWidth = 0; + } + + int width = contentWidth + boxDiffWidth (); + + // With percentage width, the image may be narrower than the buffer. + extremes->minWidth = + core::style::isPerLength (getStyle()->width) ? boxDiffWidth () : width; + + // (We ignore the same effect for the maximal width.) + extremes->maxWidth = width; + + extremes->minWidthIntrinsic = extremes->minWidth; + extremes->maxWidthIntrinsic = extremes->maxWidth; + + correctExtremes (extremes); } void Image::sizeAllocateImpl (core::Allocation *allocation) { - core::Imgbuf *oldBuffer; - int dx, dy; + DBG_OBJ_ENTER ("resize", 0, "sizeAllocateImpl", "%d, %d; %d * (%d + %d)", + allocation->x, allocation->y, allocation->width, + allocation->ascent, allocation->descent); + - /* if image is moved only */ - if (allocation->width == this->allocation.width && - allocation->ascent + allocation->descent == getHeight ()) - return; - - dx = getStyle()->boxDiffWidth (); - dy = getStyle()->boxDiffHeight (); -#if 0 - MSG("boxDiffHeight = %d + %d, buffer=%p\n", - getStyle()->boxOffsetY(), getStyle()->boxRestHeight(), buffer); - MSG("getContentWidth() = allocation.width - style->boxDiffWidth ()" - " = %d - %d = %d\n", - this->allocation.width, getStyle()->boxDiffWidth(), - this->allocation.width - getStyle()->boxDiffWidth()); - MSG("getContentHeight() = getHeight() - style->boxDiffHeight ()" - " = %d - %d = %d\n", this->getHeight(), getStyle()->boxDiffHeight(), - this->getHeight() - getStyle()->boxDiffHeight()); -#endif - if (buffer && - (allocation->width - dx > 0 || - allocation->ascent + allocation->descent - dy > 0)) { - // Zero content size : simply wait... - // Only one dimension: naturally scale - oldBuffer = buffer; - buffer = oldBuffer->getScaledBuf (allocation->width - dx, - allocation->ascent - + allocation->descent - dy); + int newBufWidth = allocation->width - boxDiffWidth (); + int newBufHeight = + allocation->ascent + allocation->descent - boxDiffHeight (); + + if (buffer && newBufWidth > 0 && newBufHeight > 0 && + // Save some time when size did not change: + (newBufWidth != bufWidth || newBufHeight != bufHeight)) { + DBG_OBJ_MSG ("resize", 1, "replacing buffer"); + + core::Imgbuf *oldBuffer = buffer; + buffer = oldBuffer->getScaledBuf (newBufWidth, newBufHeight); oldBuffer->unref (); + + bufWidth = newBufWidth; + bufHeight = newBufHeight; + + DBG_OBJ_ASSOC_CHILD (this->buffer); + DBG_OBJ_SET_NUM ("bufWidth", bufWidth); + DBG_OBJ_SET_NUM ("bufHeight", bufHeight); } + + DBG_OBJ_LEAVE (); +} + +void Image::containerSizeChangedForChildren () +{ + DBG_OBJ_ENTER0 ("resize", 0, "containerSizeChangedForChildren"); + // Nothing to do. + DBG_OBJ_LEAVE (); } void Image::enterNotifyImpl (core::EventCrossing *event) @@ -425,19 +483,25 @@ void Image::setBuffer (core::Imgbuf *buffer, bool resize) { core::Imgbuf *oldBuf = this->buffer; - if (resize) - queueResize (0, true); + if (wasAllocated () && needsResize () && + getContentWidth () > 0 && getContentHeight () > 0) { + // Don't create a new buffer for the transition from alt text to img, + // and only scale when both dimensions are known. - if (wasAllocated () && getContentWidth () > 0 && getContentHeight () > 0) { - // Only scale when both dimensions are known. - this->buffer = - buffer->getScaledBuf (getContentWidth (), getContentHeight ()); + bufWidth = getContentWidth (); + bufHeight = getContentHeight (); + this->buffer = buffer->getScaledBuf (bufWidth, bufHeight); } else { this->buffer = buffer; + bufWidth = buffer->getRootWidth (); + bufHeight = buffer->getRootHeight (); buffer->ref (); } + queueResize (0, true); DBG_OBJ_ASSOC_CHILD (this->buffer); + DBG_OBJ_SET_NUM ("bufWidth", bufWidth); + DBG_OBJ_SET_NUM ("bufHeight", bufHeight); if (oldBuf) oldBuf->unref (); diff --git a/dw/image.hh b/dw/image.hh index a712936e..9adf7806 100644 --- a/dw/image.hh +++ b/dw/image.hh @@ -121,6 +121,7 @@ class Image: public core::Widget, public core::ImgRenderer private: char *altText; core::Imgbuf *buffer; + int bufWidth, bufHeight; int altTextWidth; bool clicking; int currLink; @@ -130,7 +131,9 @@ private: protected: void sizeRequestImpl (core::Requisition *requisition); + void getExtremesImpl (core::Extremes *extremes); void sizeAllocateImpl (core::Allocation *allocation); + void containerSizeChangedForChildren (); void draw (core::View *view, core::Rectangle *area); diff --git a/dw/imgrenderer.cc b/dw/imgrenderer.cc index 285a8dcd..14806ea2 100644 --- a/dw/imgrenderer.cc +++ b/dw/imgrenderer.cc @@ -1,3 +1,22 @@ +/* + * Dillo Widget + * + * Copyright 2013 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + #include "core.hh" namespace dw { diff --git a/dw/iterator.cc b/dw/iterator.cc index 18d7cd5a..0edb580b 100644 --- a/dw/iterator.cc +++ b/dw/iterator.cc @@ -55,6 +55,24 @@ bool Iterator::equals (Object *other) (getWidget() == otherIt->getWidget() && compareTo(otherIt) == 0); } +void Iterator::intoStringBuffer(misc::StringBuffer *sb) +{ + sb->append ("{ widget = "); + //widget->intoStringBuffer (sb); + sb->appendPointer (widget); + sb->append (" ("); + sb->append (widget->getClassName()); + sb->append (")>"); + + sb->append (", mask = "); + Content::maskIntoStringBuffer (mask, sb); + + sb->append (", content = "); + Content::intoStringBuffer (&content, sb); + + sb->append (" }"); +} + /** * \brief Delete the iterator. * @@ -186,6 +204,14 @@ void Iterator::scrollTo (Iterator *it1, Iterator *it2, int start, int end, } } + +void Iterator::print () +{ + misc::StringBuffer sb; + intoStringBuffer (&sb); + printf ("%s", sb.getChars ()); +} + // ------------------- // EmptyIterator // ------------------- @@ -343,7 +369,7 @@ Iterator *DeepIterator::searchDownward (Iterator *it, Content::Type mask, //DEBUG_MSG (1, "%*smoving down (%swards) from %s\n", // indent, "", from_end ? "back" : "for", a_Dw_iterator_text (it)); - assert (it->getContent()->type == Content::WIDGET); + assert (it->getContent()->type & Content::ANY_WIDGET); it2 = it->getContent()->widget->iterator (mask, fromEnd); if (it2 == NULL) { @@ -356,7 +382,7 @@ Iterator *DeepIterator::searchDownward (Iterator *it, Content::Type mask, //DEBUG_MSG (1, "%*sexamining %s\n", // indent, "", a_Dw_iterator_text (it2)); - if (it2->getContent()->type == Content::WIDGET) { + if (it2->getContent()->type & Content::ANY_WIDGET) { // Another widget. Search in it downwards. it3 = searchDownward (it2, mask, fromEnd); if (it3 != NULL) { @@ -390,11 +416,11 @@ Iterator *DeepIterator::searchSideward (Iterator *it, Content::Type mask, //DEBUG_MSG (1, "%*smoving %swards from %s\n", // indent, "", from_end ? "back" : "for", a_Dw_iterator_text (it)); - assert (it->getContent()->type == Content::WIDGET); + assert (it->getContent()->type & Content::ANY_WIDGET); it2 = it->cloneIterator (); while (fromEnd ? it2->prev () : it2->next ()) { - if (it2->getContent()->type == Content::WIDGET) { + if (it2->getContent()->type & Content::ANY_WIDGET) { // Search downwards in this widget. it3 = searchDownward (it2, mask, fromEnd); if (it3 != NULL) { @@ -416,13 +442,14 @@ Iterator *DeepIterator::searchSideward (Iterator *it, Content::Type mask, /* Nothing found, go upwards in the tree (if possible). */ it2->unref (); - if (it->getWidget()->getParent ()) { - it2 = it->getWidget()->getParent()->iterator (mask, false); + Widget *respParent = getRespectiveParent (it->getWidget(), it->getMask()); + if (respParent) { + it2 = respParent->iterator (mask, false); while (true) { if (!it2->next ()) misc::assertNotReached (); - if (it2->getContent()->type == Content::WIDGET && + if (it2->getContent()->type & Content::ANY_WIDGET && it2->getContent()->widget == it->getWidget ()) { it3 = searchSideward (it2, mask, fromEnd); it2->unref (); @@ -440,6 +467,27 @@ Iterator *DeepIterator::searchSideward (Iterator *it, Content::Type mask, return NULL; } +Widget *DeepIterator::getRespectiveParent (Widget *widget, Content::Type mask) +{ + // Return, depending on which is requested indirectly (follow + // references or containments) the parent (container) or the + // generator. At this point, the type of the parent/generator is + // not known (since the parent/generator is not known), so we have + // to examine the mask. This is the reason why only one of + // WIDGET_OOF_CONT and WIDGET_OOF_REF is allowed. + + return (mask & Content::WIDGET_OOF_REF) ? + widget->getGenerator() : widget->getParent(); +} + +int DeepIterator::getRespectiveLevel (Widget *widget, Content::Type mask) +{ + // Similar to getRespectiveParent. + + return (mask & Content::WIDGET_OOF_REF) ? + widget->getGeneratorLevel() : widget->getLevel(); +} + /** * \brief Create a new deep iterator from an existing dw::core::Iterator. * @@ -456,6 +504,19 @@ Iterator *DeepIterator::searchSideward (Iterator *it, Content::Type mask, */ DeepIterator::DeepIterator (Iterator *it) { + //printf ("Starting creating DeepIterator %p ...\n", this); + //printf ("Initial iterator: "); + //it->print (); + //printf ("\n"); + + // Widgets out of flow are either followed widtin containers, or + // generators. Both (and also nothing at all) is not allowed. See + // also comment in getRespectiveParent. + int oofMask = + it->getMask() & (Content::WIDGET_OOF_CONT | Content::WIDGET_OOF_REF); + assert (oofMask == Content::WIDGET_OOF_CONT || + oofMask == Content::WIDGET_OOF_REF); + //DEBUG_MSG (1, "a_Dw_ext_iterator_new: %s\n", a_Dw_iterator_text (it)); // Clone input iterator, so the iterator passed as parameter @@ -467,7 +528,7 @@ DeepIterator::DeepIterator (Iterator *it) // If it points to a widget, find a near non-widget content, // since an DeepIterator should never return widgets. - if (it->getContent()->type == Content::WIDGET) { + if (it->getContent()->type & Content::ANY_WIDGET) { Iterator *it2; // The second argument of searchDownward is actually a matter of @@ -494,31 +555,50 @@ DeepIterator::DeepIterator (Iterator *it) // \todo There may be a faster way instead of iterating through the // parent widgets. + //printf ("Starting with: "); + //it->print (); + //printf ("\n"); + // Construct the iterators. - int thisLevel = it->getWidget()->getLevel (), level; + int thisLevel = getRespectiveLevel (it->getWidget()), level; Widget *w; - for (w = it->getWidget (), level = thisLevel; w->getParent() != NULL; - w = w->getParent (), level--) { - Iterator *it = w->getParent()->iterator (mask, false); + for (w = it->getWidget (), level = thisLevel; + getRespectiveParent (w) != NULL; + w = getRespectiveParent (w), level--) { + Iterator *it = getRespectiveParent(w)->iterator (mask, false); + + //printf (" parent: %s %p\n", w->getClassName (), w); + stack.put (it, level - 1); while (true) { + //printf (" "); + //it->print (); + //printf ("\n"); + bool hasNext = it->next(); assert (hasNext); - if (it->getContent()->type == Content::WIDGET && + if (it->getContent()->type & Content::ANY_WIDGET && it->getContent()->widget == w) break; } + + //printf (" %d: ", level - 1); + //it->print (); + //printf ("\n"); } stack.put (it, thisLevel); content = *(it->getContent()); } + + //printf ("... done creating DeepIterator %p.\n", this); } DeepIterator::~DeepIterator () { + //printf ("Deleting DeepIterator %p ...\n", this); } object::Object *DeepIterator::clone () @@ -539,9 +619,29 @@ int DeepIterator::compareTo (object::Comparable *other) { DeepIterator *otherDeepIterator = (DeepIterator*)other; + //printf ("Compare: %s\n", stack.toString ()); + //printf (" to: %s\n", otherDeepIterator->stack.toString ()); + // Search the highest level, where the widgets are the same. int level = 0; + // The Comparable interface does not define "uncomparable". Deep + // iterators are only comparable if they belong to the same widget + // tree, so have the same widget at the bottom at the + // stack. If this is not the case, we abort. + + assert (stack.size() > 0); + assert (otherDeepIterator->stack.size() > 0); + + //printf ("Equal? The %s %p (of %p) and the %s %p (of %p)?\n", + // stack.get(0)->getWidget()->getClassName(), + // stack.get(0)->getWidget(), this, + // otherDeepIterator->stack.get(0)->getWidget()->getClassName(), + // otherDeepIterator->stack.get(0)->getWidget(), otherDeepIterator); + + assert (stack.get(0)->getWidget() + == otherDeepIterator->stack.get(level)->getWidget()); + while (stack.get(level)->getWidget () == otherDeepIterator->stack.get(level)->getWidget ()) { if (level == stack.size() - 1 || @@ -550,10 +650,14 @@ int DeepIterator::compareTo (object::Comparable *other) level++; } + //printf (" => level = %d (temorally)\n", level); + while (stack.get(level)->getWidget () != otherDeepIterator->stack.get(level)->getWidget ()) level--; + //printf (" => level = %d (finally)\n", level); + return stack.get(level)->compareTo (otherDeepIterator->stack.get(level)); } @@ -577,7 +681,7 @@ bool DeepIterator::next () Iterator *it = stack.getTop (); if (it->next ()) { - if (it->getContent()->type == Content::WIDGET) { + if (it->getContent()->type & Content::ANY_WIDGET) { // Widget: new iterator on stack, to search in this widget. stack.push (it->getContent()->widget->iterator (mask, false)); return next (); @@ -610,7 +714,7 @@ bool DeepIterator::prev () Iterator *it = stack.getTop (); if (it->prev ()) { - if (it->getContent()->type == Content::WIDGET) { + if (it->getContent()->type & Content::ANY_WIDGET) { // Widget: new iterator on stack, to search in this widget. stack.push (it->getContent()->widget->iterator (mask, true)); return prev (); @@ -642,9 +746,17 @@ CharIterator::CharIterator () it = NULL; } -CharIterator::CharIterator (Widget *widget) +/** + * \brief ... + * + * If followReferences is true, only the reference are followed, when + * the container and generator for a widget is different. If false, + * only the container is followed. + */ +CharIterator::CharIterator (Widget *widget, bool followReferences) { - Iterator *i = widget->iterator (Content::SELECTION_CONTENT, false); + Iterator *i = + widget->iterator (Content::maskForSelection (followReferences), false); it = new DeepIterator (i); i->unref (); ch = START; diff --git a/dw/iterator.hh b/dw/iterator.hh index d086721c..abf31d0b 100644 --- a/dw/iterator.hh +++ b/dw/iterator.hh @@ -31,6 +31,7 @@ private: public: bool equals (Object *other); + void intoStringBuffer(lout::misc::StringBuffer *sb); inline Widget *getWidget () { return widget; } inline Content *getContent () { return &content; } @@ -85,6 +86,8 @@ public: static void scrollTo (Iterator *it1, Iterator *it2, int start, int end, HPosition hpos, VPosition vpos); + + virtual void print (); }; @@ -168,6 +171,16 @@ private: inline DeepIterator () { } + static Widget *getRespectiveParent (Widget *widget, Content::Type mask); + inline Widget *getRespectiveParent (Widget *widget) { + return getRespectiveParent (widget, mask); + } + + static int getRespectiveLevel (Widget *widget, Content::Type mask); + inline int getRespectiveLevel (Widget *widget) { + return getRespectiveLevel (widget, mask); + } + public: DeepIterator(Iterator *it); ~DeepIterator(); @@ -230,7 +243,7 @@ private: CharIterator (); public: - CharIterator (Widget *widget); + CharIterator (Widget *widget, bool followReferences); ~CharIterator (); lout::object::Object *clone(); diff --git a/dw/layout.cc b/dw/layout.cc index 6f2e8d8b..c2a53d08 100644 --- a/dw/layout.cc +++ b/dw/layout.cc @@ -91,6 +91,10 @@ void Layout::LayoutImgRenderer::draw (int x, int y, int width, int height) // ---------------------------------------------------------------------- +void Layout::Receiver::resizeQueued (bool extremesChanged) +{ +} + void Layout::Receiver::canvasSizeChanged (int width, int ascent, int descent) { } @@ -110,6 +114,10 @@ bool Layout::Emitter::emitToReceiver (lout::signal::Receiver *receiver, ((Integer*)argv[2])->getValue ()); break; + case RESIZE_QUEUED: + layoutReceiver->resizeQueued (((Boolean*)argv[0])->getValue ()); + break; + default: misc::assertNotReached (); } @@ -117,6 +125,13 @@ bool Layout::Emitter::emitToReceiver (lout::signal::Receiver *receiver, return false; } +void Layout::Emitter::emitResizeQueued (bool extremesChanged) +{ + Boolean ec (extremesChanged); + Object *argv[1] = { &ec }; + emitVoid (RESIZE_QUEUED, 1, argv); +} + void Layout::Emitter::emitCanvasSizeChanged (int width, int ascent, int descent) { @@ -245,6 +260,9 @@ Layout::Layout (Platform *platform) topLevel = NULL; widgetAtPoint = NULL; + queueQueueResizeList = new typed::Stack<QueueResizeItem> (true); + queueResizeList = new typed::Vector<Widget> (4, false); + DBG_OBJ_CREATE ("dw::core::Layout"); bgColor = NULL; @@ -259,6 +277,11 @@ Layout::Layout (Platform *platform) viewportWidth = viewportHeight = 0; hScrollbarThickness = vScrollbarThickness = 0; + DBG_OBJ_SET_NUM ("viewportWidth", viewportWidth); + DBG_OBJ_SET_NUM ("viewportHeight", viewportHeight); + DBG_OBJ_SET_NUM ("hScrollbarThickness", hScrollbarThickness); + DBG_OBJ_SET_NUM ("vScrollbarThickness", vScrollbarThickness); + requestedAnchor = NULL; scrollIdleId = -1; scrollIdleNotInterrupted = false; @@ -277,7 +300,13 @@ Layout::Layout (Platform *platform) selectionState.setLayout(this); + queueResizeCounter = sizeAllocateCounter = sizeRequestCounter = + getExtremesCounter = 0; + layoutImgRenderer = NULL; + + resizeIdleCounter = queueResizeCounter = sizeAllocateCounter + = sizeRequestCounter = getExtremesCounter = 0; } Layout::~Layout () @@ -299,18 +328,45 @@ Layout::~Layout () if (bgImage) bgImage->unref (); if (topLevel) { + detachWidget (topLevel); Widget *w = topLevel; topLevel = NULL; delete w; } + + delete queueQueueResizeList; + delete queueResizeList; delete platform; delete view; delete anchorsTable; delete textZone; + if (requestedAnchor) + free (requestedAnchor); + DBG_OBJ_DELETE (); } +void Layout::detachWidget (Widget *widget) +{ + // Called form ~Layout. Sometimes, the widgets (not only the toplevel widget) + // do some stuff after the layout has been deleted, so *all* widgets have to + // be detached, and check "layout != NULL" at relevant points. + + // Could be replaced by a virtual method in Widget, like getWidgetAtPoint, + // if performace were really a problem. + + widget->layout = NULL; + Iterator *it = + widget->iterator ((Content::Type) + (Content::WIDGET_IN_FLOW | Content::WIDGET_OOF_CONT), + false); + while (it->next ()) + detachWidget (it->getContent()->widget); + + it->unref (); +} + void Layout::addWidget (Widget *widget) { if (topLevel) { @@ -320,12 +376,22 @@ void Layout::addWidget (Widget *widget) topLevel = widget; widget->layout = this; + widget->container = NULL; + DBG_OBJ_SET_PTR_O (widget, "container", widget->container); + + queueResizeList->clear (); + widget->notifySetAsTopLevel (); findtextState.setWidget (widget); canvasHeightGreater = false; - setSizeHints (); - queueResize (); + DBG_OBJ_SET_SYM ("canvasHeightGreater", + canvasHeightGreater ? "true" : "false"); + + // Do not directly call Layout::queueResize(), but + // Widget::queueResize(), so that all flags are set properly, + // queueResizeList is filled, etc. + topLevel->queueResize (-1, false); } void Layout::removeWidget () @@ -334,6 +400,7 @@ void Layout::removeWidget () * \bug Some more attributes must be reset here. */ topLevel = NULL; + queueResizeList->clear (); widgetAtPoint = NULL; canvasWidth = canvasAscent = canvasDescent = 0; scrollX = scrollY = 0; @@ -413,6 +480,11 @@ void Layout::attachView (View *view) hScrollbarThickness = view->getHScrollbarThickness (); vScrollbarThickness = view->getVScrollbarThickness (); } + + DBG_OBJ_SET_NUM ("viewportWidth", viewportWidth); + DBG_OBJ_SET_NUM ("viewportHeight", viewportHeight); + DBG_OBJ_SET_NUM ("hScrollbarThickness", hScrollbarThickness); + DBG_OBJ_SET_NUM ("vScrollbarThickness", vScrollbarThickness); } /* @@ -647,7 +719,7 @@ void Layout::setAnchor (const char *anchor) _MSG("setAnchor (%s)\n", anchor); if (requestedAnchor) - free(requestedAnchor); + free (requestedAnchor); requestedAnchor = anchor ? strdup (anchor) : NULL; updateAnchor (); } @@ -776,20 +848,62 @@ void Layout::setBgImage (style::StyleImage *bgImage, void Layout::resizeIdle () { + DBG_OBJ_ENTER0 ("resize", 0, "resizeIdle"); + + enterResizeIdle (); + //static int calls = 0; - //MSG(" Layout::resizeIdle calls = %d\n", ++calls); + //printf ("Layout::resizeIdle calls = %d\n", ++calls); assert (resizeIdleId != -1); + for (typed::Iterator <Widget> it = queueResizeList->iterator(); + it.hasNext (); ) { + Widget *widget = it.getNext (); + + //printf (" the %stop-level %s %p was queued (extremes changed: %s)\n", + // widget->parent ? "non-" : "", widget->getClassName(), widget, + // widget->extremesQueued () ? "yes" : "no"); + + if (widget->resizeQueued ()) { + widget->setFlags (Widget::NEEDS_RESIZE); + widget->unsetFlags (Widget::RESIZE_QUEUED); + } + + if (widget->allocateQueued ()) { + widget->setFlags (Widget::NEEDS_ALLOCATE); + widget->unsetFlags (Widget::ALLOCATE_QUEUED); + } + + if (widget->extremesQueued ()) { + widget->setFlags (Widget::EXTREMES_CHANGED); + widget->unsetFlags (Widget::EXTREMES_QUEUED); + } + } + queueResizeList->clear (); + // Reset already here, since in this function, queueResize() may be // called again. resizeIdleId = -1; - if (topLevel) { + // If this method is triggered by a viewport change, we can save + // time when the toplevel widget is not affected (as for a toplevel + // image resource). + if (topLevel && (topLevel->needsResize () || topLevel->needsAllocate ())) { Requisition requisition; Allocation allocation; topLevel->sizeRequest (&requisition); + DBG_OBJ_MSGF ("resize", 1, "toplevel size: %d * (%d + %d)", + requisition.width, requisition.ascent, requisition.descent); + + // This method is triggered by Widget::queueResize, which will, + // in any case, set NEEDS_ALLOCATE (indirectly, as ALLOCATE_QUEUED). + // This assertion helps to find inconsistences. (Cases where + // this method is triggered by a viewport change, but the + // toplevel widget is not affected, are filtered out some lines + // above: "if (topLevel && topLevel->needsResize ())".) + assert (topLevel->needsAllocate ()); allocation.x = allocation.y = 0; allocation.width = requisition.width; @@ -812,12 +926,12 @@ void Layout::resizeIdle () int currVThickness = currVScrollbarThickness(); if (!canvasHeightGreater && - canvasAscent + canvasDescent - > viewportHeight - currHThickness) { + canvasAscent + canvasDescent > viewportHeight - currHThickness) { canvasHeightGreater = true; - setSizeHints (); - /* May queue a new resize. */ - } + DBG_OBJ_SET_SYM ("canvasHeightGreater", + canvasHeightGreater ? "true" : "false"); + containerSizeChanged (); + } // Set viewport sizes. view->setViewportSize (viewportWidth, viewportHeight, @@ -825,20 +939,15 @@ void Layout::resizeIdle () } // views are redrawn via Widget::resizeDrawImpl () - } updateAnchor (); -} -void Layout::setSizeHints () -{ - if (topLevel) { - topLevel->setWidth (viewportWidth - - (canvasHeightGreater ? vScrollbarThickness : 0)); - topLevel->setAscent (viewportHeight - hScrollbarThickness); - topLevel->setDescent (0); - } + DBG_OBJ_MSGF ("resize", 1, + "after resizeIdle: resizeIdleId = %d", resizeIdleId); + DBG_OBJ_LEAVE (); + + leaveResizeIdle (); } void Layout::queueDraw (int x, int y, int width, int height) @@ -877,13 +986,21 @@ void Layout::queueDrawExcept (int x, int y, int width, int height, queueDraw (ix2, iy1, x + width - ix2, iy2 - iy1); } -void Layout::queueResize () +void Layout::queueResize (bool extremesChanged) { + DBG_OBJ_ENTER ("resize", 0, "queueResize", "%s", + extremesChanged ? "true" : "false"); + if (resizeIdleId == -1) { view->cancelQueueDraw (); resizeIdleId = platform->addIdle (&Layout::resizeIdle); + DBG_OBJ_MSGF ("resize", 1, "setting resizeIdleId = %d", resizeIdleId); } + + emitter.emitResizeQueued (extremesChanged); + + DBG_OBJ_LEAVE (); } @@ -912,7 +1029,7 @@ bool Layout::buttonEvent (ButtonEventType type, View *view, int numPressed, * * Arguments are similar to dw::core::Layout::buttonPress. */ -bool Layout::motionNotify (View *view, int x, int y, ButtonState state) +bool Layout::motionNotify (View *view, int x, int y, ButtonState state) { EventButton event; @@ -978,7 +1095,7 @@ Widget *Layout::getWidgetAtPoint (int x, int y) { _MSG ("------------------------------------------------------------\n"); _MSG ("widget at (%d, %d)\n", x, y); - if (topLevel) + if (topLevel && topLevel->wasAllocated ()) return topLevel->getWidgetAtPoint (x, y, 0); else return NULL; @@ -1154,23 +1271,48 @@ void Layout::scrollPosChanged (View *view, int x, int y) */ void Layout::viewportSizeChanged (View *view, int width, int height) { - _MSG("Layout::viewportSizeChanged w=%d h=%d new_w=%d new_h=%d\n", - viewportWidth, viewportHeight, width, height); + DBG_OBJ_ENTER ("resize", 0, "viewportSizeChanged", "%p, %d, %d", + view, width, height); /* If the width has become higher, we test again, whether the vertical * scrollbar (so to speak) can be hidden again. */ - if (usesViewport && width > viewportWidth) + if (usesViewport && width > viewportWidth) { canvasHeightGreater = false; + DBG_OBJ_SET_SYM ("canvasHeightGreater", + canvasHeightGreater ? "true" : "false"); + } /* if size changes, redraw this view. * TODO: this is a resize call (redraw/resize code needs a review). */ - if (viewportWidth != width || viewportHeight != height) - queueResize(); + if (viewportWidth != width || viewportHeight != height) { + if (topLevel) + // similar to addWidget() + topLevel->queueResize (-1, false); + else + queueResize (false); + } viewportWidth = width; viewportHeight = height; - setSizeHints (); + DBG_OBJ_SET_NUM ("viewportWidth", viewportWidth); + DBG_OBJ_SET_NUM ("viewportHeight", viewportHeight); + + containerSizeChanged (); + + DBG_OBJ_LEAVE (); +} + +void Layout::containerSizeChanged () +{ + DBG_OBJ_ENTER0 ("resize", 0, "containerSizeChanged"); + + if (topLevel) { + topLevel->containerSizeChanged (); + queueResize (true); + } + + DBG_OBJ_LEAVE (); } } // namespace core diff --git a/dw/layout.hh b/dw/layout.hh index 47554b42..32b9a134 100644 --- a/dw/layout.hh +++ b/dw/layout.hh @@ -42,11 +42,12 @@ public: /** * \brief Receiver interface different signals. * - * May be extended + * May be extended. */ class Receiver: public lout::signal::Receiver { public: + virtual void resizeQueued (bool extremesChanged); virtual void canvasSizeChanged (int width, int ascent, int descent); }; @@ -126,7 +127,7 @@ private: class Emitter: public lout::signal::Emitter { private: - enum { CANVAS_SIZE_CHANGED }; + enum { RESIZE_QUEUED, CANVAS_SIZE_CHANGED }; protected: bool emitToReceiver (lout::signal::Receiver *receiver, int signalNo, @@ -135,6 +136,7 @@ private: public: inline void connectLayout (Receiver *receiver) { connect (receiver); } + void emitResizeQueued (bool extremesChanged); void emitCanvasSizeChanged (int width, int ascent, int descent); }; @@ -150,9 +152,28 @@ private: ~Anchor (); }; + class QueueResizeItem: public lout::object::Object + { + public: + Widget *widget; + int ref; + bool extremesChanged, fast; + + inline QueueResizeItem (Widget *widget, int ref, bool extremesChanged, + bool fast) + { + this->widget = widget; + this->ref = ref; + this->extremesChanged = extremesChanged; + this->fast = fast; + } + }; + Platform *platform; View *view; Widget *topLevel, *widgetAtPoint; + lout::container::typed::Stack<QueueResizeItem> *queueQueueResizeList; + lout::container::typed::Vector<Widget> *queueResizeList; /* The state, which must be projected into the view. */ style::Color *bgColor; @@ -186,6 +207,8 @@ private: enum ButtonEventType { BUTTON_PRESS, BUTTON_RELEASE, MOTION_NOTIFY }; + void detachWidget (Widget *widget); + Widget *getWidgetAtPoint (int x, int y); void moveToWidget (Widget *newWidgetAtPoint, ButtonState state); @@ -233,9 +256,19 @@ private: void queueDraw (int x, int y, int width, int height); void queueDrawExcept (int x, int y, int width, int height, int ex, int ey, int ewidth, int eheight); - void queueResize (); + void queueResize (bool extremesChanged); void removeWidget (); + /* For tests regarding the respective Layout and (mostly) Widget + methods. Accessed by respective methods (enter..., leave..., + ...Entered) defined here and in Widget. */ + + int resizeIdleCounter, queueResizeCounter, sizeAllocateCounter, + sizeRequestCounter, getExtremesCounter; + + void enterResizeIdle () { resizeIdleCounter++; } + void leaveResizeIdle () { resizeIdleCounter--; } + public: Layout (Platform *platform); ~Layout (); @@ -297,6 +330,8 @@ public: return buttonEvent (BUTTON_PRESS, view, numPressed, x, y, state, button); } + void containerSizeChanged (); + /** * \brief This function is called by a view, to delegate a button press * event. @@ -310,7 +345,7 @@ public: button); } - bool motionNotify (View *view, int x, int y, ButtonState state); + bool motionNotify (View *view, int x, int y, ButtonState state); void enterNotify (View *view, int x, int y, ButtonState state); void leaveNotify (View *view, ButtonState state); diff --git a/dw/listitem.cc b/dw/listitem.cc index 05344d79..65293d8d 100644 --- a/dw/listitem.cc +++ b/dw/listitem.cc @@ -69,7 +69,7 @@ int ListItem::getValue () void ListItem::setMaxValue (int maxValue, int value) { - innerPadding = maxValue; + leftInnerPadding = maxValue; line1Offset = - value; redrawY = 0; queueResize (0, true); diff --git a/dw/outofflowmgr.cc b/dw/outofflowmgr.cc new file mode 100644 index 00000000..6fc1c325 --- /dev/null +++ b/dw/outofflowmgr.cc @@ -0,0 +1,2292 @@ +/* + * Dillo Widget + * + * Copyright 2013-2014 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "outofflowmgr.hh" +#include "textblock.hh" +#include "../lout/debug.hh" + +using namespace lout::object; +using namespace lout::container::typed; +using namespace lout::misc; +using namespace dw::core; +using namespace dw::core::style; + +namespace dw { + +OutOfFlowMgr::WidgetInfo::WidgetInfo (OutOfFlowMgr *oofm, Widget *widget) +{ + this->oofm = oofm; + this->widget = widget; + wasAllocated = false; + xCB = yCB = width = height = -1; +} + +void OutOfFlowMgr::WidgetInfo::update (bool wasAllocated, int xCB, int yCB, + int width, int height) +{ + DBG_OBJ_ENTER_O ("resize.oofm", 0, widget, "update", "%s, %d, %d, %d, %d", + wasAllocated ? "true" : "false", xCB, yCB, width, height); + + this->wasAllocated = wasAllocated; + this->xCB = xCB; + this->yCB = yCB; + this->width = width; + this->height = height; + + DBG_OBJ_SET_NUM_O (widget, "<WidgetInfo>.xCB", xCB); + DBG_OBJ_SET_NUM_O (widget, "<WidgetInfo>.yCB", yCB); + DBG_OBJ_SET_NUM_O (widget, "<WidgetInfo>.width", width); + DBG_OBJ_SET_NUM_O (widget, "<WidgetInfo>.height", height); + + DBG_OBJ_LEAVE_O (widget); +} + +// ---------------------------------------------------------------------- + +OutOfFlowMgr::Float::Float (OutOfFlowMgr *oofm, Widget *widget, + Textblock *generatingBlock, int externalIndex) : + WidgetInfo (oofm, widget) +{ + this->generatingBlock = generatingBlock; + this->externalIndex = externalIndex; + + yReq = yReal = size.width = size.ascent = size.descent = 0; + dirty = sizeChangedSinceLastAllocation = true; + indexGBList = indexCBList = -1; + + // Sometimes a float with widget = NULL is created as a key; this + // is not interesting for RTFL. + if (widget) { + DBG_OBJ_SET_PTR_O (widget, "<Float>.generatingBlock", generatingBlock); + DBG_OBJ_SET_NUM_O (widget, "<Float>.externalIndex", externalIndex); + DBG_OBJ_SET_NUM_O (widget, "<Float>.yReq", yReq); + DBG_OBJ_SET_NUM_O (widget, "<Float>.yReal", yReal); + DBG_OBJ_SET_NUM_O (widget, "<Float>.size.width", size.width); + DBG_OBJ_SET_NUM_O (widget, "<Float>.size.ascent", size.ascent); + DBG_OBJ_SET_NUM_O (widget, "<Float>.size.descent", size.descent); + DBG_OBJ_SET_BOOL_O (widget, "<Float>.dirty", dirty); + DBG_OBJ_SET_BOOL_O (widget, "<Float>.sizeChangedSinceLastAllocation", + sizeChangedSinceLastAllocation); + } +} + +void OutOfFlowMgr::Float::updateAllocation () +{ + DBG_OBJ_ENTER0_O ("resize.oofm", 0, getWidget (), "updateAllocation"); + + update (isNowAllocated (), getNewXCB (), getNewYCB (), getNewWidth (), + getNewHeight ()); + + DBG_OBJ_LEAVE_O (getWidget ()); +} + +void OutOfFlowMgr::Float::intoStringBuffer(StringBuffer *sb) +{ + sb->append ("{ widget = "); + sb->appendPointer (getWidget ()); + + if (getWidget ()) { + sb->append (" ("); + sb->append (getWidget()->getClassName ()); + sb->append (")"); + } + + sb->append (", indexGBList = "); + sb->appendInt (indexGBList); + sb->append (", indexCBList = "); + sb->appendInt (indexCBList); + sb->append (", sideSpanningIndex = "); + sb->appendInt (sideSpanningIndex); + sb->append (", generatingBlock = "); + sb->appendPointer (generatingBlock); + sb->append (", yReq = "); + sb->appendInt (yReq); + sb->append (", yReal = "); + sb->appendInt (yReal); + sb->append (", size = { "); + sb->appendInt (size.width); + sb->append (" * "); + sb->appendInt (size.ascent); + sb->append (" + "); + sb->appendInt (size.descent); + sb->append (" }, dirty = "); + sb->appendBool (dirty); + sb->append (", sizeChangedSinceLastAllocation = "); + sb->appendBool (sizeChangedSinceLastAllocation); + sb->append (" }"); +} + +bool OutOfFlowMgr::Float::covers (Textblock *textblock, int y, int h) +{ + DBG_OBJ_ENTER_O ("border", 0, getOutOfFlowMgr (), "covers", + "%p, %d, %d [vloat: %p]", + textblock, y, h, getWidget ()); + + bool b; + + if (textblock == generatingBlock) { + int reqyGB = y; + int flyGB = yReal; + getOutOfFlowMgr()->ensureFloatSize (this); + int flh = size.ascent + size.descent; + b = flyGB + flh > reqyGB && flyGB < reqyGB + h; + + DBG_OBJ_MSGF_O ("border", 1, getOutOfFlowMgr (), + "for generator: reqyGB = %d, flyGB = %d, " + "flh = %d + %d = %d => %s", + reqyGB, flyGB, size.ascent, size.descent, flh, + b ? "true" : "false"); + } else { + // (If the textblock were not allocated, the GB list would have + // been choosen instead of the CB list, and so this else-branch + // would not have been not executed.) + assert (getOutOfFlowMgr()->wasAllocated (textblock)); + + if (!getWidget()->wasAllocated ()) { + DBG_OBJ_MSG_O ("border", 1, getOutOfFlowMgr (), + "not generator (not allocated) => false"); + b = false; + } else { + Allocation *tba = getOutOfFlowMgr()->getAllocation(textblock), + *fla = getWidget()->getAllocation (); + int reqyCanv = tba->y + y; + int flyCanv = fla->y; + int flh = fla->ascent + fla->descent; + b = flyCanv + flh > reqyCanv && flyCanv < reqyCanv + h; + + DBG_OBJ_MSGF_O ("border", 1, getOutOfFlowMgr (), + "not generator (allocated): reqyCanv = %d + %d = %d, " + "flyCanv = %d, flh = %d + %d = %d => %s", + tba->y, y, reqyCanv, flyCanv, + fla->ascent, fla->descent, flh, b ? "true" : "false"); + } + } + + DBG_OBJ_LEAVE_O (getOutOfFlowMgr ()); + + return b; +} + +int OutOfFlowMgr::Float::ComparePosition::compare (Object *o1, Object *o2) +{ + Float *fl1 = (Float*)o1, *fl2 = (Float*)o2; + int r; + + DBG_OBJ_ENTER_O ("border", 1, oofm, + "ComparePosition/compare", "(#%d, #%d) [refTB = %p]", + fl1->getIndex (type), fl2->getIndex (type), refTB); + + if (refTB == fl1->generatingBlock && refTB == fl2->generatingBlock) { + DBG_OBJ_MSG_O ("border", 2, oofm, "refTB is generating both floats"); + r = fl1->yReal - fl2->yReal; + } else { + DBG_OBJ_MSG_O ("border", 2, oofm, "refTB is not generating both floats"); + DBG_OBJ_MSG_START_O (oofm); + + DBG_OBJ_MSGF_O ("border", 2, oofm, "generators are %p and %p", + fl1->generatingBlock, fl2->generatingBlock); + + // (i) Floats may not yet been allocated. Non-allocated floats + // do not have an effect yet, they are considered "at the end" + // of the list. + + // (ii) Float::widget is NULL for the key used for binary + // search. In this case, Float::yReal is used instead (which is + // set in SortedFloatsVector::find, too). The generator is the + // textblock, and should be allocated. (If not, the GB list + // would have been choosen instead of the CB list, and so this + // else-branch would not have been not executed.) + + bool a1 = fl1->getWidget () ? fl1->getWidget()->wasAllocated () : true; + bool a2 = fl2->getWidget () ? fl2->getWidget()->wasAllocated () : true; + + DBG_OBJ_MSGF_O ("border", 2, oofm, + "float 1 (%p) allocated: %s; float 2 (%p) allocated: %s", + fl1->getWidget (), a1 ? "yes" : "no", fl2->getWidget (), + a2 ? "yes" : "no"); + + if (a1 && a2) { + int fly1, fly2; + + if (fl1->getWidget()) { + fly1 = fl1->getWidget()->getAllocation()->y; + DBG_OBJ_MSGF_O ("border", 2, oofm, "fly1 = %d", fly1); + } else { + assert (oofm->wasAllocated (fl1->generatingBlock)); + fly1 = oofm->getAllocation(fl1->generatingBlock)->y + fl1->yReal; + DBG_OBJ_MSGF_O ("border", 2, oofm, "fly1 = %d + %d = %d", + oofm->getAllocation(fl1->generatingBlock)->y, + fl1->yReal, fly1); + } + + if (fl2->getWidget()) { + fly2 = fl2->getWidget()->getAllocation()->y; + DBG_OBJ_MSGF_O ("border", 2, oofm, "fly2 = %d", fly2); + } else { + assert (oofm->wasAllocated (fl2->generatingBlock)); + fly2 = oofm->getAllocation(fl2->generatingBlock)->y + fl2->yReal; + DBG_OBJ_MSGF_O ("border", 2, oofm, "fly2 = %d + %d = %d", + oofm->getAllocation(fl2->generatingBlock)->y, + fl2->yReal, fly2); + } + + r = fly1 - fly2; + + DBG_OBJ_MSGF_O ("border", 2, oofm, "r = %d - %d = %d", fly1, fly2, r); + } else if (a1 && !a2) + r = -1; + else if (!a1 && a2) + r = +1; + else // if (!a1 && !a2) + return 0; + + DBG_OBJ_MSG_END_O (oofm); + } + + DBG_OBJ_MSGF_O ("border", 1, oofm, "result: %d", r); + DBG_OBJ_LEAVE_O (oofm); + return r; +} + +int OutOfFlowMgr::Float::CompareSideSpanningIndex::compare (Object *o1, + Object *o2) +{ + return ((Float*)o1)->sideSpanningIndex - ((Float*)o2)->sideSpanningIndex; +} + +int OutOfFlowMgr::Float::CompareGBAndExtIndex::compare (Object *o1, Object *o2) +{ + Float *f1 = (Float*)o1, *f2 = (Float*)o2; + int r = -123; // Compiler happiness: GCC 4.7 does not handle this?; + + DBG_OBJ_ENTER_O ("border", 1, oofm, "CompareGBAndExtIndex/compare", + "#%d -> %p/%d, #%d -> %p/#%d", + f1->getIndex (type), f1->generatingBlock, f1->externalIndex, + f2->getIndex (type), f2->generatingBlock, + f2->externalIndex); + + if (f1->generatingBlock == f2->generatingBlock) { + r = f1->externalIndex - f2->externalIndex; + DBG_OBJ_MSGF_O ("border", 2, oofm, + "(a) generating blocks equal => %d - %d = %d", + f1->externalIndex, f2->externalIndex, r); + } else { + TBInfo *t1 = oofm->getTextblock (f1->generatingBlock), + *t2 = oofm->getTextblock (f2->generatingBlock); + bool rdef = false; + + for (TBInfo *t = t1; t != NULL; t = t->parent) + if (t->parent == t2) { + rdef = true; + r = t->parentExtIndex - f2->externalIndex; + DBG_OBJ_MSGF_O ("border", 2, oofm, + "(b) %p is an achestor of %p; direct child is " + "%p (%d) => %d - %d = %d\n", + t2->getTextblock (), t1->getTextblock (), + t->getTextblock (), t->parentExtIndex, + t->parentExtIndex, f2->externalIndex, r); + } + + for (TBInfo *t = t2; !rdef && t != NULL; t = t->parent) + if (t->parent == t1) { + r = f1->externalIndex - t->parentExtIndex; + rdef = true; + DBG_OBJ_MSGF_O ("border", 2, oofm, + "(c) %p is an achestor of %p; direct child is %p " + "(%d) => %d - %d = %d\n", + t1->getTextblock (), t2->getTextblock (), + t->getTextblock (), t->parentExtIndex, + f1->externalIndex, t->parentExtIndex, r); + } + + if (!rdef) { + r = t1->index - t2->index; + DBG_OBJ_MSGF_O ("border", 2, oofm, "(d) other => %d - %d = %d", + t1->index, t2->index, r); + } + } + + DBG_OBJ_MSGF_O ("border", 2, oofm, "result: %d", r); + DBG_OBJ_LEAVE_O (oofm); + return r; +} + +int OutOfFlowMgr::SortedFloatsVector::findFloatIndex (Textblock *lastGB, + int lastExtIndex) +{ + DBG_OBJ_ENTER_O ("border", 0, oofm, "findFloatIndex", "%p, %d", + lastGB, lastExtIndex); + + Float key (oofm, NULL, lastGB, lastExtIndex); + key.setIndex (type, -1); // for debugging + Float::CompareGBAndExtIndex comparator (oofm, type); + int i = bsearch (&key, false, &comparator); + + // At position i is the next larger element, so element i should + // not included, but i - 1 returned; except if the exact element is + // found: then include it and so return i. + int r; + if (i == size()) + r = i - 1; + else { + Float *f = get (i); + if (comparator.compare (f, &key) == 0) + r = i; + else + r = i - 1; + } + + //printf ("[%p] findFloatIndex (%p, %d) => i = %d, r = %d (size = %d); " + // "in %s list %p on the %s side\n", + // oofm->containingBlock, lastGB, lastExtIndex, i, r, size (), + // type == GB ? "GB" : "CB", this, side == LEFT ? "left" : "right"); + + //for (int i = 0; i < size (); i++) { + // Float *f = get(i); + // TBInfo *t = oofm->getTextblock(f->generatingBlock); + // printf (" %d: (%p [%d, %p], %d)\n", i, f->generatingBlock, + // t->index, t->parent ? t->parent->textblock : NULL, + // get(i)->externalIndex); + //} + + DBG_OBJ_MSGF_O ("border", 1, oofm, "=> r = %d", r); + DBG_OBJ_LEAVE_O (oofm); + return r; +} + +int OutOfFlowMgr::SortedFloatsVector::find (Textblock *textblock, int y, + int start, int end) +{ + DBG_OBJ_ENTER_O ("border", 0, oofm, "find", "%p, %d, %d, %d", + textblock, y, start, end); + + Float key (oofm, NULL, NULL, 0); + key.generatingBlock = textblock; + key.yReal = y; + key.setIndex (type, -1); // for debugging + Float::ComparePosition comparator (oofm, textblock, type); + int result = bsearch (&key, false, start, end, &comparator); + + DBG_OBJ_MSGF_O ("border", 1, oofm, "=> result = %d", result); + DBG_OBJ_LEAVE_O (oofm); + return result; +} + +int OutOfFlowMgr::SortedFloatsVector::findFirst (Textblock *textblock, + int y, int h, + Textblock *lastGB, + int lastExtIndex, + int *lastReturn) +{ + DBG_OBJ_ENTER_O ("border", 0, oofm, "findFirst", "%p, %d, %d, %p, %d", + textblock, y, h, lastGB, lastExtIndex); + + DBG_IF_RTFL { + DBG_OBJ_MSG_O ("border", 2, oofm, "searching in list:"); + DBG_OBJ_MSG_START_O (oofm); + + for (int i = 0; i < size(); i++) { + DBG_OBJ_MSGF_O ("border", 2, oofm, + "%d: (%p, i = %d/%d, y = %d/%d, s = (%d * (%d + %d)), " + "%s, %s, ext = %d, GB = %p); widget at (%d, %d)", + i, get(i)->getWidget (), get(i)->getIndex (type), + get(i)->sideSpanningIndex, get(i)->yReq, get(i)->yReal, + get(i)->size.width, get(i)->size.ascent, + get(i)->size.descent, + get(i)->dirty ? "dirty" : "clean", + get(i)->sizeChangedSinceLastAllocation ? "scsla" + : "sNcsla", + get(i)->externalIndex, get(i)->generatingBlock, + get(i)->getWidget()->getAllocation()->x, + get(i)->getWidget()->getAllocation()->y); + } + + DBG_OBJ_MSG_END_O (oofm); + } + + int last = findFloatIndex (lastGB, lastExtIndex); + DBG_OBJ_MSGF_O ("border", 1, oofm, "last = %d", last); + assert (last < size()); + + // If the caller wants to reuse this value: + if (lastReturn) + *lastReturn = last; + + int i = find (textblock, y, 0, last), result; + DBG_OBJ_MSGF_O ("border", 1, oofm, "i = %d", i); + + // Note: The smallest value of "i" is 0, which means that "y" is before or + // equal to the first float. The largest value is "last + 1", which means + // that "y" is after the last float. In both cases, the first or last, + // respectively, float is a candidate. Generally, both floats, before and + // at the search position, are candidates. + + if (i > 0 && get(i - 1)->covers (textblock, y, h)) + result = i - 1; + else if (i <= last && get(i)->covers (textblock, y, h)) + result = i; + else + result = -1; + + DBG_OBJ_MSGF_O ("border", 1, oofm, "=> result = %d", result); + DBG_OBJ_LEAVE_O (oofm); + return result; +} + +int OutOfFlowMgr::SortedFloatsVector::findLastBeforeSideSpanningIndex + (int sideSpanningIndex) +{ + OutOfFlowMgr::Float::CompareSideSpanningIndex comparator; + Float key (NULL, NULL, NULL, 0); + key.sideSpanningIndex = sideSpanningIndex; + return bsearch (&key, false, &comparator) - 1; +} + +void OutOfFlowMgr::SortedFloatsVector::put (Float *vloat) +{ + lout::container::typed::Vector<Float>::put (vloat); + vloat->setIndex (type, size() - 1); +} + +OutOfFlowMgr::TBInfo::TBInfo (OutOfFlowMgr *oofm, Textblock *textblock, + TBInfo *parent, int parentExtIndex) : + WidgetInfo (oofm, textblock) +{ + this->parent = parent; + this->parentExtIndex = parentExtIndex; + + leftFloatsGB = new SortedFloatsVector (oofm, LEFT, GB); + rightFloatsGB = new SortedFloatsVector (oofm, RIGHT, GB); + + wasAllocated = getWidget()->wasAllocated (); + allocation = *(getWidget()->getAllocation ()); + clearPosition = 0; +} + +OutOfFlowMgr::TBInfo::~TBInfo () +{ + delete leftFloatsGB; + delete rightFloatsGB; +} + +void OutOfFlowMgr::TBInfo::updateAllocation () +{ + DBG_OBJ_ENTER0_O ("resize.oofm", 0, getWidget (), "updateAllocation"); + + update (isNowAllocated (), getNewXCB (), getNewYCB (), getNewWidth (), + getNewHeight ()); + + DBG_OBJ_LEAVE_O (getWidget ()); +} + +OutOfFlowMgr::OutOfFlowMgr (Textblock *containingBlock) +{ + DBG_OBJ_CREATE ("dw::OutOfFlowMgr"); + + this->containingBlock = containingBlock; + + leftFloatsCB = new SortedFloatsVector (this, LEFT, CB); + rightFloatsCB = new SortedFloatsVector (this, RIGHT, CB); + + DBG_OBJ_SET_NUM ("leftFloatsCB.size", leftFloatsCB->size()); + DBG_OBJ_SET_NUM ("rightFloatsCB.size", rightFloatsCB->size()); + + leftFloatsAll = new Vector<Float> (1, true); + rightFloatsAll = new Vector<Float> (1, true); + + DBG_OBJ_SET_NUM ("leftFloatsAll.size", leftFloatsAll->size()); + DBG_OBJ_SET_NUM ("rightFloatsAll.size", rightFloatsAll->size()); + + floatsByWidget = new HashTable <TypedPointer <Widget>, Float> (true, false); + + tbInfos = new Vector<TBInfo> (1, false); + tbInfosByTextblock = + new HashTable <TypedPointer <Textblock>, TBInfo> (true, true); + + leftFloatsMark = rightFloatsMark = 0; + lastLeftTBIndex = lastRightTBIndex = 0; + + containingBlockWasAllocated = containingBlock->wasAllocated (); + containingBlockAllocation = *(containingBlock->getAllocation()); + + addWidgetInFlow (containingBlock, NULL, 0); +} + +OutOfFlowMgr::~OutOfFlowMgr () +{ + //printf ("OutOfFlowMgr::~OutOfFlowMgr\n"); + + delete leftFloatsCB; + delete rightFloatsCB; + + // Order is important: tbInfosByTextblock is owner of the instances + // of TBInfo.tbInfosByTextblock + delete tbInfos; + delete tbInfosByTextblock; + + delete floatsByWidget; + + // Order is important, since the instances of Float are owned by + // leftFloatsAll and rightFloatsAll, so these should be deleted + // last. + delete leftFloatsAll; + delete rightFloatsAll; + + DBG_OBJ_DELETE (); +} + +void OutOfFlowMgr::sizeAllocateStart (Textblock *caller, Allocation *allocation) +{ + DBG_OBJ_ENTER ("resize.oofm", 0, "sizeAllocateStart", + "%p, (%d, %d, %d * (%d + %d))", + caller, allocation->x, allocation->y, allocation->width, + allocation->ascent, allocation->descent); + + getTextblock(caller)->allocation = *allocation; + getTextblock(caller)->wasAllocated = true; + + if (caller == containingBlock) { + // In the size allocation process, the *first* OOFM method + // called is sizeAllocateStart, with the containing block as an + // argument. So this is the correct point to initialize size + // allocation. + + containingBlockAllocation = *allocation; + containingBlockWasAllocated = true; + + // Move floats from GB lists to the one CB list. + moveFromGBToCB (LEFT); + moveFromGBToCB (RIGHT); + + // These attributes are used to keep track which floats have + // been allocated (referring to leftFloatsCB and rightFloatsCB). + lastAllocatedLeftFloat = lastAllocatedRightFloat = -1; + } + + DBG_OBJ_LEAVE (); +} + +void OutOfFlowMgr::sizeAllocateEnd (Textblock *caller) +{ + DBG_OBJ_ENTER ("resize.oofm", 0, "sizeAllocateEnd", "%p", caller); + + // (Later, absolutely positioned blocks have to be allocated.) + + if (caller != containingBlock) { + // Allocate all floats "before" this textblock. + sizeAllocateFloats (LEFT, leftFloatsCB->findFloatIndex (caller, -1)); + sizeAllocateFloats (RIGHT, rightFloatsCB->findFloatIndex (caller, -1)); + } + + // The checks below do not cover "clear position" in all cases, so + // this is done here separately. This position is stored in TBInfo + // and calculated at this points; changes will be noticed to the + // textblock. + TBInfo *tbInfo = getTextblock (caller); + int newClearPosition = calcClearPosition (caller); + if (newClearPosition != tbInfo->clearPosition) { + tbInfo->clearPosition = newClearPosition; + caller->clearPositionChanged (); + } + + if (caller == containingBlock) { + // In the size allocation process, the *last* OOFM method called + // is sizeAllocateEnd, with the containing block as an + // argument. So this is the correct point to finish size + // allocation. + + // Allocate all remaining floats. + sizeAllocateFloats (LEFT, leftFloatsCB->size () - 1); + sizeAllocateFloats (RIGHT, rightFloatsCB->size () - 1); + + // Check changes of both textblocks and floats allocation. (All + // is checked by hasRelationChanged (...).) + for (lout::container::typed::Iterator<TypedPointer <Textblock> > it = + tbInfosByTextblock->iterator (); + it.hasNext (); ) { + TypedPointer <Textblock> *key = it.getNext (); + TBInfo *tbInfo = tbInfosByTextblock->get (key); + Textblock *tb = key->getTypedValue(); + + int minFloatPos; + Widget *minFloat; + if (hasRelationChanged (tbInfo, &minFloatPos, &minFloat)) + tb->borderChanged (minFloatPos, minFloat); + } + + checkAllocatedFloatCollisions (LEFT); + checkAllocatedFloatCollisions (RIGHT); + + // Store some information for later use. + for (lout::container::typed::Iterator<TypedPointer <Textblock> > it = + tbInfosByTextblock->iterator (); + it.hasNext (); ) { + TypedPointer <Textblock> *key = it.getNext (); + TBInfo *tbInfo = tbInfosByTextblock->get (key); + Textblock *tb = key->getTypedValue(); + + tbInfo->updateAllocation (); + tbInfo->lineBreakWidth = tb->getLineBreakWidth (); + } + + // There are cases where some allocated floats (TODO: later also + // absolutely positioned elements?) exceed the CB allocation. + bool sizeChanged = doFloatsExceedCB (LEFT) || doFloatsExceedCB (RIGHT); + + // Similar for extremes. (TODO: here also absolutely positioned + // elements?) + bool extremesChanged = + haveExtremesChanged (LEFT) || haveExtremesChanged (RIGHT); + + for (int i = 0; i < leftFloatsCB->size(); i++) + leftFloatsCB->get(i)->updateAllocation (); + + for (int i = 0; i < rightFloatsCB->size(); i++) + rightFloatsCB->get(i)->updateAllocation (); + + if (sizeChanged || extremesChanged) + containingBlock->oofSizeChanged (extremesChanged); + } + + DBG_OBJ_LEAVE (); +} + +void OutOfFlowMgr::containerSizeChangedForChildren () +{ + DBG_OBJ_ENTER0 ("resize", 0, "containerSizeChangedForChildren"); + + DBG_OBJ_MSGF ("resize", 0, "%d left floats, %d right floats", + leftFloatsAll->size (), rightFloatsAll->size ()); + + for (int i = 0; i < leftFloatsAll->size (); i++) + leftFloatsAll->get(i)->getWidget()->containerSizeChanged (); + for (int i = 0; i < rightFloatsAll->size (); i++) + rightFloatsAll->get(i)->getWidget()->containerSizeChanged (); + + DBG_OBJ_LEAVE (); +} + +bool OutOfFlowMgr::hasRelationChanged (TBInfo *tbInfo, int *minFloatPos, + Widget **minFloat) +{ + DBG_OBJ_ENTER ("resize.oofm", 0, "hasRelationChanged", + "<i>widget:</i> %p, ...", tbInfo->getWidget ()); + + int leftMinPos, rightMinPos; + Widget *leftMinFloat, *rightMinFloat; + bool c1 = + hasRelationChanged (tbInfo, LEFT, &leftMinPos, &leftMinFloat); + bool c2 = + hasRelationChanged (tbInfo, RIGHT, &rightMinPos, &rightMinFloat); + if (c1 || c2) { + if (!c1) { + *minFloatPos = rightMinPos; + *minFloat = rightMinFloat; + } else if (!c2) { + *minFloatPos = leftMinPos; + *minFloat = leftMinFloat; + } else { + if (leftMinPos < rightMinPos) { + *minFloatPos = leftMinPos; + *minFloat = leftMinFloat; + } else{ + *minFloatPos = rightMinPos; + *minFloat = rightMinFloat; + } + } + } + + if (c1 || c2) + DBG_OBJ_MSGF ("resize.oofm", 1, + "has changed: minFloatPos = %d, minFloat = %p", + *minFloatPos, *minFloat); + else + DBG_OBJ_MSG ("resize.oofm", 1, "has not changed"); + + DBG_OBJ_LEAVE (); + return c1 || c2; +} + +bool OutOfFlowMgr::hasRelationChanged (TBInfo *tbInfo, Side side, + int *minFloatPos, Widget **minFloat) +{ + DBG_OBJ_ENTER ("resize.oofm", 0, "hasRelationChanged", + "<i>widget:</i> %p, %s, ...", + tbInfo->getWidget (), side == LEFT ? "LEFT" : "RIGHT"); + + SortedFloatsVector *list = side == LEFT ? leftFloatsCB : rightFloatsCB; + bool changed = false; + + for (int i = 0; i < list->size(); i++) { + // TODO binary search? + Float *vloat = list->get(i); + int floatPos; + + if (tbInfo->getTextblock () == vloat->generatingBlock) + DBG_OBJ_MSGF ("resize.oofm", 1, + "not checking (generating!) textblock %p against float " + "%p", tbInfo->getWidget (), vloat->getWidget ()); + else { + Allocation *gba = getAllocation (vloat->generatingBlock); + + int newFlx = + calcFloatX (vloat, side, + gba->x - containingBlockAllocation.x, gba->width, + vloat->generatingBlock->getLineBreakWidth ()); + int newFly = vloat->generatingBlock->getAllocation()->y + - containingBlockAllocation.y + vloat->yReal; + + DBG_OBJ_MSGF ("resize.oofm", 1, + "checking textblock %p against float %p", + tbInfo->getWidget (), vloat->getWidget ()); + DBG_OBJ_MSG_START (); + + if (hasRelationChanged (tbInfo->wasThenAllocated (), + tbInfo->getOldXCB (), tbInfo->getOldYCB (), + tbInfo->getNewWidth (), + tbInfo->getNewHeight (), + tbInfo->getNewXCB (), tbInfo->getNewYCB (), + tbInfo->getNewWidth (), + tbInfo->getNewHeight (), + vloat->wasThenAllocated (), + // When not allocated before, these values + // are undefined, but this does not matter, + // since they are neither used. + vloat->getOldXCB (), vloat->getOldYCB (), + vloat->getOldWidth (), vloat->getOldHeight (), + newFlx, newFly, vloat->size.width, + vloat->size.ascent + vloat->size.descent, + side, &floatPos)) { + if (!changed || floatPos < *minFloatPos) { + *minFloatPos = floatPos; + *minFloat = vloat->getWidget (); + } + changed = true; + } else + DBG_OBJ_MSG ("resize.oofm", 0, "No."); + + DBG_OBJ_MSG_END (); + } + + // All floarts are searched, to find the minimum. TODO: Are + // floats sorted, so this can be shortened? (The first is the + // minimum?) + } + + if (changed) + DBG_OBJ_MSGF ("resize.oofm", 1, + "has changed: minFloatPos = %d, minFloat = %p", + *minFloatPos, *minFloat); + else + DBG_OBJ_MSG ("resize.oofm", 1, "has not changed"); + + DBG_OBJ_LEAVE (); + return changed; +} + +/** + * \brief ... + * + * All coordinates are given relative to the CB. *floatPos is relative + * to the TB, and may be negative. + */ +bool OutOfFlowMgr::hasRelationChanged (bool oldTBAlloc, + int oldTBx, int oldTBy, int oldTBw, + int oldTBh, int newTBx, int newTBy, + int newTBw, int newTBh, + bool oldFlAlloc, + int oldFlx, int oldFly, int oldFlw, + int oldFlh, int newFlx, int newFly, + int newFlw, int newFlh, + Side side, int *floatPos) +{ + DBG_OBJ_ENTER ("resize.oofm", 0, "hasRelationChanged", + "<i>see below</i>, %s, ...", side == LEFT ? "LEFT" : "RIGHT"); + + if (oldTBAlloc) + DBG_OBJ_MSGF ("resize.oofm", 1, "old TB: %d, %d; %d * %d", + oldTBx, oldTBy, oldTBw, oldTBh); + else + DBG_OBJ_MSG ("resize.oofm", 1, "old TB: undefined"); + DBG_OBJ_MSGF ("resize.oofm", 1, "new TB: %d, %d; %d * %d", + newTBx, newTBy, newTBw, newTBh); + + if (oldFlAlloc) + DBG_OBJ_MSGF ("resize.oofm", 1, "old Fl: %d, %d; %d * %d", + oldFlx, oldFly, oldFlw, oldFlh); + else + DBG_OBJ_MSG ("resize.oofm", 1, "old Fl: undefined"); + DBG_OBJ_MSGF ("resize.oofm", 1, "new Fl: %d, %d; %d * %d", + newFlx, newFly, newFlw, newFlh); + + bool result; + if (oldTBAlloc && oldFlAlloc) { + bool oldCov = oldFly + oldFlh > oldTBy && oldFly < oldTBy + oldTBh; + bool newCov = newFly + newFlh > newTBy && newFly < newTBy + newTBh; + + DBG_OBJ_MSGF ("resize.oofm", 1, "covered? then: %s, now: %s.", + oldCov ? "yes" : "no", newCov ? "yes" : "no"); + DBG_OBJ_MSG_START (); + + if (oldCov && newCov) { + int yOld = oldFly - oldTBy, yNew = newFly - newTBy; + if (yOld == yNew) { + DBG_OBJ_MSGF ("resize.oofm", 2, + "old (%d - %d) and new (%d - %d) position equal: %d", + oldFly, oldTBy, newFly, newTBy, yOld); + + // Float position has not changed, but perhaps the amout + // how far the float reaches into the TB. (TODO: + // Generally, not only here, it could be tested whether + // the float reaches into the TB at all.) + int wOld, wNew; + if (side == LEFT) { + wOld = oldFlx + oldFlw - oldTBx; + wNew = newFlx + newFlw - newTBx; + } else { + wOld = oldTBx + oldTBw - oldFlx; + wNew = newTBx + newTBw - newFlx; + } + + DBG_OBJ_MSGF ("resize.oofm", 2, "wOld = %d, wNew = %d\n", + wOld, wNew); + + if (wOld == wNew) { + if (oldFlh == newFlh) + result = false; + else { + // Only heights of floats changed. Relevant only + // from bottoms of float. + *floatPos = min (yOld + oldFlh, yNew + newFlh); + result = true; + } + } else { + *floatPos = yOld; + result = true; + } + } else { + DBG_OBJ_MSGF ("resize.oofm", 2, + "old (%d - %d = %d) and new (%d - %d = %d) position " + "different", + oldFly, oldTBy, yOld, newFly, newTBy, yNew); + *floatPos = min (yOld, yNew); + result = true; + } + } else if (oldCov) { + *floatPos = oldFly - oldTBy; + result = true; + DBG_OBJ_MSGF ("resize.oofm", 2, + "returning old position: %d - %d = %d", oldFly, oldTBy, + *floatPos); + } else if (newCov) { + *floatPos = newFly - newTBy; + result = true; + DBG_OBJ_MSGF ("resize.oofm", 2, + "returning new position: %d - %d = %d", newFly, newTBy, + *floatPos); + } else + result = false; + + DBG_OBJ_MSG_END (); + } else { + // Not allocated before: ignore all old values, only check whether + // TB is covered by Float. + if (newFly + newFlh > newTBy && newFly < newTBy + newTBh) { + *floatPos = newFly - newTBy; + result = true; + } else + result = false; + } + + if (result) + DBG_OBJ_MSGF ("resize.oofm", 1, "has changed: floatPos = %d", + *floatPos); + else + DBG_OBJ_MSG ("resize.oofm", 1, "has not changed"); + + DBG_OBJ_LEAVE (); + + return result; +} + +void OutOfFlowMgr::checkAllocatedFloatCollisions (Side side) +{ + // In some cases, the collision detection in tellPosition() is + // based on the wrong allocations. Here (just after all Floats have + // been allocated), we correct this. + + // TODO In some cases this approach is rather slow, causing a too + // long queueResize() cascade. + + DBG_OBJ_ENTER ("resize.oofm", 0, "checkAllocatedFloatCollisions", "%s", + side == LEFT ? "LEFT" : "RIGHT"); + + SortedFloatsVector *list = side == LEFT ? leftFloatsCB : rightFloatsCB; + SortedFloatsVector *oppList = side == LEFT ? rightFloatsCB : leftFloatsCB; + + // While iterating through the list of floats to be checked, we + // iterate equally through the list of the opposite floats, using + // this index: + int oppIndex = 0; + + for (int index = 0; index < list->size (); index++) { + Float *vloat = list->get(index); + bool needsChange = false; + int yRealNew = INT_MAX; + + // Same side. + if (index >= 1) { + Float *other = list->get(index - 1); + DBG_OBJ_MSGF ("resize.oofm", 1, + "same side: checking %p (#%d, GB: %p) against " + "%p (#%d, GB: %p)", + vloat->getWidget (), index, vloat->generatingBlock, + other->getWidget (), index - 1, other->generatingBlock); + + if (vloat->generatingBlock != other->generatingBlock) { + int yRealNewSame; + if (collidesV (vloat, other, CB, &yRealNewSame, true)) { + DBG_OBJ_MSGF ("resize.oofm", 1, + "=> collides, new yReal = %d (old: %d)", + yRealNewSame, vloat->yReal); + if (vloat->yReal != yRealNewSame) { + needsChange = true; + yRealNew = min (yRealNew, yRealNewSame); + } + } else + DBG_OBJ_MSG ("resize.oofm", 1, "=> no collision"); + } + } + + if (oppList->size () > 0) { + // Other side. Iterate to next float on the other side, + // before this float. + while (oppIndex + 1 < oppList->size () && + oppList->get(oppIndex + 1)->sideSpanningIndex + < vloat->sideSpanningIndex) + oppIndex++; + + if (oppList->get(oppIndex)->sideSpanningIndex + < vloat->sideSpanningIndex) { + int oppIndexTmp = oppIndex, yRealNewOpp; + + // Aproach is similar to tellPosition(); see comments + // there. Again, loop as long as the vertical dimensions test + // is positive (and, of course, there are floats), ... + for (bool foundColl = false; + !foundColl && oppIndexTmp >= 0 && + collidesV (vloat, oppList->get (oppIndexTmp), CB, + &yRealNewOpp, true); + oppIndexTmp--) { + DBG_OBJ_MSGF ("resize.oofm", 1, + "opposite side (after collision (v) test): " + "checking %p (#%d/%d, GB: %p) against " + "%p (#%d/%d, GB: %p)", + vloat->getWidget (), index, + vloat->sideSpanningIndex, + vloat->generatingBlock, + oppList->get(oppIndexTmp)->getWidget (), + oppList->get(oppIndexTmp)->getIndex (CB), + oppList->get(oppIndexTmp)->sideSpanningIndex, + oppList->get(oppIndexTmp)->generatingBlock); + + // ... but stop the loop as soon as the horizontal dimensions + // test is positive. + if (collidesH (vloat, oppList->get (oppIndexTmp), CB)) { + DBG_OBJ_MSGF ("resize.oofm", 1, + "=> collides (h), new yReal = %d (old: %d)", + yRealNewOpp, vloat->yReal); + foundColl = true; + if (vloat->yReal != yRealNewOpp) { + needsChange = true; + yRealNew = min (yRealNew, yRealNewOpp); + } + } else + DBG_OBJ_MSG ("resize.oofm", 1, "=> no collision (h)"); + } + } + } + + if (needsChange) + vloat->generatingBlock->borderChanged (min (vloat->yReal, yRealNew), + vloat->getWidget ()); + } + + DBG_OBJ_LEAVE (); +} + +bool OutOfFlowMgr::doFloatsExceedCB (Side side) +{ + DBG_OBJ_ENTER ("resize.oofm", 0, "doFloatsExceedCB", "%s", + side == LEFT ? "LEFT" : "RIGHT"); + + // This method is called to determine whether the *requisition* of + // the CB must be recalculated. So, we check the float allocations + // against the *requisition* of the CB, which may (e. g. within + // tables) differ from the new allocation. (Generally, a widget may + // allocated at a different size.) + core::Requisition cbReq; + containingBlock->sizeRequest (&cbReq); + + SortedFloatsVector *list = side == LEFT ? leftFloatsCB : rightFloatsCB; + bool exceeds = false; + + DBG_OBJ_MSG_START (); + + for (int i = 0; i < list->size () && !exceeds; i++) { + Float *vloat = list->get (i); + if (vloat->getWidget()->wasAllocated ()) { + Allocation *fla = vloat->getWidget()->getAllocation (); + DBG_OBJ_MSGF ("resize.oofm", 2, + "Does FlA = (%d, %d, %d * %d) exceed CBA = " + "(%d, %d, %d * %d)?", + fla->x, fla->y, fla->width, fla->ascent + fla->descent, + containingBlockAllocation.x, containingBlockAllocation.y, + cbReq.width, cbReq.ascent + cbReq.descent); + if (fla->x + fla->width > containingBlockAllocation.x + cbReq.width || + fla->y + fla->ascent + fla->descent + > containingBlockAllocation.y + cbReq.ascent + cbReq.descent) { + exceeds = true; + DBG_OBJ_MSG ("resize.oofm", 2, "Yes."); + } else + DBG_OBJ_MSG ("resize.oofm", 2, "No."); + } + } + + DBG_OBJ_MSG_END (); + + DBG_OBJ_MSGF ("resize.oofm", 1, "=> %s", exceeds ? "true" : "false"); + DBG_OBJ_LEAVE (); + + return exceeds; +} + +bool OutOfFlowMgr::haveExtremesChanged (Side side) +{ + DBG_OBJ_ENTER ("resize.oofm", 0, "haveExtremesChanged", "%s", + side == LEFT ? "LEFT" : "RIGHT"); + + // This is quite different from doFloatsExceedCB, since there is no + // counterpart to getExtremes, as sizeAllocate is a counterpart to + // sizeRequest. So we have to determine whether the allocation has + // changed the extremes, which is done by examining the part of the + // allocation which is part of the extremes calculation (see + // getFloatsExtremes). Changes of the extremes are handled by the + // normal queueResize mechanism. + + SortedFloatsVector *list = side == LEFT ? leftFloatsCB : rightFloatsCB; + bool changed = false; + + for (int i = 0; i < list->size () && !changed; i++) { + Float *vloat = list->get (i); + // When the GB is the CB, an allocation change does not play a + // role here. + if (vloat->generatingBlock != containingBlock) { + if (!vloat->wasThenAllocated () && vloat->isNowAllocated ()) + changed = true; + else { + // This method is called within sizeAllocateEnd, where + // containinBlock->getAllocation() (old value) and + // containinBlockAllocation (new value) are different. + + Allocation *oldCBA = containingBlock->getAllocation (); + Allocation *newCBA = &containingBlockAllocation; + + // Compare also to getFloatsExtremes. The box difference + // of the GB (from style) has not changed in this context, + // so it is ignored. + + int oldDiffLeft = vloat->getOldXCB (); + int newDiffLeft = vloat->getNewXCB (); + int oldDiffRight = + oldCBA->width - (vloat->getOldXCB () + vloat->getOldWidth ()); + int newDiffRight = + newCBA->width - (vloat->getNewXCB () + vloat->getNewWidth ()); + + if (// regarding minimum + (side == LEFT && oldDiffLeft != newDiffLeft) || + (side == RIGHT && oldDiffRight != newDiffRight) || + // regarding maximum + oldDiffLeft + oldDiffRight != newDiffLeft + newDiffRight) + changed = true; + } + } + } + + DBG_OBJ_MSGF ("resize.oofm", 1, "=> %s", changed ? "true" : "false"); + DBG_OBJ_LEAVE (); + + return changed; +} + +void OutOfFlowMgr::moveFromGBToCB (Side side) +{ + DBG_OBJ_ENTER ("oofm.resize", 0, "moveFromGBToCB", "%s", + side == LEFT ? "LEFT" : "RIGHT"); + + SortedFloatsVector *dest = side == LEFT ? leftFloatsCB : rightFloatsCB; + int *floatsMark = side == LEFT ? &leftFloatsMark : &rightFloatsMark; + + for (int mark = 0; mark <= *floatsMark; mark++) + for (lout::container::typed::Iterator<TBInfo> it = tbInfos->iterator (); + it.hasNext (); ) { + TBInfo *tbInfo = it.getNext (); + SortedFloatsVector *src = + side == LEFT ? tbInfo->leftFloatsGB : tbInfo->rightFloatsGB; + for (int i = 0; i < src->size (); i++) { + Float *vloat = src->get (i); + // "vloat->indexCBList == -1": prevent copying the vloat twice. + if (vloat->indexCBList == -1 && vloat->mark == mark) { + dest->put (vloat); + DBG_OBJ_MSGF ("oofm.resize", 1, + "moving float %p (mark %d) to CB list\n", + vloat->getWidget (), vloat->mark); + DBG_OBJ_SET_NUM (side == LEFT ? + "leftFloatsCB.size" : "rightFloatsCB.size", + dest->size()); + DBG_OBJ_ARRATTRSET_PTR (side == LEFT ? + "leftFloatsCB" : "rightFloatsCB", + dest->size() - 1, "widget", + vloat->getWidget ()); + + } + } + } + + *floatsMark = 0; + + DBG_OBJ_LEAVE (); +} + +void OutOfFlowMgr::sizeAllocateFloats (Side side, int newLastAllocatedFloat) +{ + SortedFloatsVector *list = side == LEFT ? leftFloatsCB : rightFloatsCB; + int *lastAllocatedFloat = + side == LEFT ? &lastAllocatedLeftFloat : &lastAllocatedRightFloat; + + DBG_OBJ_ENTER ("resize.oofm", 0, "sizeAllocateFloats", + "%s, [%d ->] %d [size = %d]", + side == LEFT ? "LEFT" : "RIGHT", *lastAllocatedFloat, + newLastAllocatedFloat, list->size ()); + + Allocation *cba = &containingBlockAllocation; + + for (int i = *lastAllocatedFloat + 1; i <= newLastAllocatedFloat; i++) { + Float *vloat = list->get(i); + ensureFloatSize (vloat); + + Allocation *gba = getAllocation (vloat->generatingBlock); + int lineBreakWidth = vloat->generatingBlock->getLineBreakWidth(); + + Allocation childAllocation; + childAllocation.x = cba->x + + calcFloatX (vloat, side, gba->x - cba->x, gba->width, lineBreakWidth); + childAllocation.y = gba->y + vloat->yReal; + childAllocation.width = vloat->size.width; + childAllocation.ascent = vloat->size.ascent; + childAllocation.descent = vloat->size.descent; + + vloat->getWidget()->sizeAllocate (&childAllocation); + } + + *lastAllocatedFloat = newLastAllocatedFloat; + + DBG_OBJ_LEAVE (); +} + + +/** + * \brief ... + * + * gbX is given relative to the CB, as is the return value. + */ +int OutOfFlowMgr::calcFloatX (Float *vloat, Side side, int gbX, int gbWidth, + int gbLineBreakWidth) +{ + DBG_OBJ_ENTER ("resize.common", 0, "calcFloatX", "%p, %s, %d, %d, %d", + vloat->getWidget (), side == LEFT ? "LEFT" : "RIGHT", gbX, + gbWidth, gbLineBreakWidth); + int x; + + switch (side) { + case LEFT: + // Left floats are always aligned on the left side of the + // generator (content, not allocation) ... + x = gbX + vloat->generatingBlock->getStyle()->boxOffsetX(); + DBG_OBJ_MSGF ("resize.common", 1, "left: x = %d + %d = %d", + gbX, vloat->generatingBlock->getStyle()->boxOffsetX(), x); + // ... but when the float exceeds the line break width of the + // container, it is corrected (but not left of the container). + // This way, we save space and, especially within tables, avoid + // some problems. + if (wasAllocated (containingBlock) && + x + vloat->size.width > containingBlock->getLineBreakWidth ()) { + x = max (0, containingBlock->getLineBreakWidth () - vloat->size.width); + DBG_OBJ_MSGF ("resize.common", 1, + "corrected to: max (0, %d - %d) = %d", + containingBlock->getLineBreakWidth (), vloat->size.width, + x); + } + break; + + case RIGHT: + // Similar for right floats, but in this case, floats are + // shifted to the right when they are too big (instead of + // shifting the generator to the right). + + // Notice that not the actual width, but the line break width is + // used. (This changed for GROWS, where the width of a textblock + // is often smaller that the line break.) + + x = max (gbX + gbLineBreakWidth - vloat->size.width + - vloat->generatingBlock->getStyle()->boxRestWidth(), + // Do not exceed CB allocation: + 0); + DBG_OBJ_MSGF ("resize.common", 1, "x = max (%d + %d - %d - %d, 0) = %d", + gbX, gbLineBreakWidth, vloat->size.width, + vloat->generatingBlock->getStyle()->boxRestWidth(), x); + break; + + default: + assertNotReached (); + x = 0; + break; + } + + DBG_OBJ_LEAVE (); + return x; +} + + +void OutOfFlowMgr::draw (View *view, Rectangle *area) +{ + drawFloats (leftFloatsCB, view, area); + drawFloats (rightFloatsCB, view, area); +} + +void OutOfFlowMgr::drawFloats (SortedFloatsVector *list, View *view, + Rectangle *area) +{ + // This could be improved, since the list is sorted: search the + // first float fitting into the area, and iterate until one is + // found below the area. + for (int i = 0; i < list->size(); i++) { + Float *vloat = list->get(i); + Rectangle childArea; + if (vloat->getWidget()->intersects (area, &childArea)) + vloat->getWidget()->draw (view, &childArea); + } +} + +void OutOfFlowMgr::addWidgetInFlow (Textblock *textblock, + Textblock *parentBlock, int externalIndex) +{ + //printf ("[%p] addWidgetInFlow (%p, %p, %d)\n", + // containingBlock, textblock, parentBlock, externalIndex); + + TBInfo *tbInfo = + new TBInfo (this, textblock, + parentBlock ? getTextblock (parentBlock) : NULL, + externalIndex); + tbInfo->index = tbInfos->size(); + + tbInfos->put (tbInfo); + tbInfosByTextblock->put (new TypedPointer<Textblock> (textblock), tbInfo); +} + +void OutOfFlowMgr::addWidgetOOF (Widget *widget, Textblock *generatingBlock, + int externalIndex) +{ + DBG_OBJ_ENTER ("construct.oofm", 0, "addWidgetOOF", "%p, %p, %d", + widget, generatingBlock, externalIndex); + + if (isWidgetFloat (widget)) { + TBInfo *tbInfo = getTextblock (generatingBlock); + + Float *vloat = new Float (this, widget, generatingBlock, externalIndex); + + // Note: Putting the float first in the GB list, and then, + // possibly into the CB list (in that order) will trigger + // setting Float::inCBList to the right value. + + switch (widget->getStyle()->vloat) { + case FLOAT_LEFT: + leftFloatsAll->put (vloat); + DBG_OBJ_SET_NUM ("leftFloatsAll.size", leftFloatsAll->size()); + DBG_OBJ_ARRATTRSET_PTR ("leftFloatsAll", leftFloatsAll->size() - 1, + "widget", vloat->getWidget ()); + + widget->parentRef = createRefLeftFloat (leftFloatsAll->size() - 1); + DBG_OBJ_SET_NUM_O (widget, "parentRef", widget->parentRef); + tbInfo->leftFloatsGB->put (vloat); + + if (wasAllocated (generatingBlock)) { + leftFloatsCB->put (vloat); + DBG_OBJ_SET_NUM ("leftFloatsCB.size", leftFloatsCB->size()); + DBG_OBJ_ARRATTRSET_PTR ("leftFloatsCB", leftFloatsCB->size() - 1, + "widget", vloat->getWidget ()); + } else { + if (tbInfo->index < lastLeftTBIndex) + leftFloatsMark++; + + vloat->mark = leftFloatsMark; + //printf ("[%p] adding left float %p (%s %p, mark %d) to GB list " + // "(index %d, last = %d)\n", + // containingBlock, vloat, widget->getClassName(), widget, + // vloat->mark, tbInfo->index, lastLeftTBIndex); + + lastLeftTBIndex = tbInfo->index; + } + break; + + case FLOAT_RIGHT: + rightFloatsAll->put (vloat); + DBG_OBJ_SET_NUM ("rightFloatsAll.size", rightFloatsAll->size()); + DBG_OBJ_ARRATTRSET_PTR ("rightFloatsAll", rightFloatsAll->size() - 1, + "widget", vloat->getWidget ()); + + widget->parentRef = createRefRightFloat (rightFloatsAll->size() - 1); + DBG_OBJ_SET_NUM_O (widget, "parentRef", widget->parentRef); + tbInfo->rightFloatsGB->put (vloat); + + if (wasAllocated (generatingBlock)) { + rightFloatsCB->put (vloat); + DBG_OBJ_SET_NUM ("rightFloatsCB.size", rightFloatsCB->size()); + DBG_OBJ_ARRATTRSET_PTR ("rightFloatsCB", rightFloatsCB->size() - 1, + "widget", vloat->getWidget ()); + } else { + if (tbInfo->index < lastRightTBIndex) + rightFloatsMark++; + + vloat->mark = rightFloatsMark; + //printf ("[%p] adding right float %p (%s %p, mark %d) to GB list " + // "(index %d, last = %d)\n", + // containingBlock, vloat, widget->getClassName(), widget, + // vloat->mark, tbInfo->index, lastRightTBIndex); + + lastRightTBIndex = tbInfo->index; + } + + break; + + default: + assertNotReached(); + } + + // "sideSpanningIndex" is only compared, so this simple + // assignment is sufficient; differenciation between GB and CB + // lists is not neccessary. TODO: Can this also be applied to + // "index", to simplify the current code? Check: where is + // "index" used. + vloat->sideSpanningIndex = + leftFloatsAll->size() + rightFloatsAll->size() - 1; + + floatsByWidget->put (new TypedPointer<Widget> (widget), vloat); + } else + // May be extended. + assertNotReached(); + + DBG_OBJ_LEAVE (); +} + +void OutOfFlowMgr::moveExternalIndices (Textblock *generatingBlock, + int oldStartIndex, int diff) +{ + TBInfo *tbInfo = getTextblock (generatingBlock); + moveExternalIndices (tbInfo->leftFloatsGB, oldStartIndex, diff); + moveExternalIndices (tbInfo->rightFloatsGB, oldStartIndex, diff); +} + +void OutOfFlowMgr::moveExternalIndices (SortedFloatsVector *list, + int oldStartIndex, int diff) +{ + // Could be faster with binary search, but the GB (not CB!) lists + // should be rather small. + for (int i = 0; i < list->size(); i++) { + Float *vloat = list->get(i); + if (vloat->externalIndex >= oldStartIndex) { + vloat->externalIndex += diff; + DBG_OBJ_SET_NUM_O (vloat->getWidget (), "<Float>.externalIndex", + vloat->externalIndex); + } + } +} + +OutOfFlowMgr::Float *OutOfFlowMgr::findFloatByWidget (Widget *widget) +{ + TypedPointer <Widget> key (widget); + Float *vloat = floatsByWidget->get (&key); + assert (vloat != NULL); + return vloat; +} + +void OutOfFlowMgr::markSizeChange (int ref) +{ + DBG_OBJ_ENTER ("resize.oofm", 0, "markSizeChange", "%d", ref); + + if (isRefFloat (ref)) { + Float *vloat; + + if (isRefLeftFloat (ref)) { + int i = getFloatIndexFromRef (ref); + vloat = leftFloatsAll->get (i); + //printf (" => left float %d\n", i); + } else if (isRefRightFloat (ref)) { + int i = getFloatIndexFromRef (ref); + vloat = rightFloatsAll->get (i); + //printf (" => right float %d\n", i); + } else { + assertNotReached(); + vloat = NULL; // compiler happiness + } + + vloat->dirty = vloat->sizeChangedSinceLastAllocation = true; + + DBG_OBJ_SET_BOOL_O (vloat->getWidget (), "<Float>.dirty", vloat->dirty); + DBG_OBJ_SET_BOOL_O (vloat->getWidget (), + "<Float>.sizeChangedSinceLastAllocation", + vloat->sizeChangedSinceLastAllocation); + + // The generating block is told directly about this. (Others later, in + // sizeAllocateEnd.) Could be faster (cf. hasRelationChanged, which + // differentiates many special cases), but the size is not known yet, + vloat->generatingBlock->borderChanged (vloat->yReal, vloat->getWidget ()); + } else + assertNotReached(); + + DBG_OBJ_LEAVE (); +} + + +void OutOfFlowMgr::markExtremesChange (int ref) +{ + // Nothing to do here. +} + +Widget *OutOfFlowMgr::getWidgetAtPoint (int x, int y, int level) +{ + Widget *childAtPoint = getFloatWidgetAtPoint (leftFloatsCB, x, y, level); + if (childAtPoint == NULL) + childAtPoint = getFloatWidgetAtPoint (rightFloatsCB, x, y, level); + return childAtPoint; +} + +Widget *OutOfFlowMgr::getFloatWidgetAtPoint (SortedFloatsVector *list, + int x, int y, int level) +{ + for (int i = 0; i < list->size(); i++) { + // Could use binary search to be faster. + Float *vloat = list->get(i); + if (vloat->getWidget()->wasAllocated ()) { + Widget *childAtPoint = + vloat->getWidget()->getWidgetAtPoint (x, y, level + 1); + if (childAtPoint) + return childAtPoint; + } + } + + return NULL; +} + +void OutOfFlowMgr::tellPosition (Widget *widget, int yReq) +{ + if (isWidgetFloat (widget)) + tellFloatPosition (widget, yReq); + + // Nothing to do for absolutely positioned blocks. +} + + +void OutOfFlowMgr::tellFloatPosition (Widget *widget, int yReq) +{ + DBG_OBJ_ENTER ("resize.oofm", 0, "tellFloatPosition", "%p, %d", + widget, yReq); + + assert (yReq >= 0); + + Float *vloat = findFloatByWidget(widget); + + SortedFloatsVector *listSame, *listOpp; + Side side; + getFloatsListsAndSide (vloat, &listSame, &listOpp, &side); + ensureFloatSize (vloat); + + // "yReal" may change due to collisions (see below). + vloat->yReq = vloat->yReal = yReq; + + DBG_OBJ_SET_NUM_O (vloat->getWidget (), "<Float>.yReq", vloat->yReq); + DBG_OBJ_SET_NUM_O (vloat->getWidget (), "<Float>.yReal", vloat->yReal); + + // Test collisions (on this side). Although there are (rare) cases + // where it could make sense, the horizontal dimensions are not + // tested; especially since searching and border calculation would + // be confused. For this reaspn, only the previous float is + // relevant. (Cf. below, collisions on the other side.) + int index = vloat->getIndex (listSame->type), yRealNew; + if (index >= 1 && + collidesV (vloat, listSame->get (index - 1), listSame->type, + &yRealNew, false)) { + vloat->yReal = yRealNew; + DBG_OBJ_SET_NUM_O (vloat->getWidget (), "<Float>.yReal", vloat->yReal); + } + + // Test collisions (on the opposite side). There are cases when + // more than one float has to be tested. Consider the following + // HTML snippet ("id" attribute only used for simple reference + // below, as #f1, #f2, and #f3): + // + // <div style="float:left" id="f1"> + // Left left left left left left left left left left. + // </div> + // <div style="float:left" id="f2">Also left.</div> + // <div style="float:right" id="f3">Right.</div> + // + // When displayed with a suitable window width (only slightly wider + // than the text within #f1), this should look like this: + // + // --------------------------------------------------------- + // | Left left left left left left left left left left. | + // | Also left. Right. | + // --------------------------------------------------------- + // + // Consider float #f3: a collision test with #f2, considering + // vertical dimensions, is positive, but not the test with + // horizontal dimensions (because #f2 and #f3 are too + // narrow). However, a collision has to be tested with #f1; + // otherwise #f3 and #f1 would overlap. + + int oppFloatIndex = + listOpp->findLastBeforeSideSpanningIndex (vloat->sideSpanningIndex); + // Generally, the rules are simple: loop as long as the vertical + // dimensions test is positive (and, of course, there are floats), + // ... + for (bool foundColl = false; + !foundColl && oppFloatIndex >= 0 && + collidesV (vloat, listOpp->get (oppFloatIndex), listSame->type, + &yRealNew, false); + oppFloatIndex--) { + // ... but stop the loop as soon as the horizontal dimensions + // test is positive. + if (collidesH (vloat, listOpp->get (oppFloatIndex), listSame->type)) { + vloat->yReal = yRealNew; + DBG_OBJ_SET_NUM_O (vloat->getWidget (), "<Float>.yReal", vloat->yReal); + foundColl = true; + } + } + + DBG_OBJ_MSGF ("resize.oofm", 1, "vloat->yReq = %d, vloat->yReal = %d", + vloat->yReq, vloat->yReal); + + DBG_OBJ_LEAVE (); +} + +bool OutOfFlowMgr::collidesV (Float *vloat, Float *other, SFVType type, + int *yReal, bool useAllocation) +{ + // Only checks vertical (possible) collisions, and only refers to + // vloat->yReal; never to vloat->allocation->y, even when the GBs are + // different. Used only in tellPosition. + + DBG_OBJ_ENTER ("resize.oofm", 0, "collidesV", "#%d [%p], #%d [%p], ...", + vloat->getIndex (type), vloat->getWidget (), + other->getIndex (type), other->getWidget ()); + + bool result; + + DBG_OBJ_MSGF ("resize.oofm", 1, "initial yReal = %d", vloat->yReal); + + if (vloat->generatingBlock == other->generatingBlock) { + ensureFloatSize (other); + int otherBottomGB = + other->yReal + other->size.ascent + other->size.descent; + + DBG_OBJ_MSGF ("resize.oofm", 1, + "same generators: otherBottomGB = %d + (%d + %d) = %d", + other->yReal, other->size.ascent, other->size.descent, + otherBottomGB); + + if (vloat->yReal < otherBottomGB) { + *yReal = otherBottomGB; + result = true; + } else + result = false; + } else { + // If the other float is not allocated, there is no collision. The + // allocation of this float (vloat) is not used at all. + if (!other->getWidget()->wasAllocated ()) + result = false; + else { + assert (wasAllocated (vloat->generatingBlock)); + Allocation *gba = getAllocation (vloat->generatingBlock), + *flaOther = other->getWidget()->getAllocation (); + + // We distinguish two cases (by different values of useAllocation): + // (i) within tellPosition, GB allocation + yReal is used for the + // y position of the other float, while (ii) in checkAllocatedFloat- + // Collisions, the float allocation is used. The latter is necessary + // by the definition of this method, the former increases performance, + // as compared to using the float allocation, in some cases, as in + // this: + // + // When '<div><div style="float:left">[Some text]</div></div>' is + // repeated n times, the resize idle function (Layout::resizeIdle) + // would be repeated roughly n times, when also in case (i) the float + // allocation is used, since for the collision test of float n with + // float n - 1, the allocation of float n - 1 does not yet reflect the + // collision test between n - 1 and n - 2, but yReal does for n - 1. + // + // On the other hand, the GB allocations will most likely more stable + // than the float allocations. + // + // Cases where this is incorrect will hopefully be rare, and, in any + // case, corrected in sizeAllocateEnd, either because hasRelation- + // Changed returns true, or in checkAllocatedFloatCollisions. + + int otherFloatY = useAllocation ? flaOther->y : + getAllocation(other->generatingBlock)->y + other->yReal; + int otherBottomGB = + otherFloatY + flaOther->ascent + flaOther->descent - gba->y; + + DBG_OBJ_MSGF ("resize.oofm", 1, + "different generators: " + "otherBottomGB = %d + (%d + %d) - %d = %d", + otherFloatY, flaOther->ascent, flaOther->descent, gba->y, + otherBottomGB); + + if (vloat->yReal < otherBottomGB) { + *yReal = otherBottomGB; + result = true; + } else + result = false; + } + } + + if (result) + DBG_OBJ_MSGF ("resize.oofm", 1, "collides: new yReal = %d", *yReal); + else + DBG_OBJ_MSG ("resize.oofm", 1, "does not collide"); + + DBG_OBJ_LEAVE (); + return result; +} + + +bool OutOfFlowMgr::collidesH (Float *vloat, Float *other, SFVType type) +{ + // Only checks horizontal collision. For a complete test, use + // collidesV (...) && collidesH (...). + bool collidesH; + + if (vloat->generatingBlock == other->generatingBlock) + collidesH = vloat->size.width + other->size.width + + vloat->generatingBlock->getStyle()->boxDiffWidth() + > vloat->generatingBlock->getLineBreakWidth(); + else { + // Again, if the other float is not allocated, there is no + // collision. Compare to collidesV. (But vloat->size is used + // here.) + if (!other->getWidget()->wasAllocated ()) + collidesH = false; + else { + assert (wasAllocated (vloat->generatingBlock)); + Allocation *gba = getAllocation (vloat->generatingBlock); + int vloatX = + calcFloatX (vloat, + vloat->getWidget()->getStyle()->vloat == FLOAT_LEFT ? + LEFT : RIGHT, + gba->x, gba->width, + vloat->generatingBlock->getLineBreakWidth ()); + + // Generally: right border of the left float > left border of + // the right float (all in canvas coordinates). + if (vloat->getWidget()->getStyle()->vloat == FLOAT_LEFT) + // "vloat" is left, "other" is right + collidesH = vloatX + vloat->size.width + > other->getWidget()->getAllocation()->x; + else + // "other" is left, "vloat" is right + collidesH = other->getWidget()->getAllocation()->x + + other->getWidget()->getAllocation()->width + > vloatX; + } + } + + return collidesH; +} + +void OutOfFlowMgr::getFloatsListsAndSide (Float *vloat, + SortedFloatsVector **listSame, + SortedFloatsVector **listOpp, + Side *side) +{ + TBInfo *tbInfo = getTextblock (vloat->generatingBlock); + + switch (vloat->getWidget()->getStyle()->vloat) { + case FLOAT_LEFT: + if (wasAllocated (vloat->generatingBlock)) { + if (listSame) *listSame = leftFloatsCB; + if (listOpp) *listOpp = rightFloatsCB; + } else { + if (listSame) *listSame = tbInfo->leftFloatsGB; + if (listOpp) *listOpp = tbInfo->rightFloatsGB; + } + if (side) *side = LEFT; + break; + + case FLOAT_RIGHT: + if (wasAllocated (vloat->generatingBlock)) { + if (listSame) *listSame = rightFloatsCB; + if (listOpp) *listOpp = leftFloatsCB; + } else { + if (listSame) *listSame = tbInfo->rightFloatsGB; + if (listOpp) *listOpp = tbInfo->leftFloatsGB; + } + if (side) *side = RIGHT; + break; + + default: + assertNotReached(); + } +} + +void OutOfFlowMgr::getSize (Requisition *cbReq, int *oofWidth, int *oofHeight) +{ + DBG_OBJ_ENTER0 ("resize.oofm", 0, "getSize"); + + int oofWidthtLeft, oofWidthRight, oofHeightLeft, oofHeightRight; + getFloatsSize (cbReq, LEFT, &oofWidthtLeft, &oofHeightLeft); + getFloatsSize (cbReq, RIGHT, &oofWidthRight, &oofHeightRight); + + *oofWidth = max (oofWidthtLeft, oofWidthRight); + *oofHeight = max (oofHeightLeft, oofHeightRight); + + DBG_OBJ_MSGF ("resize.oofm", 1, + "=> (l: %d, r: %d => %d) * (l: %d, r: %d => %d)", + oofWidthtLeft, oofWidthRight, *oofWidth, + oofHeightLeft, oofHeightRight, *oofHeight); + DBG_OBJ_LEAVE (); +} + +void OutOfFlowMgr::getFloatsSize (Requisition *cbReq, Side side, int *width, + int *height) +{ + DBG_OBJ_ENTER ("resize.oofm", 0, "getFloatsSize", "(%d * (%d + %d), %s, ...", + cbReq->width, cbReq->ascent, cbReq->descent, + side == LEFT ? "LEFT" : "RIGHT"); + + SortedFloatsVector *list = getFloatsListForTextblock (containingBlock, side); + + *width = *height = 0; + + DBG_OBJ_MSGF ("resize.oofm", 1, "%d floats on this side", list->size()); + + for (int i = 0; i < list->size(); i++) { + Float *vloat = list->get(i); + + DBG_OBJ_MSGF ("resize.oofm", 1, + "float %p has generator %p (container is %p)", + vloat->getWidget (), vloat->generatingBlock, + containingBlock); + + if (vloat->generatingBlock == containingBlock || + wasAllocated (vloat->generatingBlock)) { + ensureFloatSize (vloat); + int x, y; + + if (vloat->generatingBlock == containingBlock) { + x = calcFloatX (vloat, side, 0, cbReq->width, + vloat->generatingBlock->getLineBreakWidth ()); + y = vloat->yReal; + } else { + Allocation *gba = getAllocation(vloat->generatingBlock); + x = calcFloatX (vloat, side, + gba->x - containingBlockAllocation.x, gba->width, + vloat->generatingBlock->getLineBreakWidth ()); + y = gba->y - containingBlockAllocation.y + vloat->yReal; + } + + *width = max (*width, x + vloat->size.width); + *height = max (*height, y + vloat->size.ascent + vloat->size.descent); + + DBG_OBJ_MSGF ("resize.oofm", 1, + "considering float %p generated by %p: (%d + %d) * " + "(%d + (%d + %d)) => %d * %d", + vloat->getWidget (), vloat->generatingBlock, + x, vloat->size.width, + y, vloat->size.ascent, vloat->size.descent, + *width, *height); + } else + DBG_OBJ_MSGF ("resize.oofm", 1, + "considering float %p generated by %p: not allocated", + vloat->getWidget (), vloat->generatingBlock); + } + + DBG_OBJ_LEAVE (); +} + +void OutOfFlowMgr::getExtremes (Extremes *cbExtr, int *oofMinWidth, + int *oofMaxWidth) +{ + DBG_OBJ_ENTER ("resize.oofm", 0, "getExtremes", "(%d / %d), ...", + cbExtr->minWidth, cbExtr->maxWidth); + + int oofMinWidthtLeft, oofMinWidthRight, oofMaxWidthLeft, oofMaxWidthRight; + getFloatsExtremes (cbExtr, LEFT, &oofMinWidthtLeft, &oofMaxWidthLeft); + getFloatsExtremes (cbExtr, RIGHT, &oofMinWidthRight, &oofMaxWidthRight); + + *oofMinWidth = max (oofMinWidthtLeft, oofMinWidthRight); + *oofMaxWidth = max (oofMaxWidthLeft, oofMaxWidthRight); + + DBG_OBJ_MSGF ("resize.oofm", 1, + "=> (l: %d, r: %d => %d) / (l: %d, r: %d => %d)", + oofMinWidthtLeft, oofMinWidthRight, *oofMinWidth, + oofMaxWidthLeft, oofMaxWidthRight, *oofMaxWidth); + DBG_OBJ_LEAVE (); +} + +void OutOfFlowMgr::getFloatsExtremes (Extremes *cbExtr, Side side, + int *minWidth, int *maxWidth) +{ + DBG_OBJ_ENTER ("resize.oofm", 0, "getFloatsExtremes", "(%d / %d), %s, ...", + cbExtr->minWidth, cbExtr->maxWidth, + side == LEFT ? "LEFT" : "RIGHT"); + + *minWidth = *maxWidth = 0; + + SortedFloatsVector *list = getFloatsListForTextblock (containingBlock, side); + DBG_OBJ_MSGF ("resize.oofm", 1, "%d floats to be examined", list->size()); + + for (int i = 0; i < list->size(); i++) { + Float *vloat = list->get(i); + int leftDiff, rightDiff; + + if (getFloatDiffToCB (vloat, &leftDiff, &rightDiff)) { + Extremes extr; + vloat->getWidget()->getExtremes (&extr); + + DBG_OBJ_MSGF ("resize.oofm", 1, + "considering float %p generated by %p: %d / %d", + vloat->getWidget (), vloat->generatingBlock, + extr.minWidth, extr.maxWidth); + + // TODO: Or zero (instead of rightDiff) for right floats? + *minWidth = + max (*minWidth, + extr.minWidth + (side == LEFT ? leftDiff : rightDiff)); + *maxWidth = max (*maxWidth, extr.maxWidth + leftDiff + rightDiff); + + DBG_OBJ_MSGF ("resize.oofm", 1, " => %d / %d", *minWidth, *maxWidth); + } else + DBG_OBJ_MSGF ("resize.oofm", 1, + "considering float %p generated by %p: not allocated", + vloat->getWidget (), vloat->generatingBlock); + } + + DBG_OBJ_LEAVE (); +} + +// Returns "false" when borders cannot yet determined; *leftDiff and +// *rightDiff are undefined in this case. +bool OutOfFlowMgr::getFloatDiffToCB (Float *vloat, int *leftDiff, + int *rightDiff) +{ + DBG_OBJ_ENTER ("resize.oofm", 0, "getDiffToCB", + "float %p [generated by %p], ...", + vloat->getWidget (), vloat->generatingBlock); + + bool result; + + if (vloat->generatingBlock == containingBlock) { + *leftDiff = vloat->generatingBlock->getStyle()->boxOffsetX(); + *rightDiff = vloat->generatingBlock->getStyle()->boxRestWidth(); + result = true; + DBG_OBJ_MSGF ("resize.oofm", 1, + "GB == CB => leftDiff = %d, rightDiff = %d", + *leftDiff, *rightDiff); + } else if (wasAllocated (vloat->generatingBlock)) { + Allocation *gba = getAllocation(vloat->generatingBlock); + *leftDiff = gba->x - containingBlockAllocation.x + + vloat->generatingBlock->getStyle()->boxOffsetX(); + *rightDiff = + (containingBlockAllocation.x + containingBlockAllocation.width) + - (gba->x + gba->width) + + vloat->generatingBlock->getStyle()->boxRestWidth(); + result = true; + DBG_OBJ_MSGF ("resize.oofm", 1, + "GB != CB => leftDiff = %d - %d + %d = %d, " + "rightDiff = (%d + %d) - (%d + %d) + %d = %d", + gba->x, containingBlockAllocation.x, + vloat->generatingBlock->getStyle()->boxOffsetX(), + *leftDiff, containingBlockAllocation.x, + containingBlockAllocation.width, gba->x, gba->width, + vloat->generatingBlock->getStyle()->boxRestWidth(), + *rightDiff); + } else { + DBG_OBJ_MSG ("resize.oofm", 1, "GB != CB, and float not allocated"); + result = false; + } + + DBG_OBJ_LEAVE (); + return result; +} + +OutOfFlowMgr::TBInfo *OutOfFlowMgr::getTextblock (Textblock *textblock) +{ + TypedPointer<Textblock> key (textblock); + TBInfo *tbInfo = tbInfosByTextblock->get (&key); + assert (tbInfo); + return tbInfo; +} + +/** + * Get the left border for the vertical position of *y*, for a height + * of *h", based on floats; relative to the allocation of the calling + * textblock. + * + * The border includes marging/border/padding of the calling textblock + * but is 0 if there is no float, so a caller should also consider + * other borders. + */ +int OutOfFlowMgr::getLeftBorder (Textblock *textblock, int y, int h, + Textblock *lastGB, int lastExtIndex) +{ + int b = getBorder (textblock, LEFT, y, h, lastGB, lastExtIndex); + DBG_OBJ_MSGF ("border", 0, "left border (%p, %d, %d, %p, %d) => %d", + textblock, y, h, lastGB, lastExtIndex, b); + return b; +} + +/** + * Get the right border for the vertical position of *y*, for a height + * of *h*, based on floats. + * + * See also getLeftBorder(int, int); + */ +int OutOfFlowMgr::getRightBorder (Textblock *textblock, int y, int h, + Textblock *lastGB, int lastExtIndex) +{ + int b = getBorder (textblock, RIGHT, y, h, lastGB, lastExtIndex); + DBG_OBJ_MSGF ("border", 0, "right border (%p, %d, %d, %p, %d) => %d", + textblock, y, h, lastGB, lastExtIndex, b); + return b; +} + +int OutOfFlowMgr::getBorder (Textblock *textblock, Side side, int y, int h, + Textblock *lastGB, int lastExtIndex) +{ + DBG_OBJ_ENTER ("border", 0, "getBorder", "%p, %s, %d, %d, %p, %d", + textblock, side == LEFT ? "LEFT" : "RIGHT", y, h, + lastGB, lastExtIndex); + + SortedFloatsVector *list = getFloatsListForTextblock (textblock, side); + int last; + int first = list->findFirst (textblock, y, h, lastGB, lastExtIndex, &last); + + DBG_OBJ_MSGF ("border", 1, "first = %d", first); + + if (first == -1) { + // No float. + DBG_OBJ_LEAVE (); + return 0; + } else { + // It is not sufficient to find the first float, since a line + // (with height h) may cover the region of multiple float, of + // which the widest has to be choosen. + int border = 0; + bool covers = true; + + // We are not searching until the end of the list, but until the + // float defined by lastGB and lastExtIndex. + for (int i = first; covers && i <= last; i++) { + Float *vloat = list->get(i); + covers = vloat->covers (textblock, y, h); + DBG_OBJ_MSGF ("border", 1, "float %d (%p) covers? %s.", + i, vloat->getWidget(), covers ? "<b>yes</b>" : "no"); + + if (covers) { + int thisBorder; + if (vloat->generatingBlock == textblock) { + int borderIn = side == LEFT ? + vloat->generatingBlock->getStyle()->boxOffsetX() : + vloat->generatingBlock->getStyle()->boxRestWidth(); + thisBorder = vloat->size.width + borderIn; + DBG_OBJ_MSGF ("border", 1, "GB: thisBorder = %d + %d = %d", + vloat->size.width, borderIn, thisBorder); + } else { + assert (wasAllocated (vloat->generatingBlock)); + assert (vloat->getWidget()->wasAllocated ()); + + Allocation *tba = getAllocation(textblock), + *fla = vloat->getWidget()->getAllocation (); + if (side == LEFT) { + thisBorder = fla->x + fla->width - tba->x; + DBG_OBJ_MSGF ("border", 1, + "not GB: thisBorder = %d + %d - %d = %d", + fla->x, fla->width, tba->x, thisBorder); + } else { + // See also calcFloatX. + thisBorder = + tba->x + textblock->getLineBreakWidth () - fla->x; + DBG_OBJ_MSGF ("border", 1, + "not GB: thisBorder = %d + %d - %d " + "= %d", + tba->x, textblock->getLineBreakWidth (), fla->x, + thisBorder); + } + } + + border = max (border, thisBorder); + DBG_OBJ_MSGF ("border", 1, "=> border = %d", border); + } + } + + DBG_OBJ_LEAVE (); + return border; + } +} + + +OutOfFlowMgr::SortedFloatsVector *OutOfFlowMgr::getFloatsListForTextblock + (Textblock *textblock, Side side) +{ + DBG_OBJ_ENTER ("oofm.common", 1, "getFloatsListForTextblock", "%p, %s", + textblock, side == LEFT ? "LEFT" : "RIGHT"); + + OutOfFlowMgr::SortedFloatsVector *list; + + if (wasAllocated (textblock)) { + DBG_OBJ_MSG ("oofm.common", 2, "returning <b>CB</b> list"); + list = side == LEFT ? leftFloatsCB : rightFloatsCB; + } else { + DBG_OBJ_MSG ("oofm.common", 2, "returning <b>GB</b> list"); + TBInfo *tbInfo = getTextblock (textblock); + list = side == LEFT ? tbInfo->leftFloatsGB : tbInfo->rightFloatsGB; + } + + DBG_OBJ_LEAVE (); + return list; +} + + +bool OutOfFlowMgr::hasFloatLeft (Textblock *textblock, int y, int h, + Textblock *lastGB, int lastExtIndex) +{ + bool b = hasFloat (textblock, LEFT, y, h, lastGB, lastExtIndex); + DBG_OBJ_MSGF ("border", 0, "has float left (%p, %d, %d, %p, %d) => %s", + textblock, y, h, lastGB, lastExtIndex, b ? "true" : "false"); + return b; +} + +bool OutOfFlowMgr::hasFloatRight (Textblock *textblock, int y, int h, + Textblock *lastGB, int lastExtIndex) +{ + bool b = hasFloat (textblock, RIGHT, y, h, lastGB, lastExtIndex); + DBG_OBJ_MSGF ("border", 0, "has float right (%p, %d, %d, %p, %d) => %s", + textblock, y, h, lastGB, lastExtIndex, b ? "true" : "false"); + return b; +} + +bool OutOfFlowMgr::hasFloat (Textblock *textblock, Side side, int y, int h, + Textblock *lastGB, int lastExtIndex) +{ + DBG_OBJ_ENTER ("border", 0, "hasFloat", "%p, %s, %d, %d, %p, %d", + textblock, side == LEFT ? "LEFT" : "RIGHT", y, h, + lastGB, lastExtIndex); + + SortedFloatsVector *list = getFloatsListForTextblock (textblock, side); + int first = list->findFirst (textblock, y, h, lastGB, lastExtIndex, NULL); + + DBG_OBJ_MSGF ("border", 1, "first = %d", first); + DBG_OBJ_LEAVE (); + return first != -1; +} + +int OutOfFlowMgr::getLeftFloatHeight (Textblock *textblock, int y, int h, + Textblock *lastGB, int lastExtIndex) +{ + return getFloatHeight (textblock, LEFT, y, h, lastGB, lastExtIndex); +} + +int OutOfFlowMgr::getRightFloatHeight (Textblock *textblock, int y, int h, + Textblock *lastGB, int lastExtIndex) +{ + return getFloatHeight (textblock, RIGHT, y, h, lastGB, lastExtIndex); +} + +// Calculate height from the position *y*. +int OutOfFlowMgr::getFloatHeight (Textblock *textblock, Side side, int y, int h, + Textblock *lastGB, int lastExtIndex) +{ + DBG_OBJ_ENTER ("border", 0, "getFloatHeight", "%p, %s, %d, %d, %p, %d", + textblock, side == LEFT ? "LEFT" : "RIGHT", y, h, + lastGB, lastExtIndex); + + SortedFloatsVector *list = getFloatsListForTextblock (textblock, side); + int first = list->findFirst (textblock, y, h, lastGB, lastExtIndex, NULL); + assert (first != -1); /* This method must not be called when there is no + float on the respective side. */ + + Float *vloat = list->get(first); + int yRelToFloat; + + if (vloat->generatingBlock == textblock) { + yRelToFloat = y - vloat->yReal; + DBG_OBJ_MSGF ("border", 1, "caller is CB: yRelToFloat = %d - %d = %d", + y, vloat->yReal, yRelToFloat); + } else { + // The respective widgets are allocated; otherwise, hasFloat() would have + // returned false. + assert (wasAllocated (textblock)); + assert (vloat->getWidget()->wasAllocated ()); + + Allocation *tba = getAllocation(textblock), + *fla = vloat->getWidget()->getAllocation (); + yRelToFloat = tba->y + y - fla->y; + + DBG_OBJ_MSGF ("border", 1, + "caller is not CB: yRelToFloat = %d + %d - %d = %d", + tba->y, y, fla->y, yRelToFloat); + } + + ensureFloatSize (vloat); + int height = vloat->size.ascent + vloat->size.descent - yRelToFloat; + + DBG_OBJ_MSGF ("border", 1, "=> (%d + %d) - %d = %d", + vloat->size.ascent, vloat->size.descent, yRelToFloat, height); + DBG_OBJ_LEAVE (); + return height; +} + +/** + * Returns position relative to the textblock "tb". + */ +int OutOfFlowMgr::getClearPosition (Textblock *tb) +{ + return getTextblock(tb)->clearPosition; +} + +int OutOfFlowMgr::calcClearPosition (Textblock *tb) +{ + DBG_OBJ_ENTER ("resize.oofm", 0, "getClearPosition", "%p", tb); + + int pos; + + if (tb->getStyle()) { + bool left = false, right = false; + switch (tb->getStyle()->clear) { + case CLEAR_NONE: break; + case CLEAR_LEFT: left = true; break; + case CLEAR_RIGHT: right = true; break; + case CLEAR_BOTH: left = right = true; break; + default: assertNotReached (); + } + + pos = max (left ? calcClearPosition (tb, LEFT) : 0, + right ? calcClearPosition (tb, RIGHT) : 0); + } else + pos = 0; + + DBG_OBJ_MSGF ("resize.oofm", 1, "=> %d", pos); + DBG_OBJ_LEAVE (); + + return pos; +} + +int OutOfFlowMgr::calcClearPosition (Textblock *tb, Side side) +{ + DBG_OBJ_ENTER ("resize.oofm", 0, "getClearPosition", "%p, %s", + tb, side == LEFT ? "LEFT" : "RIGHT"); + + int pos; + + if (!wasAllocated (tb)) + // There is no relation yet to floats generated by other + // textblocks, and this textblocks floats are unimportant for + // the "clear" property. + pos = 0; + else { + SortedFloatsVector *list = side == LEFT ? leftFloatsCB : rightFloatsCB; + + // Search the last float before (therfore -1) this textblock. + int i = list->findFloatIndex (tb, -1); + if (i < 0) { + pos = 0; + DBG_OBJ_MSG ("resize.oofm", 1, "no float"); + } else { + Float *vloat = list->get(i); + assert (vloat->generatingBlock != tb); + if (!wasAllocated (vloat->generatingBlock)) + pos = 0; // See above. + else { + ensureFloatSize (vloat); + pos = max (getAllocation(vloat->generatingBlock)->y + vloat->yReal + + vloat->size.ascent + vloat->size.descent + - getAllocation(tb)->y, + 0); + DBG_OBJ_MSGF ("resize.oofm", 1, + "float %p => max (%d + %d + (%d + %d) - %d, 0)", + vloat->getWidget (), + getAllocation(vloat->generatingBlock)->y, + vloat->yReal, vloat->size.ascent, vloat->size.descent, + getAllocation(tb)->y); + } + } + } + + DBG_OBJ_MSGF ("resize.oofm", 1, "=> %d", pos); + DBG_OBJ_LEAVE (); + + return pos; +} + +void OutOfFlowMgr::ensureFloatSize (Float *vloat) +{ + // Historical note: relative sizes (e. g. percentages) are already + // handled by (at this time) Layout::containerSizeChanged, so + // Float::dirty will be set. + + DBG_OBJ_ENTER ("resize.oofm", 0, "ensureFloatSize", "%p", + vloat->getWidget ()); + + if (vloat->dirty) { + DBG_OBJ_MSG ("resize.oofm", 1, "dirty: recalculation"); + + vloat->getWidget()->sizeRequest (&vloat->size); + vloat->cbLineBreakWidth = containingBlock->getLineBreakWidth (); + vloat->dirty = false; + DBG_OBJ_SET_BOOL_O (vloat->getWidget (), "<Float>.dirty", vloat->dirty); + + DBG_OBJ_MSGF ("resize.oofm", 1, "size: %d * (%d + %d)", + vloat->size.width, vloat->size.ascent, vloat->size.descent); + + DBG_OBJ_SET_NUM_O (vloat->getWidget(), "<Float>.size.width", + vloat->size.width); + DBG_OBJ_SET_NUM_O (vloat->getWidget(), "<Float>.size.ascent", + vloat->size.ascent); + DBG_OBJ_SET_NUM_O (vloat->getWidget(), "<Float>.size.descent", + vloat->size.descent); + + // "sizeChangedSinceLastAllocation" is reset in sizeAllocateEnd() + } + + DBG_OBJ_LEAVE (); +} + +} // namespace dw diff --git a/dw/outofflowmgr.hh b/dw/outofflowmgr.hh new file mode 100644 index 00000000..24607bad --- /dev/null +++ b/dw/outofflowmgr.hh @@ -0,0 +1,436 @@ +#ifndef __DW_OUTOFFLOWMGR_HH__ +#define __DW_OUTOFFLOWMGR_HH__ + +#include "core.hh" + +namespace dw { + +class Textblock; + +/** + * \brief Represents additional data for containing blocks. + */ +class OutOfFlowMgr +{ + friend class WidgetInfo; + +private: + enum Side { LEFT, RIGHT }; + enum SFVType { GB, CB }; + + Textblock *containingBlock; + + // These two values are redundant to TBInfo::wasAllocated and + // TBInfo::allocation, for some special cases. + bool containingBlockWasAllocated; + core::Allocation containingBlockAllocation; + + class WidgetInfo: public lout::object::Object + { + private: + bool wasAllocated; + int xCB, yCB; // relative to the containing block + int width, height; + + OutOfFlowMgr *oofm; + core::Widget *widget; + + protected: + OutOfFlowMgr *getOutOfFlowMgr () { return oofm; } + + public: + WidgetInfo (OutOfFlowMgr *oofm, core::Widget *widget); + + inline bool wasThenAllocated () { return wasAllocated; } + inline int getOldXCB () { return xCB; } + inline int getOldYCB () { return yCB; } + inline int getOldWidth () { return width; } + inline int getOldHeight () { return height; } + + + void update (bool wasAllocated, int xCB, int yCB, int width, int height); + + inline core::Widget *getWidget () { return widget; } + }; + + class Float: public WidgetInfo + { + public: + class ComparePosition: public lout::object::Comparator + { + private: + OutOfFlowMgr *oofm; + Textblock *refTB; + SFVType type; // actually only used for debugging + + public: + ComparePosition (OutOfFlowMgr *oofm, Textblock *refTB, SFVType type) + { this->oofm = oofm; this->refTB = refTB; this->type = type; } + int compare(Object *o1, Object *o2); + }; + + class CompareSideSpanningIndex: public lout::object::Comparator + { + public: + int compare(Object *o1, Object *o2); + }; + + class CompareGBAndExtIndex: public lout::object::Comparator + { + private: + OutOfFlowMgr *oofm; + SFVType type; // actually only used for debugging + + public: + CompareGBAndExtIndex (OutOfFlowMgr *oofm, SFVType type) + { this->oofm = oofm; this->type = type; } + int compare(Object *o1, Object *o2); + }; + + Textblock *generatingBlock; + int externalIndex; + int yReq, yReal; // relative to generator, not container + int indexGBList; /* Refers to TBInfo::leftFloatsGB or + TBInfo::rightFloatsGB, respectively. -1 + initially. */ + int indexCBList; /* Refers to leftFloatsCB or rightFloatsCB, + respectively. -1 initially. */ + int sideSpanningIndex, mark; + core::Requisition size; + int cbLineBreakWidth; /* On which the calculation of relative sizes + is based. Height not yet used, and probably + not added before size redesign. */ + bool dirty, sizeChangedSinceLastAllocation; + + Float (OutOfFlowMgr *oofm, core::Widget *widget, + Textblock *generatingBlock, int externalIndex); + + inline bool isNowAllocated () { return getWidget()->wasAllocated (); } + inline int getNewXCB () { return getWidget()->getAllocation()->x - + getOutOfFlowMgr()->containingBlockAllocation.x; } + inline int getNewYCB () { return getWidget()->getAllocation()->y - + getOutOfFlowMgr()->containingBlockAllocation.y; } + inline int getNewWidth () { return getWidget()->getAllocation()->width; } + inline int getNewHeight () { return getWidget()->getAllocation()->ascent + + getWidget()->getAllocation()->descent; } + void updateAllocation (); + + inline int *getIndexRef (SFVType type) { + return type == GB ? &indexGBList : &indexCBList; } + inline int getIndex (SFVType type) { return *(getIndexRef (type)); } + inline void setIndex (SFVType type, int value) { + *(getIndexRef (type)) = value; } + + void intoStringBuffer(lout::misc::StringBuffer *sb); + + bool covers (Textblock *textblock, int y, int h); + }; + + /** + * This list is kept sorted. + * + * To prevent accessing methods of the base class in an + * uncontrolled way, the inheritance is private, not public; this + * means that all methods must be delegated (see iterator(), size() + * etc. below.) + * + * TODO Update comment: still sorted, but ... + * + * More: add() and change() may check order again. + */ + class SortedFloatsVector: private lout::container::typed::Vector<Float> + { + public: + SFVType type; + + private: + OutOfFlowMgr *oofm; + Side side; + + public: + inline SortedFloatsVector (OutOfFlowMgr *oofm, Side side, SFVType type) : + lout::container::typed::Vector<Float> (1, false) + { this->oofm = oofm; this->side = side; this->type = type; } + + int findFloatIndex (Textblock *lastGB, int lastExtIndex); + int find (Textblock *textblock, int y, int start, int end); + int findFirst (Textblock *textblock, int y, int h, Textblock *lastGB, + int lastExtIndex, int *lastReturn); + int findLastBeforeSideSpanningIndex (int sideSpanningIndex); + void put (Float *vloat); + + inline lout::container::typed::Iterator<Float> iterator() + { return lout::container::typed::Vector<Float>::iterator (); } + inline int size () + { return lout::container::typed::Vector<Float>::size (); } + inline Float *get (int pos) + { return lout::container::typed::Vector<Float>::get (pos); } + inline void clear () + { lout::container::typed::Vector<Float>::clear (); } + }; + + class TBInfo: public WidgetInfo + { + public: + int lineBreakWidth; + int index; // position within "tbInfos" + + TBInfo *parent; + int parentExtIndex; + + // These two values are set by sizeAllocateStart(), and they are + // accessable also within sizeAllocateEnd() for the same + // textblock, for which allocation and WAS_ALLOCATED is set + // *after* sizeAllocateEnd(). See the two functions + // wasAllocated(Widget*) and getAllocation(Widget*) (further + // down) for usage. + bool wasAllocated; + core::Allocation allocation; + int clearPosition; + + // These two lists store all floats generated by this textblock, + // as long as this textblock is not allocates. + SortedFloatsVector *leftFloatsGB, *rightFloatsGB; + + TBInfo (OutOfFlowMgr *oofm, Textblock *textblock, + TBInfo *parent, int parentExtIndex); + ~TBInfo (); + + inline bool isNowAllocated () { + return getOutOfFlowMgr()->wasAllocated (getTextblock ()); } + inline int getNewXCB () { + return getOutOfFlowMgr()->getAllocation (getTextblock ())->x - + getOutOfFlowMgr()->containingBlockAllocation.x; } + inline int getNewYCB () { + return getOutOfFlowMgr()->getAllocation (getTextblock ())->y - + getOutOfFlowMgr()->containingBlockAllocation.y; } + inline int getNewWidth () { + return getOutOfFlowMgr()->getAllocation (getTextblock ())->width; } + inline int getNewHeight () { + core::Allocation *allocation = + getOutOfFlowMgr()->getAllocation (getTextblock ()); + return allocation->ascent + allocation->descent; } + void updateAllocation (); + + inline Textblock *getTextblock () { return (Textblock*)getWidget (); } + }; + + // These two lists store all floats, in the order in which they are + // defined. Only used for iterators. + lout::container::typed::Vector<Float> *leftFloatsAll, *rightFloatsAll; + + // These two lists store all floats whose generators are already + // allocated. + SortedFloatsVector *leftFloatsCB, *rightFloatsCB; + + // These two attributes are used in the size allocation process; + // see sizeAllocateStart and sizeAllocateEnd. + int lastAllocatedLeftFloat, lastAllocatedRightFloat; + + lout::container::typed::HashTable<lout::object::TypedPointer + <dw::core::Widget>, Float> *floatsByWidget; + + lout::container::typed::Vector<TBInfo> *tbInfos; + lout::container::typed::HashTable<lout::object::TypedPointer <Textblock>, + TBInfo> *tbInfosByTextblock; + + int lastLeftTBIndex, lastRightTBIndex, leftFloatsMark, rightFloatsMark; + + /** + * Variant of Widget::wasAllocated(), which can also be used within + * OOFM::sizeAllocateEnd(). + */ + inline bool wasAllocated (Textblock *textblock) { + return getTextblock(textblock)->wasAllocated; + } + + /** + * Variant of Widget::getAllocation(), which can also be used + * within OOFM::sizeAllocateEnd(). + */ + inline core::Allocation *getAllocation (Textblock *textblock) { + return &(getTextblock(textblock)->allocation); + } + + void moveExternalIndices (SortedFloatsVector *list, int oldStartIndex, + int diff); + Float *findFloatByWidget (core::Widget *widget); + + void moveFromGBToCB (Side side); + void sizeAllocateFloats (Side side, int newLastAllocatedFloat); + int calcFloatX (Float *vloat, Side side, int gbX, int gbWidth, + int gbLineBreakWidth); + + bool hasRelationChanged (TBInfo *tbInfo,int *minFloatPos, + core::Widget **minFloat); + bool hasRelationChanged (TBInfo *tbInfo, Side side, int *minFloatPos, + core::Widget **minFloat); + bool hasRelationChanged (bool oldTBAlloc, + int oldTBx, int oldTBy, int oldTBw, int oldTBh, + int newTBx, int newTBy, int newTBw, int newTBh, + bool oldFlAlloc, + int oldFlx, int oldFly, int oldFlw, int oldFlh, + int newFlx, int newFly, int newFlw, int newFlh, + Side side, int *floatPos); + + void checkAllocatedFloatCollisions (Side side); + + bool doFloatsExceedCB (Side side); + bool haveExtremesChanged (Side side); + + void drawFloats (SortedFloatsVector *list, core::View *view, + core::Rectangle *area); + core::Widget *getFloatWidgetAtPoint (SortedFloatsVector *list, int x, int y, + int level); + + bool collidesV (Float *vloat, Float *other, SFVType type, int *yReal, + bool useAllocation); + bool collidesH (Float *vloat, Float *other, SFVType type); + + void getFloatsListsAndSide (Float *vloat, SortedFloatsVector **listSame, + SortedFloatsVector **listOpp, Side *side); + + void getFloatsSize (core::Requisition *cbReq, Side side, int *width, + int *height); + void getFloatsExtremes (core::Extremes *cbExtr, Side side, int *minWidth, + int *maxWidth); + bool getFloatDiffToCB (Float *vloat, int *leftDiff, int *rightDiff); + + TBInfo *getTextblock (Textblock *textblock); + int getBorder (Textblock *textblock, Side side, int y, int h, + Textblock *lastGB, int lastExtIndex); + SortedFloatsVector *getFloatsListForTextblock (Textblock *textblock, + Side side); + bool hasFloat (Textblock *textblock, Side side, int y, int h, + Textblock *lastGB, int lastExtIndex); + + int getFloatHeight (Textblock *textblock, Side side, int y, int h, + Textblock *lastGB, int lastExtIndex); + + int calcClearPosition (Textblock *tb, Side side); + int calcClearPosition (Textblock *tb); + + void ensureFloatSize (Float *vloat); + + void tellFloatPosition (core::Widget *widget, int yReq); + + static inline bool isStyleFloat (core::style::Style *style) + { return style->vloat != core::style::FLOAT_NONE; } + static inline bool isWidgetFloat (core::Widget *widget) + { return isStyleFloat (widget->getStyle()); } + + /* + * Format for parent ref (see also below for isRefOutOfFlow, + * createRefNormalFlow, and getLineNoFromRef. + * + * Widget in flow: + * + * +---+ - - - +---+---+- - - - - -+---+---+---+---+ + * | line number | 0 | + * +---+ - - - +---+---+- - - - - -+---+---+---+---+ + * + * So, anything with the least signifant bit set to 1 is out of flow. + * + * Floats: + * + * +---+ - - - +---+---+- - - - - -+---+---+---+---+ + * | left float index | 0 | 0 | 1 | + * +---+ - - - +---+---+- - - - - -+---+---+---+---+ + * + * +---+ - - - +---+---+- - - - - -+---+---+---+---+ + * | right float index | 1 | 0 | 1 | + * +---+ - - - +---+---+- - - - - -+---+---+---+---+ + * + * Absolutely positioned blocks: solved differently in the + * "dillo_grows" repository. + */ + + inline static bool isRefFloat (int ref) + { return ref != -1 && (ref & 3) == 1; } + inline static bool isRefLeftFloat (int ref) + { return ref != -1 && (ref & 7) == 1; } + inline static bool isRefRightFloat (int ref) + { return ref != -1 && (ref & 7) == 5; } + + inline static int createRefLeftFloat (int index) + { return (index << 3) | 1; } + inline static int createRefRightFloat (int index) + { return (index << 3) | 5; } + + inline static int getFloatIndexFromRef (int ref) + { return ref == -1 ? ref : (ref >> 3); } + +public: + OutOfFlowMgr (Textblock *containingBlock); + ~OutOfFlowMgr (); + + void sizeAllocateStart (Textblock *caller, core::Allocation *allocation); + void sizeAllocateEnd (Textblock *caller); + void containerSizeChangedForChildren (); + void draw (core::View *view, core::Rectangle *area); + + void markSizeChange (int ref); + void markExtremesChange (int ref); + core::Widget *getWidgetAtPoint (int x, int y, int level); + + static bool isStyleOutOfFlow (core::style::Style *style) + { return isStyleFloat (style); } + static inline bool isWidgetOutOfFlow (core::Widget *widget) + { return isStyleOutOfFlow (widget->getStyle()); } + + void addWidgetInFlow (Textblock *textblock, Textblock *parentBlock, + int externalIndex); + void addWidgetOOF (core::Widget *widget, Textblock *generatingBlock, + int externalIndex); + void moveExternalIndices (Textblock *generatingBlock, int oldStartIndex, + int diff); + + void tellPosition (core::Widget *widget, int yReq); + + void getSize (core::Requisition *cbReq, int *oofWidth, int *oofHeight); + void getExtremes (core::Extremes *cbExtr, + int *oofMinWidth, int *oofMaxWidth); + + int getLeftBorder (Textblock *textblock, int y, int h, Textblock *lastGB, + int lastExtIndex); + int getRightBorder (Textblock *textblock, int y, int h, Textblock *lastGB, + int lastExtIndex); + + bool hasFloatLeft (Textblock *textblock, int y, int h, Textblock *lastGB, + int lastExtIndex); + bool hasFloatRight (Textblock *textblock, int y, int h, Textblock *lastGB, + int lastExtIndex); + + int getLeftFloatHeight (Textblock *textblock, int y, int h, + Textblock *lastGB, int lastExtIndex); + int getRightFloatHeight (Textblock *textblock, int y, int h, + Textblock *lastGB, int lastExtIndex); + + int getClearPosition (Textblock *tb); + + inline static bool isRefOutOfFlow (int ref) + { return ref != -1 && (ref & 1) != 0; } + inline static int createRefNormalFlow (int lineNo) { return lineNo << 1; } + inline static int getLineNoFromRef (int ref) + { return ref == -1 ? ref : (ref >> 1); } + + // for iterators + inline int getNumWidgets () + { return leftFloatsAll->size() + rightFloatsAll->size(); } + + inline core::Widget *getWidget (int i) { + if (i < leftFloatsAll->size()) + return leftFloatsAll->get(i)->getWidget (); + else + return rightFloatsAll->get(i - leftFloatsAll->size())->getWidget (); + } + + inline bool affectsLeftBorder (core::Widget *widget) { + return widget->getStyle()->vloat == core::style::FLOAT_LEFT; } + inline bool affectsRightBorder (core::Widget *widget) { + return widget->getStyle()->vloat == core::style::FLOAT_RIGHT; } +}; + +} // namespace dw + +#endif // __DW_OUTOFFLOWMGR_HH__ diff --git a/dw/ruler.cc b/dw/ruler.cc index 115dfaa5..3fdbfb6d 100644 --- a/dw/ruler.cc +++ b/dw/ruler.cc @@ -28,17 +28,41 @@ namespace dw { Ruler::Ruler () { - setFlags (BLOCK_LEVEL); - unsetFlags (HAS_CONTENTS); } void Ruler::sizeRequestImpl (core::Requisition *requisition) { - requisition->width = getStyle()->boxDiffWidth (); + requisition->width = + lout::misc::max (getAvailWidth (true), getStyle()->boxDiffWidth ()); requisition->ascent = getStyle()->boxOffsetY (); requisition->descent = getStyle()->boxRestHeight (); } +void Ruler::getExtremesImpl (core::Extremes *extremes) +{ + extremes->minWidth = extremes->maxWidth = getStyle()->boxDiffWidth (); + extremes->minWidthIntrinsic = extremes->minWidth; + extremes->maxWidthIntrinsic = extremes->maxWidth; + correctExtremes (extremes); +} + +bool Ruler::isBlockLevel () +{ + return true; +} + +void Ruler::containerSizeChangedForChildren () +{ + DBG_OBJ_ENTER0 ("resize", 0, "containerSizeChangedForChildren"); + // Nothing to do. + DBG_OBJ_LEAVE (); +} + +bool Ruler::usesAvailWidth () +{ + return true; +} + void Ruler::draw (core::View *view, core::Rectangle *area) { drawWidgetBox (view, area, false); diff --git a/dw/ruler.hh b/dw/ruler.hh index 32e859a1..1f3491bc 100644 --- a/dw/ruler.hh +++ b/dw/ruler.hh @@ -17,11 +17,16 @@ class Ruler: public core::Widget { protected: void sizeRequestImpl (core::Requisition *requisition); + void getExtremesImpl (core::Extremes *extremes); + void containerSizeChangedForChildren (); + bool usesAvailWidth (); void draw (core::View *view, core::Rectangle *area); public: Ruler (); + bool isBlockLevel (); + core::Iterator *iterator (core::Content::Type mask, bool atEnd); }; diff --git a/dw/selection.hh b/dw/selection.hh index ef9df0e0..3004d2d4 100644 --- a/dw/selection.hh +++ b/dw/selection.hh @@ -178,7 +178,7 @@ namespace core { class SelectionState { public: - enum { END_OF_WORD = 1 << 30 }; + enum { END_OF_WORD = 1 << 30 }; private: Layout *layout; diff --git a/dw/simpletablecell.cc b/dw/simpletablecell.cc new file mode 100644 index 00000000..02f92db6 --- /dev/null +++ b/dw/simpletablecell.cc @@ -0,0 +1,122 @@ +/* + * Dillo Widget + * + * Copyright 2014 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + + +#include "simpletablecell.hh" +#include "tablecell.hh" +#include "../lout/misc.hh" +#include "../lout/debug.hh" + +namespace dw { + +int SimpleTableCell::CLASS_ID = -1; + +SimpleTableCell::SimpleTableCell (bool limitTextWidth): + Textblock (limitTextWidth) +{ + DBG_OBJ_CREATE ("dw::SimpleTableCell"); + registerName ("dw::SimpleTableCell", &CLASS_ID); +} + +SimpleTableCell::~SimpleTableCell() +{ + DBG_OBJ_DELETE (); +} + +bool SimpleTableCell::getAdjustMinWidth () +{ + return tablecell::getAdjustMinWidth (); +} + +bool SimpleTableCell::isBlockLevel () +{ + return tablecell::isBlockLevel (); +} + +int SimpleTableCell::getAvailWidthOfChild (Widget *child, bool forceValue) +{ + DBG_OBJ_ENTER ("resize", 0, "SimpleTableCell/getAvailWidthOfChild", + "%p, %s", child, forceValue ? "true" : "false"); + + int width = tablecell::correctAvailWidthOfChild + (this, child, Textblock::getAvailWidthOfChild (child, forceValue), + forceValue); + + DBG_OBJ_LEAVE (); + return width; +} + +int SimpleTableCell::getAvailHeightOfChild (Widget *child, bool forceValue) +{ + DBG_OBJ_ENTER ("resize", 0, "SimpleTableCell/getAvailHeightOfChild", + "%p, %s", child, forceValue ? "true" : "false"); + + int height = tablecell::correctAvailHeightOfChild + (this, child, Textblock::getAvailHeightOfChild (child, forceValue), + forceValue); + + DBG_OBJ_LEAVE (); + return height; +} + +void SimpleTableCell::correctRequisitionOfChild (Widget *child, + core::Requisition *requisition, + void (*splitHeightFun) (int, + int*, + int*)) +{ + DBG_OBJ_ENTER ("resize", 0, "SimpleTableCell/correctRequisitionOfChild", + "%p, %d * (%d + %d), ...", child, requisition->width, + requisition->ascent, requisition->descent); + + Textblock::correctRequisitionOfChild (child, requisition, splitHeightFun); + tablecell::correctCorrectedRequisitionOfChild (this, child, requisition, + splitHeightFun); + + DBG_OBJ_LEAVE (); +} + +void SimpleTableCell::correctExtremesOfChild (Widget *child, + core::Extremes *extremes) +{ + DBG_OBJ_ENTER ("resize", 0, "SimpleTableCell/correctExtremesOfChild", + "%p, %d (%d) / %d (%d)", + child, extremes->minWidth, extremes->minWidthIntrinsic, + extremes->maxWidth, extremes->maxWidthIntrinsic); + + Textblock::correctExtremesOfChild (child, extremes); + tablecell::correctCorrectedExtremesOfChild (this, child, extremes); + + DBG_OBJ_LEAVE (); +} + +int SimpleTableCell::applyPerWidth (int containerWidth, + core::style::Length perWidth) +{ + return tablecell::applyPerWidth (this, containerWidth, perWidth); +} + +int SimpleTableCell::applyPerHeight (int containerHeight, + core::style::Length perHeight) +{ + return tablecell::applyPerHeight (this, containerHeight, perHeight); +} + +} // namespace dw diff --git a/dw/simpletablecell.hh b/dw/simpletablecell.hh new file mode 100644 index 00000000..4c18b454 --- /dev/null +++ b/dw/simpletablecell.hh @@ -0,0 +1,35 @@ +#ifndef __DW_SIMPLETABLECELL_HH__ +#define __DW_SIMPLETABLECELL_HH__ + +#include "textblock.hh" + +namespace dw { + +class SimpleTableCell: public Textblock +{ +protected: + int getAvailWidthOfChild (Widget *child, bool forceValue); + int getAvailHeightOfChild (Widget *child, bool forceValue); + + void correctRequisitionOfChild (Widget *child, + core::Requisition *requisition, + void (*splitHeightFun) (int, int*, int*)); + void correctExtremesOfChild (Widget *child, core::Extremes *extremes); + + bool getAdjustMinWidth (); + +public: + static int CLASS_ID; + + SimpleTableCell (bool limitTextWidth); + ~SimpleTableCell (); + + int applyPerWidth (int containerWidth, core::style::Length perWidth); + int applyPerHeight (int containerHeight, core::style::Length perHeight); + + bool isBlockLevel (); +}; + +} // namespace dw + +#endif // __DW_SIMPLETABLECELL_HH__ diff --git a/dw/style.cc b/dw/style.cc index 8ec230a1..54d9af4b 100644 --- a/dw/style.cc +++ b/dw/style.cc @@ -17,8 +17,6 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - - #include <stdio.h> #include <string.h> #include <unistd.h> @@ -75,6 +73,12 @@ void StyleAttrs::initValues () backgroundPositionX = createPerLength (0); backgroundPositionY = createPerLength (0); width = height = lineHeight = LENGTH_AUTO; + minWidth = maxWidth = minHeight = maxHeight = LENGTH_AUTO; + vloat = FLOAT_NONE; + clear = CLEAR_NONE; + overflow = OVERFLOW_VISIBLE; + position = POSITION_STATIC; + top = bottom = left = right = LENGTH_AUTO; textIndent = 0; margin.setVal (0); borderWidth.setVal (0); @@ -101,6 +105,12 @@ void StyleAttrs::resetValues () valign = VALIGN_BASELINE; textAlignChar = '.'; + vloat = FLOAT_NONE; /** \todo Correct? Check specification. */ + clear = CLEAR_NONE; /** \todo Correct? Check specification. */ + overflow = OVERFLOW_VISIBLE; + position = POSITION_STATIC; /** \todo Correct? Check specification. */ + top = bottom = left = right = LENGTH_AUTO; /** \todo Correct? Check + specification. */ backgroundColor = NULL; backgroundImage = NULL; backgroundRepeat = BACKGROUND_REPEAT; @@ -109,6 +119,7 @@ void StyleAttrs::resetValues () backgroundPositionY = createPerLength (0); width = LENGTH_AUTO; height = LENGTH_AUTO; + minWidth = maxWidth = minHeight = maxHeight = LENGTH_AUTO; margin.setVal (0); borderWidth.setVal (0); @@ -156,11 +167,23 @@ bool StyleAttrs::equals (object::Object *other) { valign == otherAttrs->valign && textAlignChar == otherAttrs->textAlignChar && textTransform == otherAttrs->textTransform && + vloat == otherAttrs->vloat && + clear == otherAttrs->clear && + overflow == otherAttrs->overflow && + position == otherAttrs->position && + top == otherAttrs->top && + bottom == otherAttrs->bottom && + left == otherAttrs->left && + right == otherAttrs->right && hBorderSpacing == otherAttrs->hBorderSpacing && vBorderSpacing == otherAttrs->vBorderSpacing && wordSpacing == otherAttrs->wordSpacing && width == otherAttrs->width && height == otherAttrs->height && + minWidth == otherAttrs->minWidth && + maxWidth == otherAttrs->maxWidth && + minHeight == otherAttrs->minHeight && + maxHeight == otherAttrs->maxHeight && lineHeight == otherAttrs->lineHeight && textIndent == otherAttrs->textIndent && margin.equals (&otherAttrs->margin) && @@ -201,11 +224,23 @@ int StyleAttrs::hashValue () { valign + textAlignChar + textTransform + + vloat + + clear + + overflow + + position + + top + + bottom + + left + + right + hBorderSpacing + vBorderSpacing + wordSpacing + width + height + + minWidth + + maxWidth + + minHeight + + maxHeight + lineHeight + textIndent + margin.hashValue () + @@ -316,6 +351,14 @@ void Style::copyAttrs (StyleAttrs *attrs) valign = attrs->valign; textAlignChar = attrs->textAlignChar; textTransform = attrs->textTransform; + vloat = attrs->vloat; + clear = attrs->clear; + overflow = attrs->overflow; + position = attrs->position; + top = attrs->top; + bottom = attrs->bottom; + left = attrs->left; + right = attrs->right; hBorderSpacing = attrs->hBorderSpacing; vBorderSpacing = attrs->vBorderSpacing; wordSpacing = attrs->wordSpacing; @@ -323,6 +366,10 @@ void Style::copyAttrs (StyleAttrs *attrs) height = attrs->height; lineHeight = attrs->lineHeight; textIndent = attrs->textIndent; + minWidth = attrs->minWidth; + maxWidth = attrs->maxWidth; + minHeight = attrs->minHeight; + maxHeight = attrs->maxHeight; margin = attrs->margin; borderWidth = attrs->borderWidth; padding = attrs->padding; @@ -1157,18 +1204,26 @@ void drawBorder (View *view, Layout *layout, Rectangle *area, * * Otherwise, the caller should not try to increase the performance by * doing some tests before; this is all done in this method. + * + * "bgColor" is passes implicitly. For non-inversed drawing, + * style->backgroundColor may simply used. However, when drawing is + * inversed, and style->backgroundColor is undefined (NULL), a + * background color defined higher in the hierarchy (which is not + * accessable here) must be used. + * + * (Background *images* are never drawn inverse.) */ void drawBackground (View *view, Layout *layout, Rectangle *area, int x, int y, int width, int height, int xRef, int yRef, int widthRef, int heightRef, - Style *style, bool inverse, bool atTop) + Style *style, Color *bgColor, bool inverse, bool atTop) { - bool bgColor = style->backgroundColor != NULL && + bool hasBgColor = bgColor != NULL && // The test for background colors is rather simple, since only the color // has to be compared, ... - (!atTop || layout->getBgColor () != style->backgroundColor); - bool bgImage = (style->backgroundImage != NULL && - style->backgroundImage->getImgbufSrc() != NULL) && + (!atTop || layout->getBgColor () != bgColor); + bool hasBgImage = (style->backgroundImage != NULL && + style->backgroundImage->getImgbufSrc() != NULL) && // ... but for backgrounds, it would be rather complicated. To handle the // two cases (normal HTML in a viewport, where the layout background // image is set, and contents of <button> within a flat view, where the @@ -1182,7 +1237,7 @@ void drawBackground (View *view, Layout *layout, Rectangle *area, // necessary to draw the background if background color and image // are not set (NULL), i. e. shining through. - if (bgColor || bgImage) { + if (hasBgColor || hasBgImage) { Rectangle bgArea, intersection; bgArea.x = x; bgArea.y = y; @@ -1190,14 +1245,14 @@ void drawBackground (View *view, Layout *layout, Rectangle *area, bgArea.height = height; if (area->intersectsWith (&bgArea, &intersection)) { - if (bgColor) - view->drawRectangle (style->backgroundColor, + if (hasBgColor) + view->drawRectangle (bgColor, inverse ? Color::SHADING_INVERSE : Color::SHADING_NORMAL, true, intersection.x, intersection.y, intersection.width, intersection.height); - if (bgImage) + if (hasBgImage) drawBackgroundImage (view, style->backgroundImage, style->backgroundRepeat, style->backgroundAttachment, diff --git a/dw/style.hh b/dw/style.hh index e0ce9d89..230baa24 100644 --- a/dw/style.hh +++ b/dw/style.hh @@ -296,7 +296,6 @@ enum ListStylePosition { LIST_STYLE_POSITION_INSIDE, LIST_STYLE_POSITION_OUTSIDE }; - enum ListStyleType { LIST_STYLE_TYPE_DISC, LIST_STYLE_TYPE_CIRCLE, @@ -332,6 +331,20 @@ enum FontVariant { FONT_VARIANT_SMALL_CAPS }; +enum Overflow { + OVERFLOW_VISIBLE, + OVERFLOW_HIDDEN, + OVERFLOW_SCROLL, + OVERFLOW_AUTO +}; + +enum Position { + POSITION_STATIC, + POSITION_RELATIVE, + POSITION_ABSOLUTE, + POSITION_FIXED, +}; + enum TextDecoration { TEXT_DECORATION_NONE = 0, TEXT_DECORATION_UNDERLINE = 1 << 0, @@ -348,6 +361,19 @@ enum WhiteSpace { WHITE_SPACE_PRE_LINE, }; +enum FloatType { + FLOAT_NONE, + FLOAT_LEFT, + FLOAT_RIGHT +}; + +enum ClearType { + CLEAR_LEFT, + CLEAR_RIGHT, + CLEAR_BOTH, + CLEAR_NONE +}; + /** * \brief Type for representing all lengths within dw::core::style. * @@ -416,7 +442,8 @@ inline int absLengthVal(Length l) { return l >> 2; } * When possible, do not use this function directly; it may be removed * soon. Instead, use multiplyWithPerLength or multiplyWithPerLengthRounded. */ -inline double perLengthVal(Length l) { return (double)(l & ~3) / (1 << 18); } +inline double perLengthVal_useThisOnlyForDebugging(Length l) +{ return (double)(l & ~3) / (1 << 18); } /** \brief Returns the value of a relative length, as a float. * @@ -431,7 +458,7 @@ inline double relLengthVal(Length l) { return (double)(l & ~3) / (1 << 18); } * Use this instead of perLengthVal, when possible. */ inline int multiplyWithPerLength(int x, Length l) { - return x * perLengthVal(l); + return x * perLengthVal_useThisOnlyForDebugging (l); } /** @@ -440,8 +467,8 @@ inline int multiplyWithPerLength(int x, Length l) { * * (This function exists for backward compatibility.) */ -inline int multiplyWithPerLengthRounded (int x, Length l) { - return lout::misc::roundInt (x * perLengthVal(l)); +inline int multiplyWithPerLengthRounded(int x, Length l) { + return lout::misc::roundInt (x * perLengthVal_useThisOnlyForDebugging (l)); } inline int multiplyWithRelLength(int x, Length l) { @@ -504,8 +531,17 @@ public: char textAlignChar; /* In future, strings will be supported. */ TextTransform textTransform; + FloatType vloat; /* "float" is a keyword. */ + ClearType clear; + + Overflow overflow; + + Position position; + Length top, bottom, left, right; + int hBorderSpacing, vBorderSpacing, wordSpacing; Length width, height, lineHeight, textIndent; + Length minWidth, maxWidth, minHeight, maxHeight; Box margin, borderWidth, padding; BorderCollapse borderCollapse; @@ -539,22 +575,14 @@ public: = borderStyle.left = val; } inline int boxOffsetX () - { - return margin.left + borderWidth.left + padding.left; - } + { return margin.left + borderWidth.left + padding.left; } inline int boxRestWidth () - { - return margin.right + borderWidth.right + padding.right; - } + { return margin.right + borderWidth.right + padding.right; } inline int boxDiffWidth () { return boxOffsetX () + boxRestWidth (); } inline int boxOffsetY () - { - return margin.top + borderWidth.top + padding.top; - } + { return margin.top + borderWidth.top + padding.top; } inline int boxRestHeight () - { - return margin.bottom + borderWidth.bottom + padding.bottom; - } + { return margin.bottom + borderWidth.bottom + padding.bottom; } inline int boxDiffHeight () { return boxOffsetY () + boxRestHeight (); } inline bool hasBackground () @@ -861,7 +889,7 @@ void drawBorder (View *view, Layout *layout, Rectangle *area, void drawBackground (View *view, Layout *layout, Rectangle *area, int x, int y, int width, int height, int xRef, int yRef, int widthRef, int heightRef, - Style *style, bool inverse, bool atTop); + Style *style, Color *bgColor, bool inverse, bool atTop); void drawBackgroundImage (View *view, StyleImage *backgroundImage, BackgroundRepeat backgroundRepeat, BackgroundAttachment backgroundAttachment, diff --git a/dw/table.cc b/dw/table.cc index b6f7209b..30ee99c7 100644 --- a/dw/table.cc +++ b/dw/table.cc @@ -1,7 +1,7 @@ /* * Dillo Widget * - * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org> + * Copyright 2005-2007, 2014 Sebastian Geerken <sgeerken@dillo.org> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,47 +28,48 @@ using namespace lout; namespace dw { +bool Table::adjustTableMinWidth = true; int Table::CLASS_ID = -1; Table::Table(bool limitTextWidth) { DBG_OBJ_CREATE ("dw::Table"); registerName ("dw::Table", &CLASS_ID); - setFlags (BLOCK_LEVEL); - setFlags (USES_HINTS); setButtonSensitive(false); this->limitTextWidth = limitTextWidth; rowClosed = false; - // random values - availWidth = 100; - availAscent = 100; - availDescent = 0; - numRows = 0; numCols = 0; curRow = -1; curCol = 0; + DBG_OBJ_SET_NUM ("numCols", numCols); + DBG_OBJ_SET_NUM ("numRows", numCols); + children = new misc::SimpleVector <Child*> (16); colExtremes = new misc::SimpleVector<core::Extremes> (8); + colWidthSpecified = new misc::SimpleVector<bool> (8); + colWidthPercentage = new misc::SimpleVector<bool> (8); colWidths = new misc::SimpleVector <int> (8); cumHeight = new misc::SimpleVector <int> (8); rowSpanCells = new misc::SimpleVector <int> (8); - colSpanCells = new misc::SimpleVector <int> (8); baseline = new misc::SimpleVector <int> (8); rowStyle = new misc::SimpleVector <core::style::Style*> (8); - hasColPercent = 0; - colPercents = new misc::SimpleVector <core::style::Length> (8); + colWidthsUpToDateWidthColExtremes = true; + DBG_OBJ_SET_BOOL ("colWidthsUpToDateWidthColExtremes", + colWidthsUpToDateWidthColExtremes); + + numColWidthSpecified = 0; + numColWidthPercentage = 0; redrawX = 0; redrawY = 0; } - Table::~Table() { for (int i = 0; i < children->size (); i++) { @@ -91,20 +92,22 @@ Table::~Table() delete children; delete colExtremes; + delete colWidthSpecified; + delete colWidthPercentage; delete colWidths; delete cumHeight; delete rowSpanCells; - delete colSpanCells; delete baseline; delete rowStyle; - delete colPercents; DBG_OBJ_DELETE (); } void Table::sizeRequestImpl (core::Requisition *requisition) { - forceCalcCellSizes (); + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequestImpl"); + + forceCalcCellSizes (true); /** * \bug Baselines are not regarded here. @@ -119,40 +122,46 @@ void Table::sizeRequestImpl (core::Requisition *requisition) + getStyle()->vBorderSpacing; requisition->descent = 0; + correctRequisition (requisition, core::splitHeightPreserveDescent); + + DBG_OBJ_LEAVE (); } void Table::getExtremesImpl (core::Extremes *extremes) { - if (numCols == 0) { - extremes->minWidth = extremes->maxWidth = 0; - return; - } + DBG_OBJ_ENTER0 ("resize", 0, "getExtremesImpl"); - forceCalcColumnExtremes (); + if (numCols == 0) + extremes->minWidth = extremes->minWidthIntrinsic = extremes->maxWidth = + extremes->maxWidthIntrinsic = boxDiffWidth (); + else { + forceCalcColumnExtremes (); - extremes->minWidth = extremes->maxWidth = - (numCols + 1) * getStyle()->hBorderSpacing - + getStyle()->boxDiffWidth (); - for (int col = 0; col < numCols; col++) { - extremes->minWidth += colExtremes->getRef(col)->minWidth; - extremes->maxWidth += colExtremes->getRef(col)->maxWidth; - } - if (core::style::isAbsLength (getStyle()->width)) { - extremes->minWidth = - misc::max (extremes->minWidth, - core::style::absLengthVal(getStyle()->width)); - extremes->maxWidth = - misc::max (extremes->maxWidth, - core::style::absLengthVal(getStyle()->width)); + extremes->minWidth = extremes->minWidthIntrinsic = extremes->maxWidth = + extremes->maxWidthIntrinsic = + (numCols + 1) * getStyle()->hBorderSpacing + boxDiffWidth (); + for (int col = 0; col < numCols; col++) { + extremes->minWidth += colExtremes->getRef(col)->minWidth; + extremes->minWidthIntrinsic += + colExtremes->getRef(col)->minWidthIntrinsic; + extremes->maxWidth += colExtremes->getRef(col)->maxWidth; + extremes->maxWidthIntrinsic += + colExtremes->getRef(col)->maxWidthIntrinsic; + } } - _MSG(" Table::getExtremesImpl, {%d, %d} numCols=%d\n", - extremes->minWidth, extremes->maxWidth, numCols); + correctExtremes (extremes); + + DBG_OBJ_LEAVE (); } void Table::sizeAllocateImpl (core::Allocation *allocation) { - calcCellSizes (); + DBG_OBJ_ENTER ("resize", 0, "sizeAllocateImpl", "%d, %d; %d * (%d + %d)", + allocation->x, allocation->y, allocation->width, + allocation->ascent, allocation->descent); + + calcCellSizes (true); /** * \bug Baselines are not regarded here. @@ -167,8 +176,7 @@ void Table::sizeAllocateImpl (core::Allocation *allocation) for (int row = 0; row < numRows; row++) { int n = row * numCols + col; if (childDefined (n)) { - int width = - (children->get(n)->cell.colspanEff - 1) + int width = (children->get(n)->cell.colspanEff - 1) * getStyle()->hBorderSpacing; for (int i = 0; i < children->get(n)->cell.colspanEff; i++) width += colWidths->get (col + i); @@ -192,6 +200,8 @@ void Table::sizeAllocateImpl (core::Allocation *allocation) x += colWidths->get (col) + getStyle()->hBorderSpacing; } + + DBG_OBJ_LEAVE (); } void Table::resizeDrawImpl () @@ -202,30 +212,133 @@ void Table::resizeDrawImpl () redrawY = getHeight (); } -void Table::setWidth (int width) +int Table::getAvailWidthOfChild (Widget *child, bool forceValue) { - // If limitTextWidth is set, a queueResize may also be necessary. - if (availWidth != width || limitTextWidth) { - _MSG(" Table::setWidth %d\n", width); - availWidth = width; - queueResize (0, false); - } + DBG_OBJ_ENTER ("resize", 0, "getAvailWidthOfChild", "%p, %s", + child, forceValue ? "true" : "false"); + + int width; + + // Unlike other containers, the table widget sometimes narrows + // columns to a width less than specified by CSS (see + // forceCalcCellSizes). For this reason, the column widths have to + // be calculated in all cases. + if (forceValue) { + calcCellSizes (false); + width = calcAvailWidthForDescendant (child); + } else + width = -1; + + DBG_OBJ_MSGF ("resize", 1, "=> %d", width); + DBG_OBJ_LEAVE (); + return width; } -void Table::setAscent (int ascent) +int Table::calcAvailWidthForDescendant (Widget *child) { - if (availAscent != ascent) { - availAscent = ascent; - queueResize (0, false); + DBG_OBJ_ENTER ("resize", 0, "calcAvailWidthForDescendant", "%p", child); + + // "child" is not a direct child, but a direct descendant. Search + // for the actual childs. + Widget *actualChild = child; + while (actualChild != NULL && actualChild->getParent () != this) + actualChild = actualChild->getParent (); + + assert (actualChild != NULL); + + // TODO This is inefficient. (Use parentRef?) + int width = -1; + for (int row = numRows - 1; width == -1 && row >= 0; row--) { + for (int col = 0; width == -1 && col < numCols; col++) { + int n = row * numCols + col; + if (childDefined (n) && + children->get(n)->cell.widget == actualChild) { + DBG_OBJ_MSGF ("resize", 1, "calculated from column %d", col); + width = (children->get(n)->cell.colspanEff - 1) + * getStyle()->hBorderSpacing; + for (int i = 0; i < children->get(n)->cell.colspanEff; i++) + width += colWidths->get (col + i); + width = misc::max (width, 0); + + if (child != actualChild) { + // For table cells (direct children: child == actualChild), + // CSS 'width' is already regarded in the column calculation. + // However, for children of the table cells, CSS 'width' must + // be regarded here. + + int corrWidth = width; + child->calcFinalWidth (child->getStyle(), -1, this, 0, true, + &corrWidth); + + // But better not exceed it ... (TODO: Only here?) + width = misc::min (width, corrWidth); + } + } + } } + + assert (width != -1); + + DBG_OBJ_MSGF ("resize", 1, "=> %d", width); + DBG_OBJ_LEAVE (); + return width; +} + +int Table::applyPerWidth (int containerWidth, core::style::Length perWidth) +{ + return core::style::multiplyWithPerLength (containerWidth, perWidth); } -void Table::setDescent (int descent) +int Table::applyPerHeight (int containerHeight, core::style::Length perHeight) { - if (availDescent != descent) { - availDescent = descent; - queueResize (0, false); + return core::style::multiplyWithPerLength (containerHeight, perHeight); +} + +void Table::containerSizeChangedForChildren () +{ + DBG_OBJ_ENTER0 ("resize", 0, "containerSizeChangedForChildren"); + + for (int col = 0; col < numCols; col++) { + for (int row = 0; row < numRows; row++) { + int n = row * numCols + col; + if (childDefined (n)) + children->get(n)->cell.widget->containerSizeChanged (); + } } + + DBG_OBJ_LEAVE (); +} + +bool Table::affectsSizeChangeContainerChild (core::Widget *child) +{ + DBG_OBJ_ENTER ("resize", 0, "affectsSizeChangeContainerChild", "%p", child); + + bool ret; + + // This is a bit more complicated, as compared to the standard + // implementation (Widget::affectsSizeChangeContainerChild). + // Height would handled the same way, but width is more + // complicated: we would have to track numerous values here. Always + // returning true is correct in all cases, but generally + // inefficient. + + // TODO Better solution? + + ret = true; + + DBG_OBJ_MSGF ("resize", 1, "=> %s", ret ? "true" : "false"); + DBG_OBJ_LEAVE (); + return ret; +} + +bool Table::usesAvailWidth () +{ + return true; +} + +bool Table::isBlockLevel () +{ + return true; } void Table::draw (core::View *view, core::Rectangle *area) @@ -272,6 +385,9 @@ core::Iterator *Table::iterator (core::Content::Type mask, bool atEnd) void Table::addCell (Widget *widget, int colspan, int rowspan) { + DBG_OBJ_ENTER ("resize", 0, "addCell", "%p, %d, %d", + widget, colspan, rowspan); + const int maxspan = 100; Child *child; int colspanEff; @@ -373,6 +489,8 @@ void Table::addCell (Widget *widget, int colspan, int rowspan) } MSG("\n"); #endif + + DBG_OBJ_LEAVE (); } void Table::addRow (core::style::Style *style) @@ -393,7 +511,7 @@ void Table::addRow (core::style::Style *style) rowClosed = false; } -TableCell *Table::getCellRef () +AlignedTableCell *Table::getCellRef () { core::Widget *child; @@ -401,14 +519,119 @@ TableCell *Table::getCellRef () int n = curCol + row * numCols; if (childDefined (n)) { child = children->get(n)->cell.widget; - if (child->instanceOf (TableCell::CLASS_ID)) - return (TableCell*)child; + if (child->instanceOf (AlignedTableCell::CLASS_ID)) + return (AlignedTableCell*)child; } } return NULL; } +const char *Table::getExtrModName (ExtrMod mod) +{ + switch (mod) { + case MIN: + return "MIN"; + + case MIN_INTR: + return "MIN_INTR"; + + case MIN_MIN: + return "MIN_MIN"; + + case MAX_MIN: + return "MAX_MIN"; + + case MAX: + return "MAX"; + + case MAX_INTR: + return "MAX_INTR"; + + case DATA: + return "DATA"; + + default: + misc::assertNotReached (); + return NULL; + } +} + +int Table::getExtreme (core::Extremes *extremes, ExtrMod mod) +{ + switch (mod) { + case MIN: + return extremes->minWidth; + + case MIN_INTR: + return extremes->minWidthIntrinsic; + + case MIN_MIN: + return misc::min (extremes->minWidth, extremes->minWidthIntrinsic); + + case MAX_MIN: + return misc::max (extremes->minWidth, extremes->minWidthIntrinsic); + + case MAX: + return extremes->maxWidth; + + case MAX_INTR: + return extremes->maxWidthIntrinsic; + + default: + misc::assertNotReached (); + return 0; + } +} + +void Table::setExtreme (core::Extremes *extremes, ExtrMod mod, int value) +{ + switch (mod) { + case MIN: + extremes->minWidth = value; + break; + + case MIN_INTR: + extremes->minWidthIntrinsic = value; + break; + + // MIN_MIN and MAX_MIN not supported here. + + case MAX: + extremes->maxWidth = value; + break; + + case MAX_INTR: + extremes->maxWidthIntrinsic = value; + break; + + default: + misc::assertNotReached (); + } +} + +int Table::getColExtreme (int col, ExtrMod mod, void *data) +{ + switch (mod) { + case DATA: + return ((misc::SimpleVector<int>*)data)->get (col); + + default: + return getExtreme (colExtremes->getRef(col), mod); + } +} + +void Table::setColExtreme (int col, ExtrMod mod, void *data, int value) +{ + switch (mod) { + case DATA: + ((misc::SimpleVector<int>*)data)->set (col, value); + + default: + setExtreme (colExtremes->getRef(col), mod, value); + } +} + void Table::reallocChildren (int newNumCols, int newNumRows) { assert (newNumCols >= numCols); @@ -476,107 +699,357 @@ void Table::reallocChildren (int newNumCols, int newNumRows) numCols = newNumCols; numRows = newNumRows; + + DBG_OBJ_SET_NUM ("numCols", numCols); + DBG_OBJ_SET_NUM ("numRows", numCols); } // ---------------------------------------------------------------------- -void Table::calcCellSizes () +void Table::calcCellSizes (bool calcHeights) { - if (needsResize ()) - forceCalcCellSizes (); + DBG_OBJ_ENTER ("resize", 0, "calcCellSizes", "%s", + calcHeights ? "true" : "false"); + + bool sizeChanged = needsResize () || resizeQueued (); + bool extremesChanges = extremesChanged () || extremesQueued (); + + if (calcHeights ? (extremesChanges || sizeChanged) : + (extremesChanges || !colWidthsUpToDateWidthColExtremes)) + forceCalcCellSizes (calcHeights); + + DBG_OBJ_LEAVE (); } -void Table::forceCalcCellSizes () +void Table::forceCalcCellSizes (bool calcHeights) { - int totalWidth = 0, childHeight, forceTotalWidth = 1; + DBG_OBJ_ENTER0 ("resize", 0, "forceCalcCellSizes"); + + int childHeight; core::Extremes extremes; // Will also call calcColumnExtremes(), when needed. getExtremes (&extremes); - if (core::style::isAbsLength (getStyle()->width)) { - totalWidth = core::style::absLengthVal (getStyle()->width); - } else if (core::style::isPerLength (getStyle()->width)) { - /* - * If the width is > 100%, we use 100%, this prevents ugly - * results. (May be changed in future, when a more powerful - * rendering is implemented, to handle fixed positions etc., - * as defined by CSS2.) - */ - totalWidth = - misc::min (core::style::multiplyWithPerLength (availWidth, - getStyle()->width), - availWidth); - } else if (getStyle()->width == core::style::LENGTH_AUTO) { - totalWidth = availWidth; - forceTotalWidth = 0; + int availWidth = getAvailWidth (true); + // When adjust_table_min_width is set, use the minimal (intrinsic) + // width for correction. + int corrWidth = + Table::getAdjustTableMinWidth () ? extremes.minWidthIntrinsic : 0; + int totalWidth = misc::max (availWidth, corrWidth) + - ((numCols + 1) * getStyle()->hBorderSpacing + boxDiffWidth ()); + + DBG_OBJ_MSGF ("resize", 1, + "totalWidth = max (%d, %d) - ((%d - 1) * %d + %d) = <b>%d</b>", + availWidth, corrWidth, numCols, getStyle()->hBorderSpacing, + boxDiffWidth (), totalWidth); + + colWidths->setSize (numCols, 0); + cumHeight->setSize (numRows + 1, 0); + rowSpanCells->setSize (0); + baseline->setSize (numRows); + + misc::SimpleVector<int> *oldColWidths = colWidths; + colWidths = new misc::SimpleVector <int> (8); + + int minWidth = 0, minWidthIntrinsic = 0, maxWidth = 0; + for (int col = 0; col < colExtremes->size(); col++) { + minWidth += colExtremes->getRef(col)->minWidth; + minWidthIntrinsic += colExtremes->getRef(col)->minWidthIntrinsic; + maxWidth += colExtremes->getRef(col)->maxWidth; } - _MSG(" availWidth = %d\n", availWidth); - _MSG(" totalWidth1 = %d\n", totalWidth); + // CSS 'width' defined and effective? + bool totalWidthSpecified = false; + if (getStyle()->width != core::style::LENGTH_AUTO) { + // Even if 'width' is defined, it may not have a defined value. We try + // this trick (should perhaps be replaced by a cleaner solution): + core::Requisition testReq = { -1, -1, -1 }; + correctRequisition (&testReq, core::splitHeightPreserveDescent); + if (testReq.width != -1) + totalWidthSpecified = true; + } - if (totalWidth < extremes.minWidth) - totalWidth = extremes.minWidth; - totalWidth = totalWidth - - (numCols + 1) * getStyle()->hBorderSpacing - - getStyle()->boxDiffWidth (); + DBG_OBJ_MSGF ("resize", 1, + "minWidth = %d, minWidthIntrinsic = %d, maxWidth %d, " + "totalWidth = %d, %s", + minWidth, minWidthIntrinsic, maxWidth, totalWidth, + totalWidthSpecified ? "specified" : "not specified"); + + if (minWidth > totalWidth) { + DBG_OBJ_MSG ("resize", 1, "case 1: minWidth > totalWidth"); + + // The sum of all column minima is larger than the available + // width, so we narrow the columns (see also CSS2 spec, + // section 17.5, #6). We use a similar apportioning, but not + // bases on minimal and maximal widths, but on intrinsic minimal + // widths and corrected minimal widths. This way, intrinsic + // extremes are preferred (so avoiding columns too narrow for + // the actual contents), at the expenses of corrected ones + // (which means that sometimes CSS values are handled + // incorrectly). + + // A special case is a table with columns whose widths are + // defined by percentage values. In this case, all other columns + // are applied the intrinsic minimal width, while larger values + // are applied to the columns with percentage width (but not + // larger than the corrected width). The left columns are + // preferred, but it is ensured that no column is narrower than + // the intrinsic minimum. + // + // Example two columns with both "width: 70%" will be displayed like + // this: + // + // -------------------------------------------------- + // | | | + // -------------------------------------------------- + // + // The first gets indeed 70% of the total width, the second only + // the rest. + // + // This somewhat strange behaviour tries to mimic the somewhat + // strange behaviour of Firefox and Chromium. + + if (numColWidthPercentage == 0 || minWidthIntrinsic >= totalWidth) { + // Latter case (minWidthIntrinsic >= totalWidth): special treating + // of percentage values would not make sense. + + DBG_OBJ_MSG ("resize", 1, "case 1a: simple apportioning"); + + apportion2 (totalWidth, 0, colExtremes->size() - 1, MIN_MIN, MAX_MIN, + NULL, colWidths, 0); + } else { + DBG_OBJ_MSG ("resize", 1, "case 1b: treat percentages specially"); - _MSG(" totalWidth2 = %d curCol=%d\n", totalWidth,curCol); + colWidths->setSize (colExtremes->size(), 0); + // Keep track of the width which is apportioned to the rest + // of the columns with percentage width (widthPartPer), and + // the minimal width (intrinsic minimum) which is needed for + // the rest of these columns (minWidthIntrinsicPer). - colWidths->setSize (numCols, 0); - cumHeight->setSize (numRows + 1, 0); - rowSpanCells->setSize (0); - baseline->setSize (numRows); + int widthPartPer = totalWidth, minWidthIntrinsicPer = 0; + for (int col = 0; col < colExtremes->size(); col++) + if (colWidthPercentage->get (col)) + minWidthIntrinsicPer += + colExtremes->getRef(col)->minWidthIntrinsic; + else + // Columns without percentage width get only the + // intrinsic mininal, so subtract this from the width for the + // columns *with* percentage + widthPartPer -= + colExtremes->getRef(col)->minWidthIntrinsic; - _MSG(" extremes = %d,%d\n", extremes.minWidth, extremes.maxWidth); - _MSG(" getStyle()->boxDiffWidth() = %d\n", getStyle()->boxDiffWidth()); - _MSG(" getStyle()->hBorderSpacing = %d\n", getStyle()->hBorderSpacing); + DBG_OBJ_MSGF ("resize", 1, + "widthPartPer = %d, minWidthIntrinsicPer = %d", + widthPartPer, minWidthIntrinsicPer); + for (int col = 0; col < colExtremes->size(); col++) + if (colWidthPercentage->get (col)) { + int colWidth = colExtremes->getRef(col)->minWidth; + int minIntr = colExtremes->getRef(col)->minWidthIntrinsic; - apportion_percentages2 (totalWidth, forceTotalWidth); - if (!hasColPercent) - apportion2 (totalWidth, forceTotalWidth); + minWidthIntrinsicPer -= minIntr; - setCumHeight (0, 0); - for (int row = 0; row < numRows; row++) { - /** - * \bug dw::Table::baseline is not filled. - */ - int rowHeight = 0; + if (colWidth > widthPartPer - minWidthIntrinsicPer) + colWidth = widthPartPer - minWidthIntrinsicPer; - for (int col = 0; col < numCols; col++) { - int n = row * numCols + col; - if (childDefined (n)) { - int width = (children->get(n)->cell.colspanEff - 1) - * getStyle()->hBorderSpacing; - for (int i = 0; i < children->get(n)->cell.colspanEff; i++) - width += colWidths->get (col + i); + colWidths->set (col, colWidth); + widthPartPer -= colWidth; - core::Requisition childRequisition; - children->get(n)->cell.widget->setWidth (width); - children->get(n)->cell.widget->sizeRequest (&childRequisition); - childHeight = childRequisition.ascent + childRequisition.descent; - if (children->get(n)->cell.rowspan == 1) { - rowHeight = misc::max (rowHeight, childHeight); + DBG_OBJ_MSGF ("resize", 1, + "#%d: colWidth = %d ... widthPartPer = %d, " + "minWidthIntrinsicPer = %d", + col, colWidth, widthPartPer, minWidthIntrinsicPer); + + } else + colWidths->set (col, + colExtremes->getRef(col)->minWidthIntrinsic); + + } + } else if (totalWidthSpecified && totalWidth > maxWidth) { + DBG_OBJ_MSG ("resize", 1, + "case 2: totalWidthSpecified && totalWidth > maxWidth"); + + // The width is specified (and so enforced), but all maxima sum + // up to less than this specified width. The columns will have + // there maximal width, and the extra space is apportioned + // according to the column widths, and so to the column + // maxima. This is done by simply passing MAX twice to the + // apportioning function. + + // When column widths are specified (numColWidthSpecified > 0, + // as calculated in forceCalcColumnExtremes()), they are treated + // specially and excluded from the apportioning, so that the + // specified column widths are enforced. An exception is when + // all columns are specified: in this case they must be + // enlargened to fill the whole table width. + + if (numColWidthSpecified == 0 || + numColWidthSpecified == colExtremes->size()) { + DBG_OBJ_MSG ("resize", 1, + "subcase 2a: no or all columns with specified width"); + apportion2 (totalWidth, 0, colExtremes->size() - 1, MAX, MAX, NULL, + colWidths, 0); + } else { + DBG_OBJ_MSGF ("resize", 1, + "subcase 2b: %d column(s) with specified width", + numColWidthSpecified); + + // Seperate columns with specified and unspecified width, and + // apply apportion2() only to the latter. + + int numNotSpecified = colExtremes->size() - numColWidthSpecified; + + misc::SimpleVector<int> widthsNotSpecified (numNotSpecified); + widthsNotSpecified.setSize (numNotSpecified); + misc::SimpleVector<int> apportionDest (numNotSpecified); + + int totalWidthNotSpecified = totalWidth, indexNotSpecified = 0; + for (int col = 0; col < colExtremes->size(); col++) + if (colWidthSpecified->get (col)) + totalWidthNotSpecified -= colExtremes->getRef(col)->maxWidth; + else { + widthsNotSpecified.set (indexNotSpecified, + colExtremes->getRef(col)->maxWidth); + indexNotSpecified++; + } + + DBG_IF_RTFL { + DBG_OBJ_MSGF ("resize", 1, "totalWidthNotSpecified = %d", + totalWidthNotSpecified); + + DBG_OBJ_MSG ("resize", 1, "widthsNotSpecified:"); + DBG_OBJ_MSG_START (); + + for (int i = 0; i < widthsNotSpecified.size (); i++) + DBG_OBJ_MSGF ("resize", 1, "#%d: %d", + i, widthsNotSpecified.get (i)); + + DBG_OBJ_MSG_END (); + } + + apportion2 (totalWidthNotSpecified, 0, numNotSpecified - 1, DATA, DATA, + (void*)&widthsNotSpecified, &apportionDest, 0); + + DBG_IF_RTFL { + DBG_OBJ_MSG ("resize", 1, "apportionDest:"); + DBG_OBJ_MSG_START (); + + for (int i = 0; i < apportionDest.size (); i++) + DBG_OBJ_MSGF ("resize", 1, "#%d: %d", i, apportionDest.get (i)); + + DBG_OBJ_MSG_END (); + } + + DBG_OBJ_MSG ("resize", 1, "finally setting column widths:"); + DBG_OBJ_MSG_START (); + + colWidths->setSize (colExtremes->size()); + indexNotSpecified = 0; + for (int col = 0; col < colExtremes->size(); col++) + if (colWidthSpecified->get (col)) { + DBG_OBJ_MSGF ("resize", 1, "#%d: specified, gets maximum %d", + col, colExtremes->getRef(col)->maxWidth); + colWidths->set (col, colExtremes->getRef(col)->maxWidth); } else { - rowSpanCells->increase(); - rowSpanCells->set(rowSpanCells->size()-1, n); + DBG_OBJ_MSGF ("resize", 1, "#%d: not specified, gets value %d " + "at position %d from temporary list", + col, apportionDest.get (indexNotSpecified), + indexNotSpecified); + colWidths->set (col, apportionDest.get (indexNotSpecified)); + indexNotSpecified++; } + + DBG_OBJ_MSG_END (); + } + } else { + // Normal apportioning. + int width = + totalWidthSpecified ? totalWidth : misc::min (totalWidth, maxWidth); + DBG_OBJ_MSGF ("resize", 1, "case 3: else; width = %d", width); + apportion2 (width, 0, colExtremes->size() - 1, MIN, MAX, NULL, colWidths, + 0); + } + + // TODO: Adapted from old inline function "setColWidth". But (i) is + // this anyway correct (col width is is not x)? And does the + // performance gain actually play a role? + for (int col = 0; col < colExtremes->size(); col++) { + if (colWidths->get (col) != oldColWidths->get (col)) + redrawX = lout::misc::min (redrawX, colWidths->get (col)); + } + + DBG_IF_RTFL { + DBG_OBJ_SET_NUM ("colWidths.size", colWidths->size ()); + for (int i = 0; i < colWidths->size (); i++) + DBG_OBJ_ARRSET_NUM ("colWidths", i, colWidths->get (i)); + } + + colWidthsUpToDateWidthColExtremes = true; + DBG_OBJ_SET_BOOL ("colWidthsUpToDateWidthColExtremes", + colWidthsUpToDateWidthColExtremes); + + for (int col = 0; col < numCols; col++) { + if (col >= oldColWidths->size () || col >= colWidths->size () || + oldColWidths->get (col) != colWidths->get (col)) { + // Column width has changed, tell children about this. + for (int row = 0; row < numRows; row++) { + int n = row * numCols + col; + // TODO: Columns spanning several rows are only regarded + // when the first column is affected. + if (childDefined (n)) + children->get(n)->cell.widget->containerSizeChanged (); } - }/*for col*/ + } + } + + delete oldColWidths; - setCumHeight (row + 1, - cumHeight->get (row) + rowHeight + getStyle()->vBorderSpacing); + if (calcHeights) { + setCumHeight (0, 0); + for (int row = 0; row < numRows; row++) { + /** + * \bug dw::Table::baseline is not filled. + */ + int rowHeight = 0; + + for (int col = 0; col < numCols; col++) { + int n = row * numCols + col; + if (childDefined (n)) { + int width = (children->get(n)->cell.colspanEff - 1) + * getStyle()->hBorderSpacing; + for (int i = 0; i < children->get(n)->cell.colspanEff; i++) + width += colWidths->get (col + i); + + core::Requisition childRequisition; + //children->get(n)->cell.widget->setWidth (width); + children->get(n)->cell.widget->sizeRequest (&childRequisition); + childHeight = childRequisition.ascent + childRequisition.descent; + if (children->get(n)->cell.rowspan == 1) { + rowHeight = misc::max (rowHeight, childHeight); + } else { + rowSpanCells->increase(); + rowSpanCells->set(rowSpanCells->size()-1, n); + } + } + } // for col - }/*for row*/ + setCumHeight (row + 1, + cumHeight->get (row) + rowHeight + getStyle()->vBorderSpacing); + } // for row + + apportionRowSpan (); + } - apportionRowSpan (); + DBG_OBJ_LEAVE (); } void Table::apportionRowSpan () { + DBG_OBJ_ENTER0 ("resize", 0, "apportionRowSpan"); + int *rowHeight = NULL; for (int c = 0; c < rowSpanCells->size(); ++c) { @@ -630,6 +1103,8 @@ void Table::apportionRowSpan () setCumHeight (i+1, cumHeight->get(i) + rowHeight[i]); } delete[] rowHeight; + + DBG_OBJ_LEAVE (); } @@ -640,8 +1115,12 @@ void Table::apportionRowSpan () */ void Table::calcColumnExtremes () { - if (extremesChanged ()) + DBG_OBJ_ENTER0 ("resize", 0, "calcColumnExtremes"); + + if (extremesChanged () || extremesQueued ()) forceCalcColumnExtremes (); + + DBG_OBJ_LEAVE (); } @@ -650,570 +1129,330 @@ void Table::calcColumnExtremes () */ void Table::forceCalcColumnExtremes () { - _MSG(" Table::forceCalcColumnExtremes numCols=%d\n", numCols); + DBG_OBJ_ENTER0 ("resize", 0, "forceCalcColumnExtremes"); - if (numCols == 0) - return; + if (numCols > 0) { + lout::misc::SimpleVector<int> colSpanCells (8); + colExtremes->setSize (numCols); + colWidthSpecified->setSize (numCols); + colWidthPercentage->setSize (numCols); - colExtremes->setSize (numCols); - colPercents->setSize (numCols); - colSpanCells->setSize (0); - /* 1. cells with colspan = 1 */ - for (int col = 0; col < numCols; col++) { - colExtremes->getRef(col)->minWidth = 0; - colExtremes->getRef(col)->maxWidth = 0; - colPercents->set(col, core::style::LENGTH_AUTO); + // 1. cells with colspan = 1 + for (int col = 0; col < numCols; col++) { + DBG_OBJ_MSGF ("resize", 1, "column %d", col); + DBG_OBJ_MSG_START (); + + colWidthSpecified->set (col, false); + colWidthPercentage->set (col, false); + + colExtremes->getRef(col)->minWidth = 0; + colExtremes->getRef(col)->minWidthIntrinsic = 0; + colExtremes->getRef(col)->maxWidth = 0; + colExtremes->getRef(col)->maxWidthIntrinsic = 0; + + for (int row = 0; row < numRows; row++) { + DBG_OBJ_MSGF ("resize", 1, "row %d", row); + DBG_OBJ_MSG_START (); + + int n = row * numCols + col; + + if (childDefined (n)) { + if (children->get(n)->cell.colspanEff == 1) { + core::Extremes cellExtremes; + children->get(n)->cell.widget->getExtremes (&cellExtremes); + + DBG_OBJ_MSGF ("resize", 1, "child: %d / %d", + cellExtremes.minWidth, cellExtremes.maxWidth); + + colExtremes->getRef(col)->minWidthIntrinsic = + misc::max (colExtremes->getRef(col)->minWidthIntrinsic, + cellExtremes.minWidthIntrinsic); + colExtremes->getRef(col)->maxWidthIntrinsic = + misc::max (colExtremes->getRef(col)->minWidthIntrinsic, + colExtremes->getRef(col)->maxWidthIntrinsic, + cellExtremes.maxWidthIntrinsic); + + colExtremes->getRef(col)->minWidth = + misc::max (colExtremes->getRef(col)->minWidth, + cellExtremes.minWidth); + colExtremes->getRef(col)->maxWidth = + misc::max (colExtremes->getRef(col)->minWidth, + colExtremes->getRef(col)->maxWidth, + cellExtremes.maxWidth); + + core::style::Length childWidth = + children->get(n)->cell.widget->getStyle()->width; + if (childWidth != core::style::LENGTH_AUTO) { + colWidthSpecified->set (col, true); + if (core::style::isPerLength (childWidth)) + colWidthPercentage->set (col, true); + } - for (int row = 0; row < numRows; row++) { - int n = row * numCols + col; - if (!childDefined (n)) - continue; - if (children->get(n)->cell.colspanEff == 1) { - core::Extremes cellExtremes; - int cellMinW, cellMaxW, pbm; - core::style::Length width = - children->get(n)->cell.widget->getStyle()->width; - pbm = (numCols + 1) * getStyle()->hBorderSpacing - + children->get(n)->cell.widget->getStyle()->boxDiffWidth (); - children->get(n)->cell.widget->getExtremes (&cellExtremes); - if (core::style::isAbsLength (width)) { - // Fixed lengths include table padding, border and margin. - cellMinW = cellExtremes.minWidth; - cellMaxW = misc::max (cellMinW, - core::style::absLengthVal(width) - pbm); - } else { - cellMinW = cellExtremes.minWidth; - cellMaxW = cellExtremes.maxWidth; + DBG_OBJ_MSGF ("resize", 1, "column: %d / %d (%d / %d)", + colExtremes->getRef(col)->minWidth, + colExtremes->getRef(col)->maxWidth, + colExtremes->getRef(col)->minWidthIntrinsic, + colExtremes->getRef(col)->maxWidthIntrinsic); + } else { + colSpanCells.increase (); + colSpanCells.setLast (n); + } } - _MSG("FCCE, col%d colMin,colMax,cellMin,cellMax = %d,%d,%d,%d\n", - col, - colExtremes->getRef(col)->minWidth, - colExtremes->getRef(col)->maxWidth, - cellMinW, cellMaxW); - - colExtremes->getRef(col)->minWidth = - misc::max (colExtremes->getRef(col)->minWidth, cellMinW); - colExtremes->getRef(col)->maxWidth = - misc::max (colExtremes->getRef(col)->minWidth, misc::max ( - colExtremes->getRef(col)->maxWidth, - cellMaxW)); - - // Also fill the colPercents array in this pass - if (core::style::isPerLength (width)) { - hasColPercent = 1; - if (colPercents->get(col) == core::style::LENGTH_AUTO) - colPercents->set(col, width); - } else if (core::style::isAbsLength (width)) { - // We treat LEN_ABS as a special case of LEN_AUTO. - /* - * if (colPercents->get(col) == LEN_AUTO) - * colPercents->set(col, LEN_ABS); - * - * (Hint: that's old code!) - */ - } - } else { - colSpanCells->increase(); - colSpanCells->set(colSpanCells->size()-1, n); + DBG_OBJ_MSG_END (); } - } - } - - /* 2. cells with colspan > 1 */ - /* If needed, here we set proportionally apportioned col maximums */ - for (int c = 0; c < colSpanCells->size(); ++c) { - core::Extremes cellExtremes; - int cellMinW, cellMaxW, pbm; - int n = colSpanCells->get(c); - int col = n % numCols; - int cs = children->get(n)->cell.colspanEff; - core::style::Length width = - children->get(n)->cell.widget->getStyle()->width; - pbm = (numCols + 1) * getStyle()->hBorderSpacing - + children->get(n)->cell.widget->getStyle()->boxDiffWidth (); - children->get(n)->cell.widget->getExtremes (&cellExtremes); - if (core::style::isAbsLength (width)) { - // Fixed lengths include table padding, border and margin. - cellMinW = cellExtremes.minWidth; - cellMaxW = - misc::max (cellMinW, core::style::absLengthVal(width) - pbm); - } else { - cellMinW = cellExtremes.minWidth; - cellMaxW = cellExtremes.maxWidth; - } - int minSumCols = 0, maxSumCols = 0; - for (int i = 0; i < cs; ++i) { - minSumCols += colExtremes->getRef(col+i)->minWidth; - maxSumCols += colExtremes->getRef(col+i)->maxWidth; - } - _MSG("cs=%d spanWidth=%d,%d sumCols=%d,%d\n", - cs,cellMinW,cellMaxW,minSumCols,maxSumCols); - - if (minSumCols >= cellMinW && maxSumCols >= cellMaxW) - continue; - - // Cell size is too small; apportion {min,max} for this colspan. - int spanMinW = misc::max (misc::max (cs, minSumCols), - cellMinW - (cs-1) * getStyle()->hBorderSpacing), - spanMaxW = misc::max (misc::max (cs, maxSumCols), - cellMaxW - (cs-1) * getStyle()->hBorderSpacing); - - if (minSumCols == 0) { - // No single cells defined for this span => pre-apportion equally - minSumCols = spanMinW; maxSumCols = spanMaxW; - int minW = spanMinW, maxW = spanMaxW; - for (int i = 0; i < cs; ++i) { - colExtremes->getRef(col+i)->minWidth = minW / (cs - i); - colExtremes->getRef(col+i)->maxWidth = maxW / (cs - i); - minW -= colExtremes->getRef(col+i)->minWidth; - maxW -= colExtremes->getRef(col+i)->maxWidth; - } + DBG_OBJ_MSG_END (); } - // These values will help if the span has percents. - int spanHasColPercent = 0; - int availSpanMinW = spanMinW; - float cumSpanPercent = 0.0f; - for (int i = col; i < col + cs; ++i) { - if (core::style::isPerLength (colPercents->get(i))) { - cumSpanPercent += core::style::perLengthVal (colPercents->get(i)); - ++spanHasColPercent; - } else - availSpanMinW -= colExtremes->getRef(i)->minWidth; - } + // 2. cells with colspan > 1 - // Calculate weighted-apportion columns for this span. - int wMin = 0, wMax; - int cumMaxWnew = 0, cumMaxWold = 0, goalMaxW = spanMaxW; - int curAppW = maxSumCols; - int curExtraW = spanMinW - minSumCols; - for (int i = col; i < col + cs; ++i) { - - if (!spanHasColPercent) { - int d_a = colExtremes->getRef(i)->maxWidth; - int d_w = curAppW > 0 ? (int)((float)curExtraW * d_a/curAppW) : 0; - if (d_a < 0||d_w < 0) { - MSG("d_a=%d d_w=%d\n",d_a,d_w); - exit(1); - } - wMin = colExtremes->getRef(i)->minWidth + d_w; - colExtremes->getRef(i)->minWidth = wMin; - curExtraW -= d_w; - curAppW -= d_a; - } else { - if (core::style::isPerLength (colPercents->get(i))) { - // multiplyWithPerLength would cause rounding errors, - // therefore the deprecated way, using perLengthVal: - wMin = misc::max (colExtremes->getRef(i)->minWidth, - (int)(availSpanMinW * - core::style::perLengthVal - (colPercents->get (i)) - / cumSpanPercent)); - colExtremes->getRef(i)->minWidth = wMin; - } - } + // TODO: Is this old comment still relevant? "If needed, here we + // set proportionally apportioned col maximums." - wMax = (goalMaxW-cumMaxWnew <= 0) ? 0 : - (int)((float)(goalMaxW-cumMaxWnew) - * colExtremes->getRef(i)->maxWidth - / (maxSumCols-cumMaxWold)); - wMax = misc::max (wMin, wMax); - cumMaxWnew += wMax; - cumMaxWold += colExtremes->getRef(i)->maxWidth; - colExtremes->getRef(i)->maxWidth = wMax; + for (int i = 0; i < colSpanCells.size(); i++) { + int n = colSpanCells.get (i); + int col = n % numCols; + int cs = children->get(n)->cell.colspanEff; - _MSG("i=%d, wMin=%d wMax=%d cumMaxWold=%d\n", - i,wMin,wMax,cumMaxWold); + core::Extremes cellExtremes; + children->get(n)->cell.widget->getExtremes (&cellExtremes); - } -#ifdef DBG - MSG("col min,max: ["); - for (int i = 0; i < numCols; i++) - MSG("%d,%d ", - colExtremes->getRef(i)->minWidth, - colExtremes->getRef(i)->maxWidth); - MSG("]\n"); - MSG("getStyle()->hBorderSpacing = %d\n", getStyle()->hBorderSpacing); -#endif - } -} + calcExtremesSpanMulteCols (col, cs, &cellExtremes, MIN, MAX, NULL); + calcExtremesSpanMulteCols (col, cs, &cellExtremes, MIN_INTR, MAX_INTR, + NULL); -/** - * \brief Apportionment function for AUTO-length columns. - * 'extremes' comes filled, 'result' comes defined for percentage columns. - */ -void Table::apportion2 (int totalWidth, int forceTotalWidth) -{ - if (colExtremes->size() == 0) - return; -#ifdef DBG - MSG("app2, availWidth=%d, totalWidth=%d, forceTotalWidth=%d\n", - availWidth, totalWidth, forceTotalWidth); - MSG("app2, extremes: ( "); - for (int i = 0; i < colExtremes->size (); i++) - MSG("%d,%d ", - colExtremes->get(i).minWidth, colExtremes->get(i).maxWidth); - MSG(")\n"); -#endif - int minAutoWidth = 0, maxAutoWidth = 0, availAutoWidth = totalWidth; - for (int col = 0; col < numCols; col++) { - if (core::style::isAbsLength (colPercents->get(col))) { - // set absolute lengths - setColWidth (col, colExtremes->get(col).minWidth); + core::style::Length childWidth = + children->get(n)->cell.widget->getStyle()->width; + if (childWidth != core::style::LENGTH_AUTO) { + for (int j = 0; j < cs; j++) + colWidthSpecified->set (col + j, true); + if (core::style::isPerLength (childWidth)) + for (int j = 0; j < cs; j++) + colWidthPercentage->set (col + j, true); + } } - if (colPercents->get(col) == core::style::LENGTH_AUTO) { - maxAutoWidth += colExtremes->get(col).maxWidth; - minAutoWidth += colExtremes->get(col).minWidth; - } else - availAutoWidth -= colWidths->get(col); } - if (!maxAutoWidth) // no core::style::LENGTH_AUTO cols! - return; - - colWidths->setSize (colExtremes->size (), 0); - - if (!forceTotalWidth && maxAutoWidth < availAutoWidth) { - // Enough space for the maximum table, don't widen past max. - availAutoWidth = maxAutoWidth; + numColWidthSpecified = 0; + numColWidthSpecified = 0; + for (int i = 0; i < colExtremes->size (); i++) { + if (colWidthSpecified->get (i)) + numColWidthSpecified++; + if (colWidthPercentage->get (i)) + numColWidthPercentage++; } - // General case. - int curTargetWidth = misc::max (availAutoWidth, minAutoWidth); - int curExtraWidth = curTargetWidth - minAutoWidth; - int curMaxWidth = maxAutoWidth; - int curNewWidth = minAutoWidth; - for (int col = 0; col < numCols; col++) { - _MSG("app2, col %d, minWidth=%d maxWidth=%d\n", - col, colExtremes->getRef(col)->minWidth, - colExtremes->get(col).maxWidth); - - if (colPercents->get(col) != core::style::LENGTH_AUTO) - continue; - - int colMinWidth = colExtremes->getRef(col)->minWidth; - int colMaxWidth = colExtremes->getRef(col)->maxWidth; - int w = (curMaxWidth <= 0) ? 0 : - (int)((float)curTargetWidth * colMaxWidth/curMaxWidth); - - _MSG("app2, curTargetWidth=%d colMaxWidth=%d curMaxWidth=%d " - "curNewWidth=%d ", - curTargetWidth, colMaxWidth,curMaxWidth,curNewWidth); - _MSG("w = %d, ", w); - - if (w <= colMinWidth) - w = colMinWidth; - else if (curNewWidth - colMinWidth + w > curTargetWidth) - w = colMinWidth + curExtraWidth; - - _MSG("w = %d\n", w); - - curNewWidth -= colMinWidth; - curMaxWidth -= colMaxWidth; - curExtraWidth -= (w - colMinWidth); - curTargetWidth -= w; - setColWidth (col, w); - } -#ifdef DBG - MSG("app2, result: ( "); - for (int i = 0; i < colWidths->size (); i++) - MSG("%d ", colWidths->get (i)); - MSG(")\n"); -#endif -} - -void Table::apportion_percentages2(int totalWidth, int forceTotalWidth) -{ - int hasTablePercent = core::style::isPerLength (getStyle()->width) ? 1 : 0; - - if (colExtremes->size() == 0 || (!hasTablePercent && !hasColPercent)) - return; - - // If there's a table-wide percentage, totalWidth comes already scaled. - _MSG("APP_P, availWidth=%d, totalWidth=%d, forceTotalWidth=%d\n", - availWidth, totalWidth, forceTotalWidth); - - if (!hasColPercent) { -#ifdef DBG - MSG("APP_P, only a table-wide percentage\n"); - MSG("APP_P, extremes = { "); - for (int col = 0; col < numCols; col++) - MSG("%d,%d ", colExtremes->getRef(col)->minWidth, - colExtremes->getRef(col)->maxWidth); - MSG("}\n"); -#endif - // It has only a table-wide percentage. Apportion non-absolute widths. - int sumMaxWidth = 0, perAvailWidth = totalWidth; - for (int col = 0; col < numCols; col++) { - if (core::style::isAbsLength (colPercents->get(col))) - perAvailWidth -= colExtremes->getRef(col)->maxWidth; - else - sumMaxWidth += colExtremes->getRef(col)->maxWidth; - } - - _MSG("APP_P, perAvailWidth=%d, sumMaxWidth=%d\n", - perAvailWidth, sumMaxWidth); - - for (int col = 0; col < numCols; col++) { - int max_wi = colExtremes->getRef(col)->maxWidth, new_wi; - if (!core::style::isAbsLength (colPercents->get(col))) { - new_wi = - misc::max (colExtremes->getRef(col)->minWidth, - (int)((float)max_wi * perAvailWidth/sumMaxWidth)); - setColWidth (col, new_wi); - perAvailWidth -= new_wi; - sumMaxWidth -= max_wi; - } - } -#ifdef DBG - MSG("APP_P, result = { "); - for (int col = 0; col < numCols; col++) - MSG("%d ", colWidths->get(col)); - MSG("}\n"); -#endif - - } else { - // we'll have to apportion... - _MSG("APP_P, we'll have to apportion...\n"); - - // Calculate cumPercent and available space - float cumPercent = 0.0f; - int hasAutoCol = 0; - int sumMinWidth = 0, sumMaxWidth = 0, sumMinNonPer = 0, sumMaxNonPer = 0; - for (int col = 0; col < numCols; col++) { - if (core::style::isPerLength (colPercents->get(col))) { - cumPercent += core::style::perLengthVal (colPercents->get(col)); - } else { - sumMinNonPer += colExtremes->getRef(col)->minWidth; - sumMaxNonPer += colExtremes->getRef(col)->maxWidth; - if (colPercents->get(col) == core::style::LENGTH_AUTO) - hasAutoCol++; - } - sumMinWidth += colExtremes->getRef(col)->minWidth; - sumMaxWidth += colExtremes->getRef(col)->maxWidth; - - _MSG("APP_P, col %d minWidth=%d maxWidth=%d\n", col, - colExtremes->getRef(col)->minWidth, - colExtremes->getRef(col)->maxWidth); - } - int oldTotalWidth = totalWidth; - if (!forceTotalWidth) { - if (sumMaxNonPer == 0 || cumPercent < 0.99f) { - // only percentage columns, or cumPercent < 100% => restrict width - int totW = (int)(sumMaxNonPer / (1.0f - cumPercent)); - for (int col = 0; col < numCols; col++) { - totW = misc::max - (totW, - (int)(colExtremes->getRef(col)->maxWidth - / core::style::perLengthVal (colPercents->get(col)))); - } - totalWidth = misc::min (totW, totalWidth); - } + DBG_IF_RTFL { + DBG_OBJ_SET_NUM ("colExtremes.size", colExtremes->size ()); + for (int i = 0; i < colExtremes->size (); i++) { + DBG_OBJ_ARRATTRSET_NUM ("colExtremes", i, "minWidth", + colExtremes->get(i).minWidth); + DBG_OBJ_ARRATTRSET_NUM ("colExtremes", i, "minWidthIntrinsic", + colExtremes->get(i).minWidthIntrinsic); + DBG_OBJ_ARRATTRSET_NUM ("colExtremes", i, "maxWidth", + colExtremes->get(i).maxWidth); + DBG_OBJ_ARRATTRSET_NUM ("colExtremes", i, "maxWidthIntrinsic", + colExtremes->get(i).maxWidthIntrinsic); } - // make sure there's enough space - totalWidth = misc::max (totalWidth, sumMinWidth); - // extraWidth is always >= 0 - int extraWidth = totalWidth - sumMinWidth; - int sumMinWidthPer = sumMinWidth - sumMinNonPer; - int curPerWidth = sumMinWidthPer; - // percentages refer to workingWidth - int workingWidth = totalWidth - sumMinNonPer; - if (cumPercent < 0.99f) { - // In this case, use the whole table width - workingWidth = totalWidth; - curPerWidth = sumMinWidth; - } - - _MSG("APP_P, oldTotalWidth=%d totalWidth=%d" - " workingWidth=%d extraWidth=%d sumMinNonPer=%d\n", - oldTotalWidth,totalWidth,workingWidth,extraWidth,sumMinNonPer); - - for (int col = 0; col < numCols; col++) { - int colMinWidth = colExtremes->getRef(col)->minWidth; - if (core::style::isPerLength (colPercents->get(col))) { - int w = core::style::multiplyWithPerLength (workingWidth, - colPercents->get(col)); - if (w < colMinWidth) - w = colMinWidth; - else if (curPerWidth - colMinWidth + w > workingWidth) - w = colMinWidth + extraWidth; - extraWidth -= (w - colMinWidth); - curPerWidth += (w - colMinWidth); - setColWidth (col, w); - } else { - setColWidth (col, colMinWidth); - } - } - - if (cumPercent < 0.99f) { - // Will have to apportion the other columns -#ifdef DBG - MSG("APP_P, extremes: ( "); - for (int i = 0; i < colExtremes->size (); i++) - MSG("%d,%d ", - colExtremes->get(i).minWidth, colExtremes->get(i).maxWidth); - MSG(")\n"); -#endif - curPerWidth -= sumMinNonPer; - int perWidth = (int)(curPerWidth/cumPercent); - totalWidth = misc::max (totalWidth, perWidth); - totalWidth = misc::min (totalWidth, oldTotalWidth); - - _MSG("APP_P, curPerWidth=%d perWidth=%d, totalWidth=%d\n", - curPerWidth, perWidth, totalWidth); - - if (hasAutoCol == 0) { - // Special case, cumPercent < 100% and no other columns to expand. - // We'll honor totalWidth by expanding the percentage cols. - int extraWidth = totalWidth - curPerWidth - sumMinNonPer; - for (int col = 0; col < numCols; col++) { - if (core::style::isPerLength (colPercents->get(col))) { - // This could cause rounding errors: - // - // int d = - // core::dw::multiplyWithPerLength (extraWidth, - // colPercents->get(col)) - // / cumPercent; - // - // Thus the "old" way: - int d = - (int)(extraWidth * - core::style::perLengthVal (colPercents->get(col)) - / cumPercent); - setColWidth (col, colWidths->get(col) + d); - } - } - } - } -#ifdef DBG - MSG("APP_P, result ={ "); - for (int col = 0; col < numCols; col++) - MSG("%d ", colWidths->get(col)); - MSG("}\n"); -#endif - apportion2 (totalWidth, 2); - -#ifdef DBG - MSG("APP_P, percent={"); - for (int col = 0; col < numCols; col++) - MSG("%f ", core::dw::perLengthVal (colPercents->get(col))); - MSG("}\n"); - MSG("APP_P, result ={ "); - for (int col = 0; col < numCols; col++) - MSG("%d ", colWidths->get(col)); - MSG("}\n"); -#endif + DBG_OBJ_SET_NUM ("colWidthSpecified.size", colWidthSpecified->size ()); + for (int i = 0; i < colWidthSpecified->size (); i++) + DBG_OBJ_ARRSET_BOOL ("colWidthSpecified", i, + colWidthSpecified->get(i)); + DBG_OBJ_SET_NUM ("numColWidthSpecified", numColWidthSpecified); + + DBG_OBJ_SET_NUM ("colWidthPercentage.size", colWidthPercentage->size ()); + for (int i = 0; i < colWidthPercentage->size (); i++) + DBG_OBJ_ARRSET_BOOL ("colWidthPercentage", i, + colWidthPercentage->get(i)); + DBG_OBJ_SET_NUM ("numColWidthPercentage", numColWidthPercentage); } -} -// ---------------------------------------------------------------------- + colWidthsUpToDateWidthColExtremes = false; + DBG_OBJ_SET_BOOL ("colWidthsUpToDateWidthColExtremes", + colWidthsUpToDateWidthColExtremes); -Table::TableIterator::TableIterator (Table *table, - core::Content::Type mask, bool atEnd): - core::Iterator (table, mask, atEnd) -{ - index = atEnd ? table->children->size () : -1; - content.type = atEnd ? core::Content::END : core::Content::START; + DBG_OBJ_LEAVE (); } -Table::TableIterator::TableIterator (Table *table, - core::Content::Type mask, int index): - core::Iterator (table, mask, false) +void Table::calcExtremesSpanMulteCols (int col, int cs, + core::Extremes *cellExtremes, + ExtrMod minExtrMod, ExtrMod maxExtrMod, + void *extrData) { - this->index = index; + DBG_OBJ_ENTER ("resize", 0, "calcExtremesSpanMulteCols", + "%d, %d, ..., %s, %s, ...", + col, cs, getExtrModName (minExtrMod), + getExtrModName (maxExtrMod)); - if (index < 0) - content.type = core::Content::START; - else if (index >= table->children->size ()) - content.type = core::Content::END; - else { - content.type = core::Content::WIDGET; - content.widget = table->children->get(index)->cell.widget; - } -} + int cellMin = getExtreme (cellExtremes, minExtrMod); + int cellMax = getExtreme (cellExtremes, maxExtrMod); -object::Object *Table::TableIterator::clone() -{ - return new TableIterator ((Table*)getWidget(), getMask(), index); -} + int minSumCols = 0, maxSumCols = 0; -int Table::TableIterator::compareTo(object::Comparable *other) -{ - return index - ((TableIterator*)other)->index; -} - -bool Table::TableIterator::next () -{ - Table *table = (Table*)getWidget(); - - if (content.type == core::Content::END) - return false; - - // tables only contain widgets: - if ((getMask() & core::Content::WIDGET) == 0) { - content.type = core::Content::END; - return false; + for (int j = 0; j < cs; j++) { + minSumCols += getColExtreme (col + j, minExtrMod, extrData); + maxSumCols += getColExtreme (col + j, maxExtrMod, extrData); } - do { - index++; - if (index >= table->children->size ()) { - content.type = core::Content::END; - return false; + DBG_OBJ_MSGF ("resize", 1, "cs = %d, cell: %d / %d, sum: %d / %d\n", + cs, cellMin, cellMax, minSumCols, maxSumCols); + + bool changeMin = cellMin > minSumCols; + bool changeMax = cellMax > maxSumCols; + if (changeMin || changeMax) { + // TODO This differs from the documentation? Should work, anyway. + misc::SimpleVector<int> newMin, newMax; + if (changeMin) + apportion2 (cellMin, col, col + cs - 1, MIN, MAX, NULL, &newMin, 0); + if (changeMax) + apportion2 (cellMax, col, col + cs - 1, MIN, MAX, NULL, &newMax, 0); + + for (int j = 0; j < cs; j++) { + if (changeMin) + setColExtreme (col + j, minExtrMod, extrData, newMin.get (j)); + if (changeMax) + setColExtreme (col + j, maxExtrMod, extrData, newMax.get (j)); + + // For cases where min and max are somewhat confused: + setColExtreme (col + j, maxExtrMod, extrData, + misc::max (getColExtreme (col + j, minExtrMod, + extrData), + getColExtreme (col + j, maxExtrMod, + extrData))); } - } while (table->children->get(index) == NULL || - table->children->get(index)->type != Child::CELL); + } - content.type = core::Content::WIDGET; - content.widget = table->children->get(index)->cell.widget; - return true; + DBG_OBJ_LEAVE (); } -bool Table::TableIterator::prev () +/** + * \brief Actual apportionment function. + */ +void Table::apportion2 (int totalWidth, int firstCol, int lastCol, + ExtrMod minExtrMod, ExtrMod maxExtrMod, void *extrData, + misc::SimpleVector<int> *dest, int destOffset) { - Table *table = (Table*)getWidget(); + DBG_OBJ_ENTER ("resize", 0, "apportion2", "%d, %d, %d, %s, %s, ..., %d", + totalWidth, firstCol, lastCol, getExtrModName (minExtrMod), + getExtrModName (maxExtrMod), destOffset); - if (content.type == core::Content::START) - return false; + if (lastCol >= firstCol) { + dest->setSize (destOffset + lastCol - firstCol + 1, 0); - // tables only contain widgets: - if ((getMask() & core::Content::WIDGET) == 0) { - content.type = core::Content::START; - return false; - } - - do { - index--; - if (index < 0) { - content.type = core::Content::START; - return false; + int totalMin = 0, totalMax = 0; + for (int col = firstCol; col <= lastCol; col++) { + totalMin += getColExtreme (col, minExtrMod, extrData); + totalMax += getColExtreme (col, maxExtrMod, extrData); } - } while (table->children->get(index) == NULL || - table->children->get(index)->type != Child::CELL); - - content.type = core::Content::WIDGET; - content.widget = table->children->get(index)->cell.widget; - return true; -} - -void Table::TableIterator::highlight (int start, int end, - core::HighlightLayer layer) -{ - /** todo Needs this an implementation? */ -} -void Table::TableIterator::unhighlight (int direction, - core::HighlightLayer layer) -{ -} + DBG_OBJ_MSGF ("resize", 1, + "totalWidth = %d, totalMin = %d, totalMax = %d", + totalWidth, totalMin, totalMax); + + // The actual calculation is rather simple, the ith value is: + // + // + // (max[i] - min[i]) * (totalMax - totalMin) + // width[i] = min[i] + ----------------------------------------- + // (totalWidth - totalMin) + // + // (Regard "total" as "sum".) With the following general + // definitions (for both the list and sums): + // + // diffExtr = max - min + // diffWidth = width - min + // + // it is simplified to: + // + // diffExtr[i] * totalDiffWidth + // diffWidth[i] = ---------------------------- + // totalDiffExtr + // + // Of course, if totalDiffExtr is 0, this is not defined; + // instead, we apportion according to the minima: + // + // min[i] * totalWidth + // width[i] = ------------------- + // totalMin + // + // Since min[i] <= max[i] for all i, totalMin == totalMax + // implies that min[i] == max[i] for all i. + // + // Third, it totalMin == 0 (which also implies min[i] = max[i] = 0), + // the result is + // + // width[i] = totalWidth / n + + int totalDiffExtr = totalMax - totalMin; + if (totalDiffExtr != 0) { + // Normal case. The algorithm described in + // "rounding-errors.doc" is used, with: + // + // x[i] = diffExtr[i] + // y[i] = diffWidth[i] + // a = totalDiffWidth + // b = totalDiffExtr + + DBG_OBJ_MSG ("resize", 1, "normal case"); + + int totalDiffWidth = totalWidth - totalMin; + int cumDiffExtr = 0, cumDiffWidth = 0; + + for (int col = firstCol; col <= lastCol; col++) { + int min = getColExtreme (col, minExtrMod, extrData); + int max = getColExtreme (col, maxExtrMod, extrData); + int diffExtr = max - min; + + cumDiffExtr += diffExtr; + int diffWidth = + (cumDiffExtr * totalDiffWidth) / totalDiffExtr - cumDiffWidth; + cumDiffWidth += diffWidth; + + dest->set (destOffset - firstCol + col, diffWidth + min); + } + } else if (totalMin != 0) { + // Special case. Again, same algorithm, with + // + // x[i] = min[i] + // y[i] = width[i] + // a = totalWidth + // b = totalMin + + DBG_OBJ_MSG ("resize", 1, "special case 1"); + + int cumMin = 0, cumWidth = 0; + for (int col = firstCol; col <= lastCol; col++) { + int min = getColExtreme (col, minExtrMod, extrData); + cumMin += min; + int width = (cumMin * totalWidth) / totalMin - cumWidth; + cumWidth += width; + + dest->set (destOffset - firstCol + col, width); + } + } else { // if (totalMin == 0) + // Last special case. Ssame algorithm, with + // + // x[i] = 1 (so cumX = i = col - firstCol + 1) + // y[i] = width[i] + // a = totalWidth + // b = n = lastCol - firstCol + 1 + + DBG_OBJ_MSG ("resize", 1, "special case 2"); + + int cumWidth = 0, n = (lastCol - firstCol + 1); + for (int col = firstCol; col <= lastCol; col++) { + int i = (col - firstCol + 1); + int width = (i * totalWidth) / n - cumWidth; + cumWidth += width; + + dest->set (destOffset - firstCol + col, width); + } + } + } -void Table::TableIterator::getAllocation (int start, int end, - core::Allocation *allocation) -{ - /** \bug Not implemented. */ + DBG_OBJ_LEAVE (); } } // namespace dw diff --git a/dw/table.hh b/dw/table.hh index 1d14ec07..1e7c5268 100644 --- a/dw/table.hh +++ b/dw/table.hh @@ -2,7 +2,7 @@ #define __DW_TABLE_HH__ #include "core.hh" -#include "tablecell.hh" +#include "alignedtablecell.hh" #include "../lout/misc.hh" namespace dw { @@ -10,6 +10,11 @@ namespace dw { /** * \brief A Widget for rendering tables. * + * <div style="border: 2px solid #ff0000; margin-top: 0.5em; + * margin-bottom: 0.5em; padding: 0.5em 1em; + * background-color: #ffefe0"><b>Warning:</b> Some parts of this + * description are outdated since \ref dw-grows.</div> + * * <h3>Introduction</h3> * * The dw::Table widget is used to render HTML tables. @@ -71,7 +76,7 @@ namespace dw { * is the case. * * [C] Whether this function is called, depends on NEEDS_RESIZE / - * EXTREMES_CHANGED. + * RESIZE_QUEUED / EXTREMES_CHANGED / EXTREMES_QUEUED. * * * <h4>Apportionment</h4> @@ -191,8 +196,9 @@ namespace dw { * * <ul> * <li> the specified absolute width of the table, when given, or - * <li> the available width (set by dw::Table::setWidth) times the specified - * percentage width of t(at max 100%), if the latter is given, or + * <li> the available width (set by dw::Table::setWidth [TODO outdated]) times + * the specified percentage width of t(at max 100%), if the latter is + * given, or * <li> otherwise the available width. * </ul> * @@ -316,7 +322,6 @@ namespace dw { class Table: public core::Widget { private: - struct Child { enum { @@ -355,8 +360,9 @@ private: friend class TableIterator; + static bool adjustTableMinWidth; + bool limitTextWidth, rowClosed; - int availWidth, availAscent, availDescent; // set by set... int numRows, numCols, curRow, curCol; lout::misc::SimpleVector<Child*> *children; @@ -369,6 +375,28 @@ private: lout::misc::SimpleVector<core::Extremes> *colExtremes; /** + * \brief Wether the column itself (in the future?) or at least one + * cell in this column or spanning over this column has CSS + * 'width' specified. + * + * Filled by forceCalcColumnExtremes(), since it is needed to + * calculate the column widths. + */ + lout::misc::SimpleVector<bool> *colWidthSpecified; + int numColWidthSpecified; + + /** + * \brief Wether the column itself (in the future?) or at least one + * cell in this column or spanning over this column has CSS + * 'width' specified *as percentage value*. + * + * Filled by forceCalcColumnExtremes(), since it is needed to + * calculate the column widths. + */ + lout::misc::SimpleVector<bool> *colWidthPercentage; + int numColWidthPercentage; + + /** * \brief The widths of all columns. */ lout::misc::SimpleVector<int> *colWidths; @@ -383,19 +411,19 @@ private: * If a Cell has rowspan > 1, it goes into this array */ lout::misc::SimpleVector<int> *rowSpanCells; - /** - * If a Cell has colspan > 1, it goes into this array - */ - lout::misc::SimpleVector<int> *colSpanCells; lout::misc::SimpleVector<int> *baseline; lout::misc::SimpleVector<core::style::Style*> *rowStyle; - /** - * hasColPercent becomes true when any cell specifies a percentage width. - */ - int hasColPercent; - lout::misc::SimpleVector<core::style::Length> *colPercents; + bool colWidthsUpToDateWidthColExtremes; + + enum ExtrMod { MIN, MIN_INTR, MIN_MIN, MAX_MIN, MAX, MAX_INTR, DATA }; + + const char *getExtrModName (ExtrMod mod); + int getExtreme (core::Extremes *extremes, ExtrMod mod); + void setExtreme (core::Extremes *extremes, ExtrMod mod, int value); + int getColExtreme (int col, ExtrMod mod, void *data); + inline void setColExtreme (int col, ExtrMod mod, void *data, int value); inline bool childDefined(int n) { @@ -403,17 +431,24 @@ private: children->get(n)->type != Child::SPAN_SPACE; } + int calcAvailWidthForDescendant (Widget *child); + void reallocChildren (int newNumCols, int newNumRows); - void calcCellSizes (); - void forceCalcCellSizes (); + void calcCellSizes (bool calcHeights); + void forceCalcCellSizes (bool calcHeights); void apportionRowSpan (); void calcColumnExtremes (); void forceCalcColumnExtremes (); + void calcExtremesSpanMulteCols (int col, int cs, + core::Extremes *cellExtremes, + ExtrMod minExtrMod, ExtrMod maxExtrMod, + void *extrData); - void apportion2 (int totalWidth, int forceTotalWidth); - void apportion_percentages2 (int totalWidth, int forceTotalWidth); + void apportion2 (int totalWidth, int firstCol, int lastCol, + ExtrMod minExtrMod, ExtrMod maxExtrMod, void *extrData, + lout::misc::SimpleVector<int> *dest, int destOffset); void setCumHeight (int row, int value) { @@ -423,23 +458,22 @@ private: } } - inline void setColWidth (int col, int value) - { - if (value != colWidths->get (col)) { - redrawX = lout::misc::min (redrawX, value); - colWidths->set (col, value); - } - } - protected: void sizeRequestImpl (core::Requisition *requisition); void getExtremesImpl (core::Extremes *extremes); void sizeAllocateImpl (core::Allocation *allocation); void resizeDrawImpl (); - void setWidth (int width); - void setAscent (int ascent); - void setDescent (int descent); + bool getAdjustMinWidth () { return Table::adjustTableMinWidth; } + + int getAvailWidthOfChild (Widget *child, bool forceValue); + + void containerSizeChangedForChildren (); + bool affectsSizeChangeContainerChild (Widget *child); + bool usesAvailWidth (); + + bool isBlockLevel (); + void draw (core::View *view, core::Rectangle *area); //bool buttonPressImpl (core::EventButton *event); @@ -451,14 +485,23 @@ protected: public: static int CLASS_ID; + inline static void setAdjustTableMinWidth (bool adjustTableMinWidth) + { Table::adjustTableMinWidth = adjustTableMinWidth; } + + inline static bool getAdjustTableMinWidth () + { return Table::adjustTableMinWidth; } + Table(bool limitTextWidth); ~Table(); + int applyPerWidth (int containerWidth, core::style::Length perWidth); + int applyPerHeight (int containerHeight, core::style::Length perHeight); + core::Iterator *iterator (core::Content::Type mask, bool atEnd); void addCell (Widget *widget, int colspan, int rowspan); void addRow (core::style::Style *style); - TableCell *getCellRef (); + AlignedTableCell *getCellRef (); }; } // namespace dw diff --git a/dw/table_iterator.cc b/dw/table_iterator.cc new file mode 100644 index 00000000..4da0ef4f --- /dev/null +++ b/dw/table_iterator.cc @@ -0,0 +1,134 @@ +/* + * Dillo Widget + * + * Copyright 2005-2007, 2014 Sebastian Geerken <sgeerken@dillo.org> + * + * (This file was originally part of textblock.cc.) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "table.hh" + +using namespace lout; + +namespace dw { + +Table::TableIterator::TableIterator (Table *table, + core::Content::Type mask, bool atEnd): + core::Iterator (table, mask, atEnd) +{ + index = atEnd ? table->children->size () : -1; + content.type = atEnd ? core::Content::END : core::Content::START; +} + +Table::TableIterator::TableIterator (Table *table, + core::Content::Type mask, int index): + core::Iterator (table, mask, false) +{ + this->index = index; + + if (index < 0) + content.type = core::Content::START; + else if (index >= table->children->size ()) + content.type = core::Content::END; + else { + content.type = core::Content::WIDGET_IN_FLOW; + content.widget = table->children->get(index)->cell.widget; + } +} + +object::Object *Table::TableIterator::clone() +{ + return new TableIterator ((Table*)getWidget(), getMask(), index); +} + +int Table::TableIterator::compareTo(object::Comparable *other) +{ + return index - ((TableIterator*)other)->index; +} + +bool Table::TableIterator::next () +{ + Table *table = (Table*)getWidget(); + + if (content.type == core::Content::END) + return false; + + // tables only contain widgets (in flow): + if ((getMask() & core::Content::WIDGET_IN_FLOW) == 0) { + content.type = core::Content::END; + return false; + } + + do { + index++; + if (index >= table->children->size ()) { + content.type = core::Content::END; + return false; + } + } while (table->children->get(index) == NULL || + table->children->get(index)->type != Child::CELL); + + content.type = core::Content::WIDGET_IN_FLOW; + content.widget = table->children->get(index)->cell.widget; + return true; +} + +bool Table::TableIterator::prev () +{ + Table *table = (Table*)getWidget(); + + if (content.type == core::Content::START) + return false; + + // tables only contain widgets (in flow): + if ((getMask() & core::Content::WIDGET_IN_FLOW) == 0) { + content.type = core::Content::START; + return false; + } + + do { + index--; + if (index < 0) { + content.type = core::Content::START; + return false; + } + } while (table->children->get(index) == NULL || + table->children->get(index)->type != Child::CELL); + + content.type = core::Content::WIDGET_IN_FLOW; + content.widget = table->children->get(index)->cell.widget; + return true; +} + +void Table::TableIterator::highlight (int start, int end, + core::HighlightLayer layer) +{ + /** todo Needs this an implementation? */ +} + +void Table::TableIterator::unhighlight (int direction, + core::HighlightLayer layer) +{ +} + +void Table::TableIterator::getAllocation (int start, int end, + core::Allocation *allocation) +{ + /** \bug Not implemented. */ +} + +} // namespace dw diff --git a/dw/tablecell.cc b/dw/tablecell.cc index 8612f620..5c34c781 100644 --- a/dw/tablecell.cc +++ b/dw/tablecell.cc @@ -1,7 +1,7 @@ /* * Dillo Widget * - * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org> + * Copyright 2014 Sebastian Geerken <sgeerken@dillo.org> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,97 +17,104 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - - #include "tablecell.hh" -#include "../lout/debug.hh" -#include <stdio.h> +#include "table.hh" namespace dw { -int TableCell::CLASS_ID = -1; +/** + * \brief Provided some common implementations of virtual widget + * methods. + * + * Once I understand how diamond inheritance works, a class TableCell + * will be provided, from which SimpleTableCell and AlignedTableCell + * will inherit, additionaly (in a multiple way). + */ +namespace tablecell { -TableCell::TableCell (TableCell *ref, bool limitTextWidth): - AlignedTextblock (limitTextWidth) +bool getAdjustMinWidth () { - DBG_OBJ_CREATE ("dw::TableCell"); - registerName ("dw::TableCell", &CLASS_ID); - - /** \bug ignoreLine1OffsetSometimes does not work? */ - //ignoreLine1OffsetSometimes = true; - charWordIndex = -1; - setRefTextblock (ref); - setButtonSensitive(true); + return Table::getAdjustTableMinWidth (); } -TableCell::~TableCell() +bool isBlockLevel () { - DBG_OBJ_DELETE (); + return false; } -bool TableCell::wordWrap(int wordIndex, bool wrapAll) +int correctAvailWidthOfChild (core::Widget *widget, core::Widget *child, + int width, bool forceValue) { - Textblock::Word *word; - const char *p; - - bool ret = Textblock::wordWrap (wordIndex, wrapAll); - - if (charWordIndex == -1) { - word = words->getRef (wordIndex); - if (word->content.type == core::Content::TEXT) { - if ((p = strchr (word->content.text, - word->style->textAlignChar))) { - charWordIndex = wordIndex; - charWordPos = p - word->content.text + 1; - } else if (word->style->textAlignChar == ' ' && - word->content.space) { - charWordIndex = wordIndex + 1; - charWordPos = 0; - } - } + DBG_OBJ_ENTER_O ("resize", 0, widget, "tablecell/correctAvailWidthOfChild", + "%p, %d, %s", child, width, forceValue ? "true" : "false"); + + // Make sure that this width does not exceed the width of the table + // cell (minus margin/border/padding). + + if (width != -1) { + int thisWidth = widget->getAvailWidth (forceValue); + DBG_OBJ_MSGF_O ("resize", 1, widget, "thisWidth = %d", thisWidth); + if (thisWidth != -1) + width = + lout::misc::max (lout::misc::min (width, + thisWidth + - widget->boxDiffWidth ()), + 0); } - if (wordIndex == charWordIndex) - updateValue (); + DBG_OBJ_MSGF_O ("resize", 1, widget, "=> %d", width); + DBG_OBJ_LEAVE_O (widget); + return width; +} - return ret; +int correctAvailHeightOfChild (core::Widget *widget, core::Widget *child, + int height, bool forceValue) +{ + // Something to do? + return height; } -int TableCell::getValue () +void correctCorrectedRequisitionOfChild (core::Widget *widget, + core::Widget *child, + core::Requisition *requisition, + void (*splitHeightFun) (int, int*, + int*)) { - Textblock::Word *word; - int i, wordIndex; - int w; - - if (charWordIndex == -1) - wordIndex = words->size () -1; - else - wordIndex = charWordIndex; - - w = 0; - for (i = 0; i < wordIndex; i++) { - word = words->getRef (i); - w += word->size.width + word->origSpace; - } + DBG_OBJ_ENTER_O ("resize", 0, widget, "tablecell/correctRequisitionOfChild", + "%p, %d * (%d + %d), ...", child, requisition->width, + requisition->ascent, requisition->descent); - if (charWordIndex == -1) { - if (words->size () > 0) { - word = words->getRef (words->size () - 1); - w += word->size.width; - } - } else { - word = words->getRef (charWordIndex); - w += layout->textWidth (word->style->font, word->content.text, - charWordPos); - } + // Make sure that this width does not exceed the width of the table + // cell (minus margin/border/padding). + + int thisWidth = widget->getAvailWidth (true); + DBG_OBJ_MSGF_O ("resize", 1, widget, "thisWidth = %d", thisWidth); + requisition->width = + lout::misc::max (lout::misc::min (requisition->width, + thisWidth - widget->boxDiffWidth ()), + 0); - return w; + DBG_OBJ_LEAVE_O (widget); } -void TableCell::setMaxValue (int maxValue, int value) +void correctCorrectedExtremesOfChild (core::Widget *widget, core::Widget *child, + core::Extremes *extremes) { - line1Offset = maxValue - value; - queueResize (0, true); + // Something to do? } +int applyPerWidth (core::Widget *widget, int containerWidth, + core::style::Length perWidth) +{ + return core::style::multiplyWithPerLength (containerWidth, perWidth); +} + +int applyPerHeight (core::Widget *widget, int containerHeight, + core::style::Length perHeight) +{ + return core::style::multiplyWithPerLength (containerHeight, perHeight); +} + +} // namespace dw + } // namespace dw diff --git a/dw/tablecell.hh b/dw/tablecell.hh index f7c6042e..2e26c8e8 100644 --- a/dw/tablecell.hh +++ b/dw/tablecell.hh @@ -2,27 +2,33 @@ #define __DW_TABLECELL_HH__ #include "core.hh" -#include "alignedtextblock.hh" namespace dw { -class TableCell: public AlignedTextblock -{ -private: - int charWordIndex, charWordPos; +namespace tablecell { -protected: - bool wordWrap (int wordIndex, bool wrapAll); +bool getAdjustMinWidth (); +bool isBlockLevel (); - int getValue (); - void setMaxValue (int maxValue, int value); +int correctAvailWidthOfChild (core::Widget *widget, core::Widget *child, + int width, bool forceValue); +int correctAvailHeightOfChild (core::Widget *widget, core::Widget *child, + int height, bool forceValue); -public: - static int CLASS_ID; +void correctCorrectedRequisitionOfChild (core::Widget *widget, + core::Widget *child, + core::Requisition *requisition, + void (*splitHeightFun) (int, int*, + int*)); +void correctCorrectedExtremesOfChild (core::Widget *widget, core::Widget *child, + core::Extremes *extremes); - TableCell(TableCell *ref, bool limitTextWidth); - ~TableCell(); -}; +int applyPerWidth (core::Widget *widget, int containerWidth, + core::style::Length perWidth); +int applyPerHeight (core::Widget *widget, int containerHeight, + core::style::Length perHeight); + +} // namespace dw } // namespace dw diff --git a/dw/textblock.cc b/dw/textblock.cc index 2c4ca20b..9b4d5380 100644 --- a/dw/textblock.cc +++ b/dw/textblock.cc @@ -1,7 +1,7 @@ /* * Dillo Widget * - * Copyright 2005-2007, 2012-2013 Sebastian Geerken <sgeerken@dillo.org> + * Copyright 2005-2007, 2012-2014 Sebastian Geerken <sgeerken@dillo.org> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,13 +25,14 @@ #include "../lout/debug.hh" #include <stdio.h> -#include <math.h> +#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; @@ -82,7 +83,7 @@ void Textblock::WordImgRenderer::getBgArea (int *x, int *y, int *width, *x = textblock->allocation.x + this->xWordWidget; *y = textblock->lineYOffsetCanvas (line); *width = textblock->words->getRef(wordNo)->size.width; - *height = line->boxAscent + line->boxDescent; + *height = line->borderAscent + line->borderDescent; } void Textblock::WordImgRenderer::getRefArea (int *xRef, int *yRef, @@ -193,7 +194,7 @@ void Textblock::setPenaltyHyphen (int penaltyHyphen) { penalties[PENALTY_HYPHEN][0] = penaltyHyphen; } - + void Textblock::setPenaltyHyphen2 (int penaltyHyphen2) { penalties[PENALTY_HYPHEN][1] = penaltyHyphen2; @@ -224,17 +225,18 @@ Textblock::Textblock (bool limitTextWidth) { DBG_OBJ_CREATE ("dw::Textblock"); registerName ("dw::Textblock", &CLASS_ID); - setFlags (BLOCK_LEVEL); - setFlags (USES_HINTS); setButtonSensitive(true); + containingBlock = NULL; hasListitemValue = false; - innerPadding = 0; + leftInnerPadding = 0; line1Offset = 0; ignoreLine1OffsetSometimes = false; mustQueueResize = false; redrawY = 0; + DBG_OBJ_SET_NUM ("redrawY", redrawY); lastWordDrawn = -1; + DBG_OBJ_SET_NUM ("lastWordDrawn", lastWordDrawn); /* * The initial sizes of lines and words should not be @@ -250,22 +252,23 @@ Textblock::Textblock (bool limitTextWidth) nonTemporaryLines = 0; words = new misc::NotSoSimpleVector <Word> (1); anchors = new misc::SimpleVector <Anchor> (1); - - //DBG_OBJ_SET_NUM(this, "num_lines", num_lines); + outOfFlowMgr = NULL; wrapRefLines = wrapRefParagraphs = -1; - //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); + DBG_OBJ_SET_NUM ("lines.size", lines->size ()); + DBG_OBJ_SET_NUM ("words.size", words->size ()); + DBG_OBJ_SET_NUM ("wrapRefLines", wrapRefLines); + DBG_OBJ_SET_NUM ("wrapRefParagraphs", wrapRefParagraphs); hoverLink = -1; - // random values - availWidth = 100; - availAscent = 100; - availDescent = 0; + // -1 means undefined. + lineBreakWidth = -1; + DBG_OBJ_SET_NUM ("lineBreakWidth", lineBreakWidth); + + verticalOffset = 0; + DBG_OBJ_SET_NUM ("verticalOffset", verticalOffset); this->limitTextWidth = limitTextWidth; @@ -276,6 +279,8 @@ Textblock::Textblock (bool limitTextWidth) hlEnd[layer].index = 0; hlEnd[layer].nChar = 0; } + + initNewLine (); } Textblock::~Textblock () @@ -285,18 +290,8 @@ Textblock::~Textblock () /* make sure not to call a free'd tooltip (very fast overkill) */ hoverTooltip = NULL; - for (int i = 0; i < words->size(); i++) { - Word *word = words->getRef (i); - - if (word->content.type == core::Content::WIDGET) - delete word->content.widget; - - removeWordImgRenderer (i); - removeSpaceImgRenderer (i); - - word->style->unref (); - word->spaceStyle->unref (); - } + for (int i = 0; i < words->size(); i++) + cleanupWord (i); for (int i = 0; i < anchors->size(); i++) { Anchor *anchor = anchors->getRef (i); @@ -309,6 +304,16 @@ Textblock::~Textblock () delete words; delete anchors; + if(outOfFlowMgr) { + // I feel more comfortable by letting the textblock delete these + // widgets, instead of doing this in ~OutOfFlowMgr. + + for (int i = 0; i < outOfFlowMgr->getNumWidgets (); i++) + delete outOfFlowMgr->getWidget (i); + + delete outOfFlowMgr; + } + /* Make sure we don't own widgets anymore. Necessary before call of parent class destructor. (???) */ words = NULL; @@ -323,51 +328,132 @@ Textblock::~Textblock () */ void Textblock::sizeRequestImpl (core::Requisition *requisition) { - PRINTF ("[%p] SIZE_REQUEST: ...\n", this); + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequestImpl"); + + int newLineBreakWidth = getAvailWidth (true); + if (newLineBreakWidth != lineBreakWidth) { + lineBreakWidth = newLineBreakWidth; + wrapRefLines = 0; + DBG_OBJ_SET_NUM ("lineBreakWidth", lineBreakWidth); + DBG_OBJ_SET_NUM ("wrapRefLines", wrapRefLines); + } rewrap (); showMissingLines (); if (lines->size () > 0) { - Line *lastLine = lines->getRef (lines->size () - 1); - 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; - requisition->descent = lastLine->top - + lastLine->boxAscent + lastLine->boxDescent - - lines->getRef(0)->boxAscent; + Line *firstLine = lines->getRef(0), *lastLine = lines->getLastRef (); + + // Note: the breakSpace of the last line is ignored, so breaks + // at the end of a textblock are not visible. + + requisition->width = lastLine->maxLineWidth + leftInnerPadding + + getStyle()->boxDiffWidth (); + + // Also regard collapsing of this widget top margin and the top + // margin of the first line box: + requisition->ascent = calcVerticalBorder (getStyle()->padding.top, + getStyle()->borderWidth.top, + getStyle()->margin.top, + firstLine->borderAscent, + firstLine->marginAscent); + + // And here, regard collapsing of this widget bottom margin and the + // bottom margin of the last line box: + requisition->descent = + // (BTW, this line: + lastLine->top - firstLine->borderAscent + lastLine->borderAscent + + // ... is 0 for a block with one line, so special handling + // for this case is not necessary.) + calcVerticalBorder (getStyle()->padding.bottom, + getStyle()->borderWidth.bottom, + getStyle()->margin.bottom, + lastLine->borderDescent, lastLine->marginDescent); } else { - requisition->width = 0; // before: lastLineWidth; - requisition->ascent = 0; - requisition->descent = 0; + requisition->width = leftInnerPadding + getStyle()->boxDiffWidth (); + requisition->ascent = getStyle()->boxOffsetY (); + requisition->descent = getStyle()->boxRestHeight ();; } - PRINTF ("[%p] SIZE_REQUEST: inner padding = %d, boxDiffWidth = %d\n", - this, innerPadding, getStyle()->boxDiffWidth ()); + requisition->ascent += verticalOffset; + + if (mustBeWidenedToAvailWidth ()) { + DBG_OBJ_MSGF ("resize", 1, + "before considering lineBreakWidth (= %d): %d * (%d + %d)", + lineBreakWidth, requisition->width, requisition->ascent, + requisition->descent); + if (requisition->width < lineBreakWidth) + requisition->width = lineBreakWidth; + } else + DBG_OBJ_MSG ("resize", 1, "lineBreakWidth needs no consideration"); - requisition->width += innerPadding + getStyle()->boxDiffWidth (); - requisition->ascent += getStyle()->boxOffsetY (); - requisition->descent += getStyle()->boxRestHeight (); + DBG_OBJ_MSGF ("resize", 1, "before correction: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); - if (requisition->width < availWidth) - requisition->width = availWidth; + correctRequisition (requisition, core::splitHeightPreserveAscent); - PRINTF ("[%p] SIZE_REQUEST: %d x %d + %d\n", this, requisition->width, - requisition->ascent, requisition->descent); + // Dealing with parts out of flow, which may overlap the borders of + // the text block. Base lines are ignored here: they do not play a + // role (currently) and caring about them (for the future) would + // cause too much problems. + + // Notice that the order is not typical: correctRequisition should + // be the last call. However, calling correctRequisition after + // outOfFlowMgr->getSize may result again in a size which is too + // small for floats, so triggering again (and again) the resize + // idle function resulting in CPU hogging. See also + // getExtremesImpl. + // + // Is this really what we want? An alternative could be that + // OutOfFlowMgr::getSize honours CSS attributes an corrected sizes. + + DBG_OBJ_MSGF ("resize", 1, "before considering OOF widgets: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + + if (outOfFlowMgr) { + int oofWidth, oofHeight; + outOfFlowMgr->getSize (requisition, &oofWidth, &oofHeight); + + // Floats must be within the *content* area, not the *margin* + // area (which is equivalent to the requisition / + // allocation). For this reason, boxRestWidth() and + // boxRestHeight() must be considered. + + if (oofWidth + boxRestWidth () > requisition->width) + requisition->width = oofWidth + boxRestWidth (); + if (oofHeight + boxRestHeight () + > requisition->ascent + requisition->descent) + requisition->descent = + oofHeight + boxRestHeight () - requisition->ascent; + } + + DBG_OBJ_MSGF ("resize", 1, "final: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_LEAVE (); +} + +int Textblock::calcVerticalBorder (int widgetPadding, int widgetBorder, + int widgetMargin, int lineBorderTotal, + int lineMarginTotal) +{ + DBG_OBJ_ENTER ("resize", 0, "calcVerticalBorder", "%d, %d, %d, %d, %d", + widgetPadding, widgetBorder, widgetMargin, lineBorderTotal, + lineMarginTotal); + + int result; + + if (widgetPadding == 0 && widgetBorder == 0) { + if (lineMarginTotal - lineBorderTotal >= widgetMargin) + result = lineMarginTotal; + else + result = widgetMargin + lineBorderTotal; + } else + result = lineMarginTotal + widgetPadding + widgetBorder + widgetMargin; + + DBG_OBJ_MSGF ("resize", 0, "=> %d", result); + DBG_OBJ_LEAVE (); + + return result; } /** @@ -375,67 +461,156 @@ void Textblock::sizeRequestImpl (core::Requisition *requisition) */ void Textblock::getWordExtremes (Word *word, core::Extremes *extremes) { - if (word->content.type == core::Content::WIDGET) { - if (word->content.widget->usesHints ()) - word->content.widget->getExtremes (extremes); - else { - if (core::style::isPerLength - (word->content.widget->getStyle()->width)) { - extremes->minWidth = 0; - if (word->content.widget->hasContents ()) - extremes->maxWidth = 1000000; - else - extremes->maxWidth = 0; - } else if (core::style::isAbsLength - (word->content.widget->getStyle()->width)) { - /* Fixed lengths are only applied to the content, so we have to - * add padding, border and margin. */ - extremes->minWidth = extremes->maxWidth = - core::style::absLengthVal (word->content.widget->getStyle() - ->width) - + word->style->boxDiffWidth (); - } else - word->content.widget->getExtremes (extremes); - } - } else { - extremes->minWidth = word->size.width; - extremes->maxWidth = word->size.width; - } + if (word->content.type == core::Content::WIDGET_IN_FLOW) + word->content.widget->getExtremes (extremes); + else + extremes->minWidth = extremes->minWidthIntrinsic = extremes->maxWidth = + extremes->maxWidthIntrinsic = word->size.width; } void Textblock::getExtremesImpl (core::Extremes *extremes) { - PRINTF ("[%p] GET_EXTREMES ...\n", this); + DBG_OBJ_ENTER0 ("resize", 0, "getExtremesImpl"); + + // TODO Can extremes depend on the available width? Should not; if + // they do, the following code must be reactivated, but it causes + // an endless recursion. +#if 0 + int newLineBreakWidth = getAvailWidth (true); + if (newLineBreakWidth != lineBreakWidth) { + lineBreakWidth = newLineBreakWidth; + wrapRefParagraphs = 0; + DBG_OBJ_SET_NUM ("lineBreakWidth", lineBreakWidth); + DBG_OBJ_SET_NUM ("wrapRefParagraphs", wrapRefLines); + } +#endif fillParagraphs (); if (paragraphs->size () == 0) { /* empty page */ extremes->minWidth = 0; + extremes->minWidthIntrinsic = 0; extremes->maxWidth = 0; + extremes->maxWidthIntrinsic = 0; } else { Paragraph *lastPar = paragraphs->getLastRef (); extremes->minWidth = lastPar->maxParMin; + extremes->minWidthIntrinsic = lastPar->maxParMinIntrinsic; extremes->maxWidth = lastPar->maxParMax; + extremes->maxWidthIntrinsic = lastPar->maxParMaxIntrinsic; + + DBG_OBJ_MSGF ("resize", 1, "paragraphs[%d]->maxParMin = %d (%d)", + paragraphs->size () - 1, lastPar->maxParMin, + lastPar->maxParMinIntrinsic); + DBG_OBJ_MSGF ("resize", 1, "paragraphs[%d]->maxParMax = %d (%d)", + paragraphs->size () - 1, lastPar->maxParMax, + lastPar->maxParMaxIntrinsic); } - int diff = innerPadding + getStyle()->boxDiffWidth (); + DBG_OBJ_MSGF ("resize", 0, "after considering paragraphs: %d (%d) / %d (%d)", + extremes->minWidth, extremes->minWidthIntrinsic, + extremes->maxWidth, extremes->maxWidthIntrinsic); + + int diff = leftInnerPadding + getStyle()->boxDiffWidth (); extremes->minWidth += diff; + extremes->minWidthIntrinsic += diff; extremes->maxWidth += diff; + extremes->maxWidthIntrinsic += diff; + + DBG_OBJ_MSGF ("resize", 0, "after adding diff: %d (%d) / %d (%d)", + extremes->minWidth, extremes->minWidthIntrinsic, + extremes->maxWidth, extremes->maxWidthIntrinsic); + + // For the order, see similar reasoning in sizeRequestImpl. + + correctExtremes (extremes); - PRINTF ("[%p] GET_EXTREMES => %d / %d\n", - this, extremes->minWidth, extremes->maxWidth); + DBG_OBJ_MSGF ("resize", 0, "after correction: %d (%d) / %d (%d)", + extremes->minWidth, extremes->minWidthIntrinsic, + extremes->maxWidth, extremes->maxWidthIntrinsic); + + if (outOfFlowMgr) { + int oofMinWidth, oofMaxWidth; + outOfFlowMgr->getExtremes (extremes, &oofMinWidth, &oofMaxWidth); + + DBG_OBJ_MSGF ("resize", 1, "OOFM correction: %d / %d", + oofMinWidth, oofMaxWidth); + + extremes->minWidth = misc::max (extremes->minWidth, oofMinWidth); + extremes->minWidthIntrinsic = + misc::max (extremes->minWidthIntrinsic, oofMinWidth); + extremes->maxWidth = misc::max (extremes->maxWidth, oofMaxWidth); + extremes->maxWidthIntrinsic = + misc::max (extremes->maxWidthIntrinsic, oofMinWidth); + } + + DBG_OBJ_MSGF ("resize", 0, + "finally, after considering OOFM: %d (%d) / %d (%d)", + extremes->minWidth, extremes->minWidthIntrinsic, + extremes->maxWidth, extremes->maxWidthIntrinsic); + DBG_OBJ_LEAVE (); } 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); + DBG_OBJ_ENTER ("resize", 0, "sizeAllocateImpl", "%d, %d; %d * (%d + %d)", + allocation->x, allocation->y, allocation->width, + allocation->ascent, allocation->descent); showMissingLines (); + // In some cases, this allocation results in child allocation which + // exceed the top of this allocation, which will then result in an + // endless resize idle cascade and CPU hogging (when floats come + // into play). + // + // Example: + // + // <div id="id1" style="height: 50px"> + // <div id="id2">...</div> + // <div> + // + // Assume that the inner section, div#id2, has a height of 200px = + // 100px (ascent) + 100px (descent). For the outer section, + // div#id1, this will be initially calculated for the size, but + // then (because of CSS 'height') reduced to 50px = 50px (ascent) + + // 0px (descent). Without the following correction, the inner + // section (div#id2) would be allocated at 50px top of the + // allocation of the outer section: childAllocation->y = + // allocation->y - 50. + // + // For this reason, we calculat "childBaseAllocation", which will + // avoid this case; in the example above, the height will be 50px = + // 100px (ascent) - 50px (descent: negative). + + childBaseAllocation.x = allocation->x; + childBaseAllocation.y = allocation->y; + childBaseAllocation.width = allocation->width; + childBaseAllocation.ascent = + misc::max (allocation->ascent, + // Reconstruct the initial size; see + // Textblock::sizeRequestImpl. + (lines->size () > 0 ? + calcVerticalBorder (getStyle()->padding.top, + getStyle()->borderWidth.top, + getStyle()->margin.top, + lines->getRef(0)->borderAscent, + lines->getRef(0)->marginAscent) : + getStyle()->boxOffsetY ()) + verticalOffset); + childBaseAllocation.descent = + allocation->ascent + allocation->descent - childBaseAllocation.ascent; + + DBG_OBJ_SET_NUM ("childBaseAllocation.x", childBaseAllocation.x); + DBG_OBJ_SET_NUM ("childBaseAllocation.y", childBaseAllocation.y); + DBG_OBJ_SET_NUM ("childBaseAllocation.width", childBaseAllocation.width); + DBG_OBJ_SET_NUM ("childBaseAllocation.ascent", childBaseAllocation.ascent); + DBG_OBJ_SET_NUM ("childBaseAllocation.descent", childBaseAllocation.descent); + + if (containingBlock->outOfFlowMgr) + containingBlock->outOfFlowMgr->sizeAllocateStart (this, allocation); + int lineIndex, wordIndex; Line *line; Word *word; @@ -445,11 +620,28 @@ void Textblock::sizeAllocateImpl (core::Allocation *allocation) if (allocation->width != this->allocation.width) { redrawY = 0; + DBG_OBJ_SET_NUM ("redrawY", redrawY); } + DBG_OBJ_MSG_START (); + for (lineIndex = 0; lineIndex < lines->size (); lineIndex++) { + DBG_OBJ_MSGF ("resize", 1, "line %d", lineIndex); + DBG_OBJ_MSG_START (); + + // Especially for floats, allocation->width may be different + // from the line break width, so that for centered and right + // text, the offsets have to be recalculated again. However, if + // the allocation width is greater than the line break width, + // due to wide unbreakable lines (large image etc.), use the + // original line break width. + calcTextOffset (lineIndex, + misc::min (childBaseAllocation.width, lineBreakWidth)); + line = lines->getRef (lineIndex); - xCursor = lineXOffsetWidget (line); + xCursor = line->textOffset; + + DBG_OBJ_MSGF ("resize", 1, "xCursor = %d (initially)", xCursor); for (wordIndex = line->firstWord; wordIndex <= line->lastWord; wordIndex++) { @@ -457,11 +649,21 @@ void Textblock::sizeAllocateImpl (core::Allocation *allocation) if (wordIndex == lastWordDrawn + 1) { redrawY = misc::min (redrawY, lineYOffsetWidget (line)); + DBG_OBJ_SET_NUM ("redrawY", redrawY); } - if (word->content.type == core::Content::WIDGET) { + if (word->content.type == core::Content::WIDGET_IN_FLOW) { + DBG_OBJ_MSGF ("resize", 1, + "allocating widget in flow: line %d, word %d", + lineIndex, wordIndex); + + childAllocation.x = xCursor + childBaseAllocation.x; + + DBG_OBJ_MSGF ("resize", 1, "childAllocation.x = %d + %d = %d", + xCursor, childBaseAllocation.x, childAllocation.x); + /** \todo Justification within the line is done here. */ - childAllocation.x = xCursor + allocation->x; + /* align=top: childAllocation.y = line->top + allocation->y; */ @@ -469,16 +671,19 @@ void Textblock::sizeAllocateImpl (core::Allocation *allocation) /* align=bottom (base line) */ /* Commented lines break the n2 and n3 test cases at * http://www.dillo.org/test/img/ */ - childAllocation.y = - lineYOffsetCanvasAllocation (line, allocation) - + (line->boxAscent - word->size.ascent) - - word->content.widget->getStyle()->margin.top; - childAllocation.width = word->size.width; - childAllocation.ascent = word->size.ascent - + word->content.widget->getStyle()->margin.top; - childAllocation.descent = word->size.descent - + word->content.widget->getStyle()->margin.bottom; + childAllocation.y = lineYOffsetCanvas (line) + + (line->borderAscent - word->size.ascent); + DBG_OBJ_MSGF ("resize", 1, + "childAllocation.y = %d + (%d - %d) = %d", + lineYOffsetCanvas (line), + line->borderAscent, word->size.ascent, + childAllocation.y); + + childAllocation.width = word->size.width; + childAllocation.ascent = word->size.ascent; + childAllocation.descent = word->size.descent; + oldChildAllocation = word->content.widget->getAllocation(); if (childAllocation.x != oldChildAllocation->x || @@ -488,9 +693,11 @@ void Textblock::sizeAllocateImpl (core::Allocation *allocation) * so we need to redraw from this line onwards. */ redrawY = misc::min (redrawY, lineYOffsetWidget (line)); + DBG_OBJ_SET_NUM ("redrawY", redrawY); if (word->content.widget->wasAllocated ()) { redrawY = misc::min (redrawY, oldChildAllocation->y - this->allocation.y); + DBG_OBJ_SET_NUM ("redrawY", redrawY); } } else if (childAllocation.ascent + childAllocation.descent != @@ -513,40 +720,134 @@ void Textblock::sizeAllocateImpl (core::Allocation *allocation) core::Content::BREAK)) { int childChangedY = - misc::min(childAllocation.y - allocation->y + + misc::min(childAllocation.y - childBaseAllocation.y + childAllocation.ascent + childAllocation.descent, oldChildAllocation->y - this->allocation.y + oldChildAllocation->ascent + oldChildAllocation->descent); redrawY = misc::min (redrawY, childChangedY); + DBG_OBJ_SET_NUM ("redrawY", redrawY); } else { redrawY = misc::min (redrawY, lineYOffsetWidget (line)); + DBG_OBJ_SET_NUM ("redrawY", redrawY); } } word->content.widget->sizeAllocate (&childAllocation); } xCursor += (word->size.width + word->effSpace); + DBG_OBJ_MSGF ("resize", 1, "xCursor = %d (after word %d)", + xCursor, wordIndex); + DBG_MSG_WORD("resize", 1, "<i>that is:</i> ", wordIndex, ""); } + + DBG_OBJ_MSG_END (); } + DBG_OBJ_MSG_END (); + + if (containingBlock->outOfFlowMgr) + containingBlock->outOfFlowMgr->sizeAllocateEnd (this); + for (int i = 0; i < anchors->size(); i++) { Anchor *anchor = anchors->getRef(i); int y; - if (anchor->wordIndex >= words->size()) { + if (anchor->wordIndex >= words->size() || + // Also regard not-yet-existing lines. + lines->size () <= 0 || + anchor->wordIndex > lines->getLastRef()->lastWord) { y = allocation->y + allocation->ascent + allocation->descent; } else { Line *line = lines->getRef(findLineOfWord (anchor->wordIndex)); - y = lineYOffsetCanvasAllocation (line, allocation); + y = lineYOffsetCanvas (line); } changeAnchor (anchor->name, y); } + + DBG_OBJ_LEAVE (); +} + +int Textblock::getAvailWidthOfChild (Widget *child, bool forceValue) +{ + DBG_OBJ_ENTER ("resize", 0, "Textblock/getAvailWidthOfChild", "%p, %s", + child, forceValue ? "true" : "false"); + + int width; + + if (child->getStyle()->width == core::style::LENGTH_AUTO) { + // No width specified: similar to standard implementation (see + // there), but "leftInnerPadding" has to be considered, too. + DBG_OBJ_MSG ("resize", 1, "no specification"); + if (forceValue) + width = misc::max (getAvailWidth (true) - boxDiffWidth () + - leftInnerPadding, + 0); + else + width = -1; + } else + width = Widget::getAvailWidthOfChild (child, forceValue); + + if (forceValue && this == child->getContainer () && + !mustBeWidenedToAvailWidth ()) { + core::Extremes extremes; + getExtremes (&extremes); + if (width > extremes.maxWidth - boxDiffWidth () - leftInnerPadding) + width = extremes.maxWidth - boxDiffWidth () - leftInnerPadding; + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d", width); + DBG_OBJ_LEAVE (); + return width; +} + + + +void Textblock::containerSizeChangedForChildren () +{ + DBG_OBJ_ENTER0 ("resize", 0, "containerSizeChangedForChildren"); + + for (int i = 0; i < words->size (); i++) { + Word *word = words->getRef (i); + if (word->content.type == core::Content::WIDGET_IN_FLOW) + word->content.widget->containerSizeChanged (); + } + + if (outOfFlowMgr) + outOfFlowMgr->containerSizeChangedForChildren (); + + DBG_OBJ_LEAVE (); +} + +bool Textblock::affectsSizeChangeContainerChild (Widget *child) +{ + DBG_OBJ_ENTER ("resize", 0, + "Textblock/affectsSizeChangeContainerChild", "%p", child); + + // See Textblock::getAvailWidthForChild() and Textblock::oofSizeChanged(): + // Extremes changes affect the size of the child, too: + bool ret; + if (!mustBeWidenedToAvailWidth () && + (extremesQueued () || extremesChanged ())) + ret = true; + else + ret = Widget::affectsSizeChangeContainerChild (child); + + DBG_OBJ_MSGF ("resize", 1, "=> %s", ret ? "true" : "false"); + DBG_OBJ_LEAVE (); + return ret; +} + +bool Textblock::usesAvailWidth () +{ + return true; } void Textblock::resizeDrawImpl () { + DBG_OBJ_ENTER0 ("draw", 0, "resizeDrawImpl"); + queueDrawArea (0, redrawY, allocation.width, getHeight () - redrawY); if (lines->size () > 0) { Line *lastLine = lines->getRef (lines->size () - 1); @@ -554,99 +855,141 @@ void Textblock::resizeDrawImpl () * draw any new added words (see sizeAllocateImpl()). */ lastWordDrawn = lastLine->lastWord; + DBG_OBJ_SET_NUM ("lastWordDrawn", lastWordDrawn); } redrawY = getHeight (); + DBG_OBJ_SET_NUM ("redrawY", redrawY); + + DBG_OBJ_LEAVE (); } void Textblock::markSizeChange (int ref) { - PRINTF ("[%p] MARK_SIZE_CHANGE (%d): %d => ...\n", this, ref, wrapRefLines); + DBG_OBJ_ENTER ("resize", 0, "markSizeChange", "%d", ref); - /* 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 necessary. */ - if (ref != -1) { - if (wrapRefLines == -1) - wrapRefLines = ref; - else - wrapRefLines = misc::min (wrapRefLines, ref); - } + if (OutOfFlowMgr::isRefOutOfFlow (ref)) { + assert (outOfFlowMgr != NULL); + outOfFlowMgr->markSizeChange (ref); + } else { + PRINTF ("[%p] MARK_SIZE_CHANGE (%d): %d => ...\n", + this, ref, wrapRefLines); + + /* 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 necessary. */ + if (ref != -1) { + if (wrapRefLines == -1) + wrapRefLines = OutOfFlowMgr::getLineNoFromRef (ref); + else + wrapRefLines = misc::min (wrapRefLines, + OutOfFlowMgr::getLineNoFromRef (ref)); + } - PRINTF (" ... => %d\n", wrapRefLine); + DBG_OBJ_SET_NUM ("wrapRefLines", wrapRefLines); - // It seems that sometimes the lines structure is changed, so that - // wrapRefLines may refers to a line which does not exist - // anymore. Should be examined again. Until then, setting - // wrapRefLines to the same value is a workaround. - markExtremesChange (ref); + // It seems that sometimes (even without floats) the lines + // structure is changed, so that wrapRefLines may refers to a + // line which does not exist anymore. Should be examined + // again. Until then, setting wrapRefLines to the same value is + // a workaround. + markExtremesChange (ref); + } + + DBG_OBJ_LEAVE (); } void Textblock::markExtremesChange (int ref) { - PRINTF ("[%p] MARK_EXTREMES_CHANGE (%d): %d => ...\n", - this, ref, wrapRefParagraphs); - - /* 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 necessary. */ - if (ref != -1) { - if (wrapRefParagraphs == -1) - wrapRefParagraphs = ref; - else - wrapRefParagraphs = misc::min (wrapRefParagraphs, ref); + DBG_OBJ_ENTER ("resize", 1, "markExtremesChange", "%d", ref); + + if (OutOfFlowMgr::isRefOutOfFlow (ref)) { + assert (outOfFlowMgr != NULL); + outOfFlowMgr->markExtremesChange (ref); + } else { + PRINTF ("[%p] MARK_EXTREMES_CHANGE (%d): %d => ...\n", + this, ref, wrapRefParagraphs); + + /* 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 necessary. */ + if (ref != -1) { + if (wrapRefParagraphs == -1) + wrapRefParagraphs = OutOfFlowMgr::getLineNoFromRef (ref); + else + wrapRefParagraphs = + misc::min (wrapRefParagraphs, + OutOfFlowMgr::getLineNoFromRef (ref)); + } + + DBG_OBJ_SET_NUM ("wrapRefParagraphs", wrapRefParagraphs); } - PRINTF (" ... => %d\n", wrapRefParagraphs); + DBG_OBJ_LEAVE (); } -void Textblock::setWidth (int width) +void Textblock::notifySetAsTopLevel() { - /* If limitTextWidth is set to YES, a queueResize() may also be - * necessary. */ - if (availWidth != width || limitTextWidth) { - //DEBUG_MSG(DEBUG_REWRAP_LEVEL, - // "setWidth: Calling queueResize, " - // "in page with %d word(s)\n", - // words->size()); + PRINTF ("%p becomes toplevel\n", this); + containingBlock = this; + PRINTF ("-> %p is its own containing block\n", this); +} - availWidth = width; - queueResize (0, false); - mustQueueResize = false; - redrawY = 0; - } +bool Textblock::isContainingBlock (Widget *widget) +{ + return + // Of course, only textblocks are considered as containing + // blocks. + widget->instanceOf (Textblock::CLASS_ID) && + // The second condition: that this block is "out of flow", in a + // wider sense. + (// The toplevel widget is "out of flow", since there is no + // parent, and so no context. + widget->getParent() == NULL || + // A similar reasoning applies to a widget with another parent + // than a textblock (typical example: a table cell (this is + // also a text block) within a table widget). + !widget->getParent()->instanceOf (Textblock::CLASS_ID) || + // Inline blocks are containing blocks, too. + widget->getStyle()->display == core::style::DISPLAY_INLINE_BLOCK || + // Same for blocks with 'overview' set to another value than + // (the default value) 'visible'. + widget->getStyle()->overflow != core::style::OVERFLOW_VISIBLE || + // Finally, "out of flow" in a narrower sense: floats and + // absolute positions. + OutOfFlowMgr::isWidgetOutOfFlow (widget)); } -void Textblock::setAscent (int ascent) +void Textblock::notifySetParent () { - if (availAscent != ascent) { - //DEBUG_MSG(DEBUG_REWRAP_LEVEL, - // "setAscent: Calling queueResize, " - // "in page with %d word(s)\n", - // words->size()); + PRINTF ("%p becomes a child of %p\n", this, getParent()); - availAscent = ascent; - queueResize (0, false); - mustQueueResize = false; - } + // Search for containing Box. + containingBlock = NULL; + + for (Widget *widget = this; widget != NULL && containingBlock == NULL; + widget = widget->getParent()) + if (isContainingBlock (widget)) { + containingBlock = (Textblock*)widget; + + if (containingBlock == this) { + PRINTF ("-> %p is its own containing block\n", this); + } else { + PRINTF ("-> %p becomes containing block of %p\n", + containingBlock, this); + } + } + + assert (containingBlock != NULL); } -void Textblock::setDescent (int descent) +bool Textblock::isBlockLevel () { - if (availDescent != descent) { - //DEBUG_MSG(DEBUG_REWRAP_LEVEL, - // "setDescent: Calling queueResize, " - // "in page with %d word(s)\n", - // words->size()); - - availDescent = descent; - queueResize (0, false); - mustQueueResize = false; - } + return true; } bool Textblock::buttonPressImpl (core::EventButton *event) @@ -743,8 +1086,8 @@ bool Textblock::sendSelectionEvent (core::SelectionState::EventType eventType, } else { Line *lastLine = lines->getRef (lines->size () - 1); int yFirst = lineYOffsetCanvasI (0); - int yLast = lineYOffsetCanvas (lastLine) + lastLine->boxAscent + - lastLine->boxDescent; + int yLast = lineYOffsetCanvas (lastLine) + lastLine->borderAscent + + lastLine->borderDescent; if (event->yCanvas < yFirst) { // Above the first line: take the first word. wordIndex = 0; @@ -753,19 +1096,20 @@ bool Textblock::sendSelectionEvent (core::SelectionState::EventType eventType, wordIndex = words->size () - 1; charPos = core::SelectionState::END_OF_WORD; } else { - Line *line = lines->getRef (findLineIndex (event->yWidget)); + Line *line = + lines->getRef (findLineIndexWhenAllocated (event->yWidget)); // Pointer within the break space? - if (event->yWidget > - (lineYOffsetWidget (line) + line->boxAscent + line->boxDescent)) { + if (event->yWidget > (lineYOffsetWidget (line) + + line->borderAscent + line->borderDescent)) { // Choose this break. wordIndex = line->lastWord; charPos = core::SelectionState::END_OF_WORD; - } else if (event->xWidget < lineXOffsetWidget (line)) { + } else if (event->xWidget < line->textOffset) { // Left of the first word in the line. wordIndex = line->firstWord; } else { - int nextWordStartX = lineXOffsetWidget (line); + int nextWordStartX = line->textOffset; for (wordIndex = line->firstWord; wordIndex <= line->lastWord; @@ -778,7 +1122,8 @@ bool Textblock::sendSelectionEvent (core::SelectionState::EventType eventType, if (event->xWidget >= wordStartX && event->xWidget < nextWordStartX) { // We have found the word. - int yWidgetBase = lineYOffsetWidget (line) + line->boxAscent; + int yWidgetBase = + lineYOffsetWidget (line) + line->borderAscent; if (event->xWidget >= nextWordStartX - word->effSpace) { charPos = core::SelectionState::END_OF_WORD; @@ -860,8 +1205,9 @@ bool Textblock::sendSelectionEvent (core::SelectionState::EventType eventType, } } } - it = new TextblockIterator (this, core::Content::SELECTION_CONTENT, - wordIndex); + + it = new TextblockIterator (this, core::Content::maskForSelection (true), + false, wordIndex); r = selectionHandleEvent (eventType, it, charPos, link, event); it->unref (); return r; @@ -877,65 +1223,6 @@ core::Iterator *Textblock::iterator (core::Content::Type mask, bool atEnd) return new TextblockIterator (this, mask, atEnd); } - -/** - * Calculate the size of a widget within the page. - * (Subject of change in the near future!) - */ -void Textblock::calcWidgetSize (core::Widget *widget, core::Requisition *size) -{ - core::Requisition requisition; - int availWidth, availAscent, availDescent; - core::style::Style *wstyle = widget->getStyle(); - - /* We ignore line1_offset[_eff]. */ - availWidth = this->availWidth - getStyle()->boxDiffWidth () - innerPadding; - availAscent = this->availAscent - getStyle()->boxDiffHeight (); - availDescent = this->availDescent; - - if (widget->usesHints ()) { - widget->setWidth (availWidth); - widget->setAscent (availAscent); - widget->setDescent (availDescent); - widget->sizeRequest (size); - } else { - if (wstyle->width == core::style::LENGTH_AUTO || - wstyle->height == core::style::LENGTH_AUTO) - widget->sizeRequest (&requisition); - - if (wstyle->width == core::style::LENGTH_AUTO) - size->width = requisition.width; - else if (core::style::isAbsLength (wstyle->width)) - /* Fixed lengths are only applied to the content, so we have to - * add padding, border and margin. */ - size->width = core::style::absLengthVal (wstyle->width) - + wstyle->boxDiffWidth (); - else - size->width = - core::style::multiplyWithPerLength (availWidth, wstyle->width); - - if (wstyle->height == core::style::LENGTH_AUTO) { - size->ascent = requisition.ascent; - size->descent = requisition.descent; - } else if (core::style::isAbsLength (wstyle->height)) { - /* Fixed lengths are only applied to the content, so we have to - * add padding, border and margin. */ - size->ascent = core::style::absLengthVal (wstyle->height) - + wstyle->boxDiffHeight (); - size->descent = 0; - } else { - size->ascent = - core::style::multiplyWithPerLength (wstyle->height, availAscent); - size->descent = - core::style::multiplyWithPerLength (wstyle->height, availDescent); - } - } - - /* ascent and descent in words do not contain margins. */ - size->ascent -= wstyle->margin.top; - size->descent -= wstyle->margin.bottom; -} - /* * Draw the decorations on a word. */ @@ -994,13 +1281,13 @@ void Textblock::drawText(core::View *view, core::style::Style *style, if (isStart) { /* \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])) @@ -1009,7 +1296,7 @@ void Textblock::drawText(core::View *view, core::style::Style *style, 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); @@ -1020,7 +1307,7 @@ void Textblock::drawText(core::View *view, core::style::Style *style, } break; } - + view->drawText(style->font, style->color, shading, x, y, str ? str : text + start, str ? strlen(str) : len); if (str) @@ -1049,8 +1336,8 @@ void Textblock::drawWord (Line *line, int wordIndex1, int wordIndex2, for (int i = wordIndex1; i <= wordIndex2; i++) w += words->getRef(i)->size.width; w += words->getRef(wordIndex2)->hyphenWidth; - drawBox (view, style, area, xWidget, yWidgetBase - line->boxAscent, - w, line->boxAscent + line->boxDescent, false); + drawBox (view, style, area, xWidget, yWidgetBase - line->borderAscent, + w, line->borderAscent + line->borderDescent, false); } if (wordIndex1 == wordIndex2 && !drawHyphen) { @@ -1080,7 +1367,7 @@ void Textblock::drawWord (Line *line, int wordIndex1, int wordIndex2, text[p++] = hyphenDrawChar[i]; text[p++] = 0; } - + drawWord0 (wordIndex1, wordIndex2, text, totalWidth, drawHyphen, style, view, area, xWidget, yWidgetBase); } @@ -1245,8 +1532,17 @@ void Textblock::drawSpace(int wordIndex, core::View *view, */ void Textblock::drawLine (Line *line, core::View *view, core::Rectangle *area) { - int xWidget = lineXOffsetWidget(line); - int yWidgetBase = lineYOffsetWidget (line) + line->boxAscent; + DBG_OBJ_ENTER ("draw", 0, "drawLine", "..., %d, %d, %d * %d", + area->x, area->y, area->width, area->height); + + int xWidget = line->textOffset; + int yWidgetBase = lineYOffsetWidget (line) + line->borderAscent; + + DBG_OBJ_MSGF ("draw", 1, "line from %d to %d (%d words), at (%d, %d)", + line->firstWord, line->lastWord, words->size (), + xWidget, yWidgetBase); + DBG_MSG_WORD ("draw", 0, "<i>line starts with: </i>", line->firstWord, ""); + DBG_MSG_WORD ("draw", 0, "<i>line ends with: </i>", line->lastWord, ""); for (int wordIndex = line->firstWord; wordIndex <= line->lastWord && xWidget < area->x + area->width; @@ -1256,10 +1552,10 @@ void Textblock::drawLine (Line *line, core::View *view, core::Rectangle *area) if (xWidget + wordSize + word->hyphenWidth + word->effSpace >= area->x) { if (word->content.type == core::Content::TEXT || - word->content.type == core::Content::WIDGET) { + word->content.type == core::Content::WIDGET_IN_FLOW) { if (word->size.width > 0) { - if (word->content.type == core::Content::WIDGET) { + if (word->content.type == core::Content::WIDGET_IN_FLOW) { core::Widget *child = word->content.widget; core::Rectangle childArea; @@ -1290,8 +1586,8 @@ void Textblock::drawLine (Line *line, core::View *view, core::Rectangle *area) if (word->spaceStyle->hasBackground ()) drawBox (view, word->spaceStyle, area, xWidget + wordSize, - yWidgetBase - line->boxAscent, word->effSpace, - line->boxAscent + line->boxDescent, false); + yWidgetBase - line->borderAscent, word->effSpace, + line->borderAscent + line->borderDescent, false); drawSpace(wordIndex, view, area, xWidget + wordSize, yWidgetBase); } @@ -1300,13 +1596,46 @@ void Textblock::drawLine (Line *line, core::View *view, core::Rectangle *area) } xWidget += wordSize + word->effSpace; } + + DBG_OBJ_LEAVE (); } /** - * Find the first line index that includes y, relative to top of widget. + * Find the first line index that includes y, which is given in widget + * coordinates. */ int Textblock::findLineIndex (int y) { + return wasAllocated () ? + findLineIndexWhenAllocated (y) : findLineIndexWhenNotAllocated (y); +} + +int Textblock::findLineIndexWhenNotAllocated (int y) +{ + if (lines->size() == 0) + return -1; + else + return + findLineIndex (y, calcVerticalBorder (getStyle()->padding.top, + getStyle()->borderWidth.top, + getStyle()->margin.top, + lines->getRef(0)->borderAscent, + lines->getRef(0)->marginAscent)); +} + +int Textblock::findLineIndexWhenAllocated (int y) +{ + assert (wasAllocated ()); + return findLineIndex (y, childBaseAllocation.ascent); +} + +int Textblock::findLineIndex (int y, int ascent) +{ + DBG_OBJ_ENTER ("events", 0, "findLineIndex", "%d, %d", y, ascent); + + core::Allocation alloc; + alloc.ascent = ascent; // More is not needed. + int maxIndex = lines->size () - 1; int step, index, low = 0; @@ -1314,12 +1643,12 @@ int Textblock::findLineIndex (int y) while ( step > 1 ) { index = low + step; if (index <= maxIndex && - lineYOffsetWidgetI (index) <= y) + lineYOffsetWidgetIAllocation (index, &alloc) <= y) low = index; step = (step + 1) >> 1; } - if (low < maxIndex && lineYOffsetWidgetI (low + 1) <= y) + if (low < maxIndex && lineYOffsetWidgetIAllocation (low + 1, &alloc) <= y) low++; /* @@ -1332,6 +1661,10 @@ int Textblock::findLineIndex (int y) * Dw_page_find_link() --EG * That function has now been inlined into Dw_page_motion_notify() --JV */ + + DBG_OBJ_MSGF ("events", 1, "=> %d", low); + DBG_OBJ_LEAVE (); + return low; } @@ -1340,12 +1673,13 @@ int Textblock::findLineIndex (int y) */ 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 ()) + if (wordIndex < 0 || wordIndex >= words->size () || + // Also regard not-yet-existing lines. + lines->size () <= 0 || wordIndex > lines->getLastRef()->lastWord) return -1; + int high = lines->size () - 1, index, low = 0; + while (true) { index = (low + high) / 2; if (wordIndex >= lines->getRef(index)->firstWord) { @@ -1396,14 +1730,14 @@ Textblock::Word *Textblock::findWord (int x, int y, bool *inSpace) *inSpace = false; - if ((lineIndex = findLineIndex (y)) >= lines->size ()) + if ((lineIndex = findLineIndexWhenAllocated (y)) >= lines->size ()) return NULL; line = lines->getRef (lineIndex); - yWidgetBase = lineYOffsetWidget (line) + line->boxAscent; - if (yWidgetBase + line->boxDescent <= y) + yWidgetBase = lineYOffsetWidget (line) + line->borderAscent; + if (yWidgetBase + line->borderDescent <= y) return NULL; - xCursor = lineXOffsetWidget (line); + xCursor = line->textOffset; for (wordIndex = line->firstWord; wordIndex <= line->lastWord;wordIndex++) { word = words->getRef (wordIndex); lastXCursor = xCursor; @@ -1432,23 +1766,37 @@ 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); + DBG_OBJ_ENTER ("draw", 0, "draw", "%d, %d, %d * %d", + area->x, area->y, area->width, area->height); int lineIndex; Line *line; - drawWidgetBox (view, area, false); + // Instead of drawWidgetBox, use drawBox to include verticalOffset. + if (getParent() == NULL) { + // The toplevel (parent == NULL) widget is a special case, which + // we leave to drawWidgetBox; verticalOffset will here always 0. + assert (verticalOffset == 0); + drawWidgetBox (view, area, false); + } else + drawBox (view, getStyle(), area, 0, verticalOffset, allocation.width, + getHeight() - verticalOffset, false); - lineIndex = findLineIndex (area->y); + lineIndex = findLineIndexWhenAllocated (area->y); for (; lineIndex < lines->size (); lineIndex++) { line = lines->getRef (lineIndex); if (lineYOffsetWidget (line) >= area->y + area->height) break; + DBG_OBJ_MSGF ("draw", 0, "line %d (of %d)", lineIndex, lines->size ()); drawLine (line, view, area); } + + if(outOfFlowMgr) + outOfFlowMgr->draw(view, area); + + DBG_OBJ_LEAVE (); } /** @@ -1457,11 +1805,22 @@ void Textblock::draw (core::View *view, core::Rectangle *area) Textblock::Word *Textblock::addWord (int width, int ascent, int descent, short flags, core::style::Style *style) { + DBG_OBJ_ENTER ("construct.word", 0, "addWord", "%d * (%d + %d), %d, %p", + width, ascent, descent, flags, style); + + if (lineBreakWidth == -1) { + lineBreakWidth = getAvailWidth (true); + DBG_OBJ_SET_NUM ("lineBreakWidth", lineBreakWidth); + } + words->increase (); + DBG_OBJ_SET_NUM ("words.size", words->size ()); int wordNo = words->size () - 1; initWord (wordNo); fillWord (wordNo, width, ascent, descent, flags, style); - return words->getRef (wordNo);; + + DBG_OBJ_LEAVE (); + return words->getRef (wordNo); } /** @@ -1476,6 +1835,21 @@ void Textblock::initWord (int wordNo) word->spaceImgRenderer = NULL; } +void Textblock::cleanupWord (int wordNo) +{ + Word *word = words->getRef (wordNo); + + if (word->content.type == core::Content::WIDGET_IN_FLOW) + delete word->content.widget; + /** \todo Widget references? What about texts? */ + + removeWordImgRenderer (wordNo); + removeSpaceImgRenderer (wordNo); + + word->style->unref (); + word->spaceStyle->unref (); +} + void Textblock::removeWordImgRenderer (int wordNo) { Word *word = words->getRef (wordNo); @@ -1581,7 +1955,7 @@ int Textblock::textWidth(const char *text, int start, int len, if (isStart) { /* \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; @@ -1589,7 +1963,7 @@ int Textblock::textWidth(const char *text, int start, int len, ret = layout->textWidth(style->font, text+start, len); } else { int after = 0; - + text += start; while (ispunct(text[after])) after++; @@ -1676,7 +2050,8 @@ void Textblock::calcTextSize (const char *text, size_t len, void Textblock::addText (const char *text, size_t len, core::style::Style *style) { - PRINTF ("[%p] ADD_TEXT (%d characters)\n", this, (int)len); + DBG_OBJ_ENTER ("construct.word", 0, "addText", "..., %d, %p", + (int)len, style); // Count dividing characters. int numParts = 1; @@ -1736,10 +2111,10 @@ void Textblock::addText (const char *text, size_t len, foundDiv = j; } } - + if (foundDiv != -1) { int lDiv = strlen (divChars[foundDiv].s); - + if (divChars[foundDiv].charRemoved) { assert (divChars[foundDiv].penaltyIndexLeft != -1); assert (divChars[foundDiv].penaltyIndexRight == -1); @@ -1824,7 +2199,7 @@ void Textblock::addText (const char *text, size_t len, // Finished! for (int i = 0; i < numParts; i++) { short flags = 0; - + // If this parts adjoins at least one division characters, // for which canBeHyphenated is set to false (this is the // case for soft hyphens), do not hyphenate. @@ -1847,14 +2222,14 @@ void Textblock::addText (const char *text, size_t len, flags |= Word::WORD_START; if (i == numParts - 1) flags |= Word::WORD_END; - + addText0 (text + partStart[i], partEnd[i] - partStart[i], flags, style, &wordSize[i]); //printf ("[%p] %d: added word part: ", this, words->size() - 1); //printWordWithFlags (words->getLastRef()); //printf ("\n"); - + //PRINTF("H... [%d] '", i); //for (int j = partStart[i]; j < partEnd[i]; j++) // PUTCHAR(text[j]); @@ -1865,6 +2240,7 @@ void Textblock::addText (const char *text, size_t len, setBreakOption (word, style, penalties[partPenaltyIndex[i]][0], penalties[partPenaltyIndex[i]][1], false); + DBG_SET_WORD (words->size () - 1); if (charRemoved[i]) // Currently, only unconditional hyphens (UTF-8: @@ -1881,6 +2257,8 @@ void Textblock::addText (const char *text, size_t len, } } } + + DBG_OBJ_LEAVE (); } void Textblock::calcTextSizes (const char *text, size_t textLen, @@ -1923,6 +2301,19 @@ void Textblock::calcTextSizes (const char *text, size_t textLen, void Textblock::addText0 (const char *text, size_t len, short flags, core::style::Style *style, core::Requisition *size) { + DBG_OBJ_ENTER ("construct.word", 0, "addText0", + "..., %d, %s:%s:%s:%s:%s:%s:%s, %p, %d * (%d + %d)", + (int)len, + // Ugly copy&paste from printWordFlags: + (flags & Word::CAN_BE_HYPHENATED) ? "h?" : "--", + (flags & Word::DIV_CHAR_AT_EOL) ? "de" : "--", + (flags & Word::PERM_DIV_CHAR) ? "dp" : "--", + (flags & Word::DRAW_AS_ONE_TEXT) ? "t1" : "--", + (flags & Word::UNBREAKABLE_FOR_MIN_WIDTH) ? "um" : "--", + (flags & Word::WORD_START) ? "st" : "--", + (flags & Word::WORD_END) ? "en" : "--", + style, size->width, size->ascent, size->descent); + //printf("[%p] addText0 ('", this); //for (size_t i = 0; i < len; i++) // putchar(text[i]); @@ -1936,7 +2327,17 @@ void Textblock::addText0 (const char *text, size_t len, short flags, word->content.type = core::Content::TEXT; word->content.text = layout->textZone->strndup(text, len); + DBG_SET_WORD (words->size () - 1); + + // The following debug message may be useful to identify the + // different textblocks. + + //if (words->size() == 1) + // printf ("[%p] first word: '%s'\n", this, text); + processWord (words->size () - 1); + + DBG_OBJ_LEAVE (); } /** @@ -1944,28 +2345,57 @@ void Textblock::addText0 (const char *text, size_t len, short flags, */ void Textblock::addWidget (core::Widget *widget, core::style::Style *style) { - Word *word; - core::Requisition size; + DBG_OBJ_ENTER ("construct.word", 0, "addWidget", "%p, %p", widget, style); /* We first assign -1 as parent_ref, since the call of widget->size_request * will otherwise let this Textblock be rewrapped from the beginning. * (parent_ref is actually undefined, but likely has the value 0.) At the, * end of this function, the correct value is assigned. */ widget->parentRef = -1; + DBG_OBJ_SET_NUM_O (widget, "parentRef", widget->parentRef); - 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, 0, style); + PRINTF ("adding the %s %p to %p (word %d) ...\n", + widget->getClassName(), widget, this, words->size()); + + if (containingBlock->outOfFlowMgr == NULL) { + containingBlock->outOfFlowMgr = new OutOfFlowMgr (containingBlock); + DBG_OBJ_ASSOC (containingBlock, containingBlock->outOfFlowMgr); + } + + if (OutOfFlowMgr::isWidgetOutOfFlow (widget)) { + PRINTF (" -> out of flow.\n"); + + widget->setParent (containingBlock); + widget->setGenerator (this); + containingBlock->outOfFlowMgr->addWidgetOOF (widget, this, + words->size ()); + Word *word = addWord (0, 0, 0, 0, style); + word->content.type = core::Content::WIDGET_OOF_REF; + word->content.widget = widget; - word->content.type = core::Content::WIDGET; - word->content.widget = widget; + // After a out-of-flow reference, breaking is allowed. (This avoids some + // problems with breaking near float definitions.) + setBreakOption (word, style, 0, 0, false); + } else { + PRINTF (" -> within flow.\n"); - //DBG_OBJ_ARRSET_PTR (page, "words.%d.content.widget", words->size() - 1, - // word->content.widget); + widget->setParent (this); + + // TODO Replace (perhaps) later "textblock" by "OOF aware widget". + if (widget->instanceOf (Textblock::CLASS_ID)) + containingBlock->outOfFlowMgr->addWidgetInFlow ((Textblock*)widget, + this, words->size ()); + + core::Requisition size; + widget->sizeRequest (&size); + Word *word = addWord (size.width, size.ascent, size.descent, 0, style); + word->content.type = core::Content::WIDGET_IN_FLOW; + word->content.widget = widget; + } + + DBG_SET_WORD (words->size () - 1); processWord (words->size () - 1); //DBG_OBJ_SET_NUM (word->content.widget, "parent_ref", @@ -1975,6 +2405,8 @@ void Textblock::addWidget (core::Widget *widget, core::style::Style *style) // "Assigning parent_ref = %d to added word %d, " // "in page with %d word(s)\n", // lines->size () - 1, words->size() - 1, words->size()); + + DBG_OBJ_LEAVE (); } /** @@ -1986,8 +2418,11 @@ void Textblock::addWidget (core::Widget *widget, core::style::Style *style) */ bool Textblock::addAnchor (const char *name, core::style::Style *style) { + DBG_OBJ_ENTER ("construct.word", 0, "addAnchor", "\"%s\", %p", name, style); + char *copy; int y; + bool result; // Since an anchor does not take any space, it is safe to call // addAnchor already here. @@ -2005,7 +2440,7 @@ bool Textblock::addAnchor (const char *name, core::style::Style *style) * \todo It may be necessary for future uses to save the anchor in * some way, e.g. when parts of the widget tree change. */ - return false; + result = false; else { Anchor *anchor; @@ -2013,8 +2448,12 @@ bool Textblock::addAnchor (const char *name, core::style::Style *style) anchor = anchors->getRef(anchors->size() - 1); anchor->name = copy; anchor->wordIndex = words->size(); - return true; + result = true; } + + DBG_OBJ_MSGF ("construct.word", 0, "=> %s", result ? "true" : "false"); + DBG_OBJ_LEAVE (); + return result; } @@ -2023,39 +2462,62 @@ bool Textblock::addAnchor (const char *name, core::style::Style *style) */ void Textblock::addSpace (core::style::Style *style) { + DBG_OBJ_ENTER ("construct.word", 0, "addSpace", "%p", style); + int wordIndex = words->size () - 1; if (wordIndex >= 0) { fillSpace (wordIndex, style); + DBG_SET_WORD (wordIndex); accumulateWordData (wordIndex); correctLastWordExtremes (); } + + DBG_OBJ_LEAVE (); } /** * Add a break option (see setBreakOption() for details). Used instead - * of addStyle for ideographic characters. + * of addSpace for ideographic characters. * * When "forceBreak" is true, a break is even possible within PRE etc. */ void Textblock::addBreakOption (core::style::Style *style, bool forceBreak) { + DBG_OBJ_ENTER ("construct.word", 0, "addBreakOption", "%p, %s", + style, forceBreak ? "true" : "false"); + int wordIndex = words->size () - 1; if (wordIndex >= 0) { setBreakOption (words->getRef(wordIndex), style, 0, 0, forceBreak); + DBG_SET_WORD (wordIndex); // Call of accumulateWordData() is not needed here. correctLastWordExtremes (); } + + DBG_OBJ_LEAVE (); } void Textblock::fillSpace (int wordNo, core::style::Style *style) { + DBG_OBJ_ENTER ("construct.word", 0, "fillSpace", "%d, ...", wordNo); + + DBG_OBJ_MSGF ("construct.word", 1, "style.white-space = %s", + style->whiteSpace == core::style::WHITE_SPACE_NORMAL ? "normal" + : style->whiteSpace == core::style::WHITE_SPACE_PRE ? "pre" + : style->whiteSpace == core::style::WHITE_SPACE_NOWRAP ? + "nowrap" + : style->whiteSpace == core::style::WHITE_SPACE_PRE_WRAP ? + "pre-wrap" + : style->whiteSpace == core::style::WHITE_SPACE_PRE_LINE ? + "pre-line" : "???"); + // 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> @@ -2067,30 +2529,26 @@ void Textblock::fillSpace (int wordNo, core::style::Style *style) // TODO: This line does not work: addBreakOption (word, style); - // Do not override a previously set break penalty. - if (!word->content.space) { + if (// Do not override a previously set break penalty: + !word->content.space && + // OOF references are considered specially, and must not have a space: + word->content.type != core::Content::WIDGET_OOF_REF) { setBreakOption (word, style, 0, 0, false); word->content.space = true; - word->effSpace = word->origSpace = style->font->spaceWidth + - style->wordSpacing; - - //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->origSpace = word->effSpace = + style->font->spaceWidth + style->wordSpacing; removeSpaceImgRenderer (wordNo); word->spaceStyle->unref (); word->spaceStyle = style; style->ref (); - + setSpaceImgRenderer (wordNo); } + + DBG_OBJ_LEAVE (); } /** @@ -2102,19 +2560,24 @@ void Textblock::setBreakOption (Word *word, core::style::Style *style, int breakPenalty1, int breakPenalty2, bool forceBreak) { + DBG_OBJ_ENTER ("construct.word", 0, "setBreakOption", "..., %d, %d, %s", + breakPenalty1, breakPenalty2, forceBreak ? "true" : "false"); + // TODO: lineMustBeBroken should be independent of the penalty // index? Otherwise, examine the last line. if (!word->badnessAndPenalty.lineMustBeBroken(0)) { - if (forceBreak || isBreakAllowed (word)) + if (forceBreak || isBreakAllowed (style)) word->badnessAndPenalty.setPenalties (breakPenalty1, breakPenalty2); else word->badnessAndPenalty.setPenalty (PENALTY_PROHIBIT_BREAK); } + + DBG_OBJ_LEAVE (); } -bool Textblock::isBreakAllowed (Word *word) +bool Textblock::isBreakAllowed (core::style::Style *style) { - switch (word->style->whiteSpace) { + switch (style->whiteSpace) { case core::style::WHITE_SPACE_NORMAL: case core::style::WHITE_SPACE_PRE_LINE: case core::style::WHITE_SPACE_PRE_WRAP: @@ -2123,11 +2586,12 @@ bool Textblock::isBreakAllowed (Word *word) case core::style::WHITE_SPACE_PRE: case core::style::WHITE_SPACE_NOWRAP: return false; + + default: + // compiler happiness + lout::misc::assertNotReached (); + return false; } - - // compiler happiness - lout::misc::assertNotReached (); - return false; } @@ -2136,11 +2600,17 @@ bool Textblock::isBreakAllowed (Word *word) */ void Textblock::addParbreak (int space, core::style::Style *style) { + DBG_OBJ_ENTER ("construct.word", 0, "addParbreak", "%d, %p", + space, style); + DBG_OBJ_MSG ("construct.word", 0, + "<i>No nesting! Strack trace may be incomplete.</i>"); + DBG_OBJ_LEAVE (); + Word *word; /* A break may not be the first word of a page, or directly after the bullet/number (which is the first word) in a list item. (See - also comment in Dw_page_size_request.) */ + also comment in sizeRequest.) */ if (words->size () == 0 || (hasListitemValue && words->size () == 1)) { /* This is a bit hackish: If a break is added as the @@ -2149,23 +2619,23 @@ void Textblock::addParbreak (int space, core::style::Style *style) a widget is used as a text box (lists, blockquotes, list items etc) -- then we simply adjust the break before, in a way that the space is in any case visible. */ - Widget *widget; - - /* Find the widget where to adjust the breakSpace. */ - for (widget = this; - widget->getParent() && - widget->getParent()->instanceOf (Textblock::CLASS_ID); + /* Find the widget where to adjust the breakSpace. (Only + consider normal flow, no floats etc.) */ + for (Widget *widget = this; + widget->getParent() != NULL && + widget->getParent()->instanceOf (Textblock::CLASS_ID) && + !OutOfFlowMgr::isRefOutOfFlow (widget->parentRef); widget = widget->getParent ()) { Textblock *textblock2 = (Textblock*)widget->getParent (); int index = textblock2->hasListitemValue ? 1 : 0; bool isfirst = (textblock2->words->getRef(index)->content.type - == core::Content::WIDGET + == core::Content::WIDGET_IN_FLOW && textblock2->words->getRef(index)->content.widget == widget); if (!isfirst) { - /* The page we searched for has been found. */ + /* The text block we searched for has been found. */ Word *word2; - int lineno = widget->parentRef; + int lineno = OutOfFlowMgr::getLineNoFromRef (widget->parentRef); if (lineno > 0 && (word2 = @@ -2174,7 +2644,8 @@ void Textblock::addParbreak (int space, core::style::Style *style) word2->content.type == core::Content::BREAK) { if (word2->content.breakSpace < space) { word2->content.breakSpace = space; - textblock2->queueResize (lineno, false); + textblock2->queueResize + (OutOfFlowMgr::createRefNormalFlow (lineno), false); textblock2->mustQueueResize = false; } } @@ -2182,6 +2653,7 @@ void Textblock::addParbreak (int space, core::style::Style *style) } /* Otherwise continue to examine parents. */ } + /* Return in any case. */ return; } @@ -2195,7 +2667,7 @@ void Textblock::addParbreak (int space, core::style::Style *style) misc::max (word->content.breakSpace, space); lastLine->breakSpace = misc::max (word->content.breakSpace, - lastLine->marginDescent - lastLine->boxDescent, + lastLine->marginDescent - lastLine->borderDescent, lastLine->breakSpace); return; } @@ -2205,6 +2677,10 @@ void Textblock::addParbreak (int space, core::style::Style *style) word->content.type = core::Content::BREAK; word->badnessAndPenalty.setPenalty (PENALTY_FORCE_BREAK); word->content.breakSpace = space; + + DBG_SET_WORD (words->size () - 1); + + breakAdded (); processWord (words->size () - 1); } @@ -2213,6 +2689,8 @@ void Textblock::addParbreak (int space, core::style::Style *style) */ void Textblock::addLinebreak (core::style::Style *style) { + DBG_OBJ_ENTER ("construct.word", 0, "addLinebreak", "%p", style); + Word *word; if (words->size () == 0 || @@ -2230,9 +2708,41 @@ void Textblock::addLinebreak (core::style::Style *style) word->content.type = core::Content::BREAK; word->badnessAndPenalty.setPenalty (PENALTY_FORCE_BREAK); word->content.breakSpace = 0; + + DBG_SET_WORD (words->size () - 1); + + breakAdded (); processWord (words->size () - 1); + + DBG_OBJ_LEAVE (); } +/** + * Called directly after a (line or paragraph) break has been added. + */ +void Textblock::breakAdded () +{ + assert (words->size () >= 1); + assert (words->getRef(words->size () - 1)->content.type + == core::Content::BREAK); + + // Any space before is removed. It is not used; on the other hand, + // this snippet (an example from a real-world debugging session) + // would cause problems: + // + // <input style="width: 100%" .../> + // <button ...>...</button> + // + // (Notice the space between <input> and <button>, and also that + // the HTML parser will insert a BREAK between them.) The <input> + // would be given the available width ("width: 100%"), but the + // actual width (Word::totalWidth) would include the space, so that + // the width of the line is larger than the available width. + + if (words->size () >= 2) + words->getRef(words->size () - 2)->origSpace = + words->getRef(words->size () - 2)->effSpace = 0; +} /** * \brief Search recursively through widget. @@ -2242,6 +2752,10 @@ void Textblock::addLinebreak (core::style::Style *style) */ core::Widget *Textblock::getWidgetAtPoint(int x, int y, int level) { + //printf ("%*s-> examining the %s %p (%d, %d, %d x (%d + %d))\n", + // 3 * level, "", getClassName (), this, allocation.x, allocation.y, + // allocation.width, allocation.ascent, allocation.descent); + int lineIndex, wordIndex; Line *line; @@ -2252,7 +2766,15 @@ core::Widget *Textblock::getWidgetAtPoint(int x, int y, int level) return NULL; } - lineIndex = findLineIndex (y - allocation.y); + // First, search for widgets out of flow, notably floats, since + // there are cases where they overlap child textblocks. Should + // later be refined using z-index. + Widget *oofWidget = + outOfFlowMgr ? outOfFlowMgr->getWidgetAtPoint (x, y, level) : NULL; + if (oofWidget) + return oofWidget; + + lineIndex = findLineIndexWhenAllocated (y - allocation.y); if (lineIndex < 0 || lineIndex >= lines->size ()) { return this; @@ -2263,12 +2785,14 @@ core::Widget *Textblock::getWidgetAtPoint(int x, int y, int level) for (wordIndex = line->firstWord; wordIndex <= line->lastWord;wordIndex++) { Word *word = words->getRef (wordIndex); - if (word->content.type == core::Content::WIDGET) { + if (word->content.type == core::Content::WIDGET_IN_FLOW) { core::Widget * childAtPoint; - childAtPoint = word->content.widget->getWidgetAtPoint (x, y, - level + 1); - if (childAtPoint) { - return childAtPoint; + if (word->content.widget->wasAllocated ()) { + childAtPoint = word->content.widget->getWidgetAtPoint (x, y, + level + 1); + if (childAtPoint) { + return childAtPoint; + } } } } @@ -2288,7 +2812,8 @@ void Textblock::handOverBreak (core::style::Style *style) Line *lastLine = lines->getRef (lines->size () - 1); if (lastLine->breakSpace != 0 && (parent = getParent()) && - parent->instanceOf (Textblock::CLASS_ID)) { + parent->instanceOf (Textblock::CLASS_ID) && + parent->getStyle()->display != core::style::DISPLAY_BLOCK) { Textblock *textblock2 = (Textblock*) parent; textblock2->addParbreak(lastLine->breakSpace, style); } @@ -2303,13 +2828,16 @@ void Textblock::handOverBreak (core::style::Style *style) */ void Textblock::flush () { - PRINTF ("[%p] FLUSH => %s (parentRef = %d)\n", - this, mustQueueResize ? "true" : "false", parentRef); + DBG_OBJ_ENTER0 ("resize", 0, "flush"); if (mustQueueResize) { + DBG_OBJ_MSG ("resize", 0, "mustQueueResize set"); + queueResize (-1, true); mustQueueResize = false; } + + DBG_OBJ_LEAVE (); } @@ -2344,7 +2872,7 @@ void Textblock::changeLinkColor (int link, int newColor) old_style->unref(); break; } - case core::Content::WIDGET: + case core::Content::WIDGET_IN_FLOW: { core::Widget *widget = word->content.widget; styleAttrs = *widget->getStyle(); styleAttrs.color = core::style::Color::create (layout, @@ -2362,7 +2890,7 @@ void Textblock::changeLinkColor (int link, int newColor) } if (changed) queueDrawArea (0, lineYOffsetWidget(line), allocation.width, - line->boxAscent + line->boxDescent); + line->borderAscent + line->borderDescent); } } @@ -2387,13 +2915,295 @@ void Textblock::queueDrawRange (int index1, int index2) if (line1idx >= 0 && line2idx >= 0) { Line *line1 = lines->getRef (line1idx), *line2 = lines->getRef (line2idx); - int y = lineYOffsetWidget (line1) + line1->boxAscent - + int y = lineYOffsetWidget (line1) + line1->borderAscent - line1->contentAscent; - int h = lineYOffsetWidget (line2) + line2->boxAscent + + int h = lineYOffsetWidget (line2) + line2->borderAscent + line2->contentDescent - y; queueDrawArea (0, y, allocation.width, h); } } +void Textblock::setVerticalOffset (int verticalOffset) +{ + DBG_OBJ_ENTER ("resize", 0, "setVerticalOffset", "%d", verticalOffset); + + if (this->verticalOffset != verticalOffset) { + this->verticalOffset = verticalOffset; + DBG_OBJ_SET_NUM ("verticalOffset", verticalOffset); + mustQueueResize = true; + queueDraw (); // Could perhaps be optimized. + } + + DBG_OBJ_LEAVE (); +} + +/** + * Called by dw::OutOfFlowMgr when the border has changed due to a + * float (or some floats). + * + * "y", which given in widget coordinates, denotes the minimal + * position (when more than one float caused this), "vloat" the + * floating widget belonging to "y". + */ +void Textblock::borderChanged (int y, Widget *vloat) +{ + DBG_OBJ_ENTER ("resize", 0, "borderChanged", "%d, %p", y, vloat); + + int lineIndex = findLineIndex (y); + DBG_OBJ_MSGF ("resize", 1, "Line index: %d (of %d).", + lineIndex, lines->size ()); + + // Nothing to do at all, when lineIndex >= lines->size (), + // i. e. the change is below the bottom of this widget. + if (lineIndex < lines->size ()) { + int wrapLineIndex; + if (lineIndex < 0) + // Rewrap all. + wrapLineIndex = 0; + else + wrapLineIndex = lineIndex; + + int realWrapLineIndex = wrapLineIndex; + // The following two variables are only used for debugging: + int minWrapLineIndex = wrapLineIndex, maxWrapLineIndex = wrapLineIndex; + + if (vloat->getGenerator() == this && lines->size () > 0) { + bool found = false; + // Sometimes, the respective word is not yet part of a + // line. Nothing to do, but because of the assertion below + // (and also for performace reasons) this should be + // considered. TODO: Integrate this below. + for (int wordIndex = + lines->size() > 0 ? lines->getLastRef()->lastWord + 1 : 0; + !found && wordIndex < words->size(); wordIndex++) { + Word *word = words->getRef (wordIndex); + if (word->content.type == core::Content::WIDGET_OOF_REF && + word->content.widget == vloat) + found = true; + } + + // We search for the line of the float reference. There are + // two cases when this is not the line corresponsing to y: + // + // (1) When the float was moved down, due to collisions with + // other floats: in this case, the line number gets + // smaller (since the float reference is before). + // + // (2) In some cases, the line number may become larger, due + // to the per-line optimization of the words: initially, + // lines->size() - 1 is assigned, but it may happen that + // the float reference is put into another line. + // + // Only in the first case, a correction is neccessary, but a + // test for the second case is useful. (TODO: I've forgotten + // why a correction is neccessary.) + // + // Searched is done in the following order: + // + // - wrapLineIndex, + // - wrapLineIndex - 1, + // - wrapLineIndex + 1, + // - wrapLineIndex - 2, + // - wrapLineIndex + 2, + // + // etc. until either the float reference has been found or + // all lines have been searched (the latter triggers an + // abortion). + + bool exceedsBeginning = false, exceedsEnd = false; + for (int i = 0; !found; i++) { + bool exceeds; + int lineIndex2; + if (i % 2 == 0) { + // even: +0, +1, +2, ... + lineIndex2 = realWrapLineIndex + i / 2; + if (i > 0) + exceeds = exceedsEnd = lineIndex2 >= lines->size (); + else + exceeds = exceedsEnd = false; + } else { + // odd: -1, -2, ... + lineIndex2 = realWrapLineIndex - (i + 1) / 2; + exceeds = exceedsBeginning = lineIndex2 < 0; + } + + DBG_OBJ_MSGF ("resize", 2, + "lineIndex2 = %d (of %d), exceeds = %s, " + "exceedsBeginning = %s, exceedsEnd = %s", + lineIndex2, lines->size (), + exceeds ? "true" : "false", + exceedsBeginning ? "true" : "false", + exceedsEnd ? "true" : "false"); + + if (exceedsBeginning && exceedsEnd) + break; + + if (!exceeds) { + Line *line = lines->getRef (lineIndex2); + for (int wordIndex = line->firstWord; + !found && wordIndex <= line->lastWord; wordIndex++) { + Word *word = words->getRef (wordIndex); + if (word->content.type == core::Content::WIDGET_OOF_REF && + word->content.widget == vloat) { + found = true; + // Correct only by smaller values (case (1) above): + realWrapLineIndex = + misc::min (realWrapLineIndex, lineIndex2); + } + } + + minWrapLineIndex = misc::min (minWrapLineIndex, lineIndex2); + maxWrapLineIndex = misc::max (maxWrapLineIndex, lineIndex2); + } + } + + assert (found); + } + + DBG_OBJ_MSGF ("resize", 1, + "wrapLineIndex: corrected from %d to %d (%d lines total); " + "searched between %d and %d; this is the GB: %s", + wrapLineIndex, realWrapLineIndex, lines->size (), + minWrapLineIndex, maxWrapLineIndex, + vloat->getGenerator() == this ? "yes" : "no"); + + queueResize (OutOfFlowMgr::createRefNormalFlow (realWrapLineIndex), true); + + // Notice that the line no. realWrapLineIndex may not exist yet. + if (realWrapLineIndex == 0) + lastWordDrawn = misc::min (lastWordDrawn, -1); + else + lastWordDrawn = + misc::min (lastWordDrawn, + lines->getRef(realWrapLineIndex - 1)->lastWord); + DBG_OBJ_SET_NUM ("lastWordDrawn", lastWordDrawn); + + // TODO Is the following necessary? Or even useless? + //redrawY = + // misc::min (redrawY, + // lineYOffsetWidget (lines->getRef (realWrapLineIndex))); + //DBG_OBJ_SET_NUM ("redrawY", redrawY); + } + + DBG_OBJ_LEAVE (); +} + +void Textblock::clearPositionChanged () +{ + DBG_OBJ_ENTER0 ("resize", 0, "clearPositionChanged"); + // Not very efficient (actually, a rewrapping could be easily + // avoided), but this case should not occur very often. + queueResize (0, false); + DBG_OBJ_LEAVE (); +} + +void Textblock::oofSizeChanged (bool extremesChanged) +{ + DBG_OBJ_ENTER ("resize", 0, "oofSizeChanged", "%s", + extremesChanged ? "true" : "false"); + queueResize (-1, extremesChanged); + + // See Textblock::getAvailWidthForChild(): Extremes changes may become also + // relevant for the children, under certain conditions: + if (extremesChanged && !mustBeWidenedToAvailWidth ()) + containerSizeChanged (); + + DBG_OBJ_LEAVE (); +} + +Textblock *Textblock::getTextblockForLine (Line *line) +{ + return getTextblockForLine (line->firstWord, line->lastWord); +} + +Textblock *Textblock::getTextblockForLine (int lineNo) +{ + // Can also be used for a line not yet existing. + int firstWord = lineNo == 0 ? 0 : lines->getRef(lineNo - 1)->lastWord + 1; + int lastWord = lineNo < lines->size() ? + lines->getRef(lineNo)->lastWord : words->size() - 1; + return getTextblockForLine (firstWord, lastWord); +} + +Textblock *Textblock::getTextblockForLine (int firstWord, int lastWord) +{ + DBG_OBJ_ENTER ("resize", 0, "getTextblockForLine", "%d, %d", + firstWord, lastWord); + DBG_OBJ_MSGF ("resize", 1, "words.size = %d", words->size ()); + + Textblock *textblock = NULL; + + if (firstWord < words->size ()) { + // A textblock is always between two line breaks, and so the + // first word of the line. + Word *word = words->getRef (firstWord); + + DBG_MSG_WORD ("resize", 1, "<i>first word:</i> ", firstWord, ""); + + if (word->content.type == core::Content::WIDGET_IN_FLOW) { + Widget *widget = word->content.widget; + if (widget->instanceOf (Textblock::CLASS_ID) && + // Exclude some cases where a textblock constitutes a new + // container (see definition of float container in + // Textblock::isContainingBlock). + widget->getStyle()->display != core::style::DISPLAY_INLINE_BLOCK && + widget->getStyle()->overflow == core::style::OVERFLOW_VISIBLE) + textblock = (Textblock*)widget; + + // (TODO: It would look nicer if there is one common place + // for such definitions. Will be fixed in "dillo_grows", not + // here.) + } + } + + DBG_OBJ_MSGF ("resize", 1, "=> %p", textblock); + DBG_OBJ_LEAVE (); + return textblock; +} + +/** + * Includes margin, border, and padding. + */ +int Textblock::yOffsetOfLineToBeCreated () +{ + // This method does not return an exact result: the position of the + // new line, which does not yet exist, cannot be calculated, since + // the top margin of the new line (which collapses either with the + // top margin of the textblock widget, or the bottom margin of the + // last line) must be taken into account. However, this method is + // only called for positioning floats; here, a slight incorrectness + // does not cause real harm. + + // (Similar applies to the line *height*, which calculated in an + // iterative way; see wrapWordInFlow. Using the same approach for + // the *position* is possible, but not worth the increased + // complexity.) + + DBG_OBJ_ENTER0 ("line.yoffset", 0, "yOffsetOfLineToBeCreated"); + + int result; + + if (lines->size () == 0) { + result = verticalOffset + calcVerticalBorder (getStyle()->padding.top, + getStyle()->borderWidth.top, + getStyle()->margin.top, + 0, 0); + DBG_OBJ_MSGF ("line.yoffset", 1, "first line: ... = %d", result); + } else { + Line *firstLine = lines->getRef (0), *lastLine = lines->getLastRef (); + result = verticalOffset + calcVerticalBorder (getStyle()->padding.top, + getStyle()->borderWidth.top, + getStyle()->margin.top, + firstLine->borderAscent, + firstLine->marginAscent) + - firstLine->borderAscent + lastLine->top + lastLine->totalHeight (0); + DBG_OBJ_MSGF ("line.yoffset", 1, "other line: ... = %d", result); + } + + DBG_OBJ_LEAVE (); + + return result; +} + } // namespace dw diff --git a/dw/textblock.hh b/dw/textblock.hh index b85937ba..4bc74669 100644 --- a/dw/textblock.hh +++ b/dw/textblock.hh @@ -4,13 +4,18 @@ #include <limits.h> #include "core.hh" +#include "outofflowmgr.hh" #include "../lout/misc.hh" -// These were used when improved line breaking and hyphenation were -// implemented. Should be cleaned up; perhaps reactivate RTFL again. +// These were used when improved line breaking and hyphenation were implemented. +// Should be, bit by bit, replaced by RTFL (see ../lout/debug.hh). #define PRINTF(fmt, ...) #define PUTCHAR(ch) +#ifdef DBG_RTFL +# define DEBUG +#endif + namespace dw { /** @@ -18,10 +23,11 @@ namespace dw { * of paragraphs. * * <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) have not yet been incorporated into this - * documentation. See \ref dw-line-breaking.</div> + * 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-out-of-flow.</div> * * <h3>Signals</h3> * @@ -32,6 +38,11 @@ namespace dw { * * <h3>Collapsing Spaces</h3> * + * <div style="border: 2px solid #ffff00; margin-top: 0.5em; + * margin-bottom: 0.5em; padding: 0.5em 1em; background-color: + * #ffffe0"><b>Info:</b> Collapsing spaces are deprecated, in favor of + * collapsing margins (see below).</div> + * * The idea behind this is that every paragraph has a specific vertical * space around and that they are combined to one space, according to * rules stated below. A paragraph consists either of the lines between @@ -47,9 +58,9 @@ namespace dw { * * \image html dw-textblock-collapsing-spaces-1-1.png * - * are combined like this: + * are combined like this: * - * \image html dw-textblock-collapsing-spaces-1-2.png + * \image html dw-textblock-collapsing-spaces-1-2.png * * <li> a) If one paragraph is the first paragraph within another, the upper * space of these paragraphs collapse. b) The analogue is the case for the @@ -73,19 +84,20 @@ namespace dw { * automatically. See the code of dw::Textblock::addParBreak. * * <li> To collapse spaces according to rule 2b, - * dw::Textblock::addParBreak::handOverBreak must be called for - * the \em inner widget. The HTML parser does this in - * Html_eventually_pop_dw. + * dw::Textblock::addParBreak::handOverBreak must be called for + * the \em inner widget. The HTML parser does this in + * Html_eventually_pop_dw. * </ul> * * * <h3>Collapsing Margins</h3> * * Collapsing margins, as defined in the CSS2 specification, are, - * supported in addition to collapsing spaces. Also, spaces and margins - * collapse themselves. I.e., the space between two paragraphs is the - * maximum of the space calculated as described in "Collapsing Spaces" - * and the space calculated according to the rules for collapsing margins. + * supported in addition to collapsing spaces. Also, spaces and + * margins collapse themselves. I. e., the space between two + * paragraphs is the maximum of the space calculated as described in + * "Collapsing Spaces" and the space calculated according to the rules + * for collapsing margins. * * (This is an intermediate hybrid state, collapsing spaces are used in * the current version of dillo, while I implemented collapsing margins @@ -93,6 +105,44 @@ namespace dw { * a pure CSS-based dillo, collapsing spaces will not be needed anymore, and * may be removed for simplicity.) * + * Currently implemented cases: + * + * - The top margin of of the textblock widget and the top margin of + * the first line box (based on widgets in the first line) collapse. + * + * - The bottom margin of of the textblock widget and the bottom + * margin of the last line box collapse. + * + * - The bottom margin of a line box and the top margin of the + * following line collapse. Here, the break space is regarded, too. + * + * Open issues: + * + * - Only the value of Style::margin is regarded, not the result of + * the collapsing itself. For the widgets A, B (child of A), and C + * (child of B), the effective margin of A is the maximum of the + * *style* margins of A and B, while the effective margin of B (the + * collapsed margin of B and C) is ignored here. This could be + * solved by introducing an additional "effective" ("calculated", + * "collapsed") margin as an attribute of Widget. + * + * - For similar reasons, backgrounds to not work exactly. Usage of + * Widget::extraSpace should fix this, but it is only fully working + * in the GROWS branch (<http://flpsed.org/hgweb/dillo_grows>). + * + * - Do margins of inline blocks and tables collapse? Check CSS + * spec. (They do currently; if not, ignoring them is simple.) + * + * - Lines which only contain a BREAK should be skipped for collapsing + * margins, or at least all three should collapse: the previous + * margin, the break, and the following margin. (Compare this with + * the CSS spec.) + * + * - Related to this: adding breaks should be revised. + * Textblock::addLinebreak and Textblock::addParbreak work quite + * differently, and Textblock::addParbreak seems much to complex for + * our needs, even when spaces of lines are kept. + * * * <h3>Some Internals</h3> * @@ -124,7 +174,7 @@ namespace dw { * widget: * * <ul> - * <li> The available size of the widget has changed, e.g., because the + * <li> The line break size of the widget has changed, e.g., because the * user has changed the size of the browser window. In this case, * it is necessary to rewrap all the lines. * @@ -157,7 +207,7 @@ private: * badness is not well defined, so fiddling with the penalties is a * bit difficult. */ - + enum { PENALTY_FORCE_BREAK = INT_MIN, PENALTY_PROHIBIT_BREAK = INT_MAX @@ -170,7 +220,7 @@ private: badnessState; int ratio; // ratio is only defined when badness is defined int badness, penalty[2]; - + // For debugging: define DEBUG for more informations in print(). #ifdef DEBUG int totalWidth, idealWidth, totalStretchability, totalShrinkability; @@ -204,13 +254,16 @@ private: void setSinglePenalty (int index, int penalty); int badnessValue (int infLevel); int penaltyValue (int index, int infLevel); - + public: void calcBadness (int totalWidth, int idealWidth, int totalStretchability, int totalShrinkability); inline void setPenalty (int penalty) { setPenalties (penalty, penalty); } void setPenalties (int penalty1, int penalty2); + // Rather for debugging: + inline int getPenalty (int i) { return penalty[i]; } + bool lineLoose (); bool lineTight (); bool lineTooTight (); @@ -218,6 +271,7 @@ private: bool lineCanBeBroken (int penaltyIndex); int compareTo (int penaltyIndex, BadnessAndPenalty *other); + void intoStringBuffer(lout::misc::StringBuffer *sb); void print (); }; @@ -236,6 +290,9 @@ private: static const char *hyphenDrawChar; + Textblock *containingBlock; + OutOfFlowMgr *outOfFlowMgr; + protected: /** * \brief Implementation used for words. @@ -251,7 +308,7 @@ protected: public: WordImgRenderer (Textblock *textblock, int wordNo); ~WordImgRenderer (); - + void setData (int xWordWidget, int lineNo); bool readyToDraw (); @@ -293,14 +350,18 @@ protected: int parMin; /* The sum of all word minima (plus spaces, hyphen width etc.) since the last possible break within this paragraph. */ + int parMinIntrinsic; int parMax; /* The sum of all word maxima in this - * paragraph (plus spaces, hyphen width - * etc.). */ + paragraph (plus spaces, hyphen width + etc.). */ + int parMaxIntrinsic; int maxParMin; /* Maximum of all paragraph minima (value of - * "parMin), including this paragraph. */ + "parMin"), including this paragraph. */ + int maxParMinIntrinsic; int maxParMax; /* Maximum of all paragraph maxima (value of - * "parMax"), including this paragraph. */ + "parMax""), including this paragraph. */ + int maxParMaxIntrinsic; }; struct Line @@ -308,14 +369,40 @@ protected: int firstWord; /* first word's index in word vector */ int lastWord; /* last word's index in word vector */ - /* "top" is always relative to the top of the first line, i.e. - * page->lines[0].top is always 0. */ - int top, boxAscent, boxDescent, contentAscent, contentDescent, - breakSpace, leftOffset; - /* This is similar to descent, but includes the bottom margins of the - * widgets within this line. */ - int marginDescent; + int top; /* "top" is always relative to the top + of the first line, i.e. + page->lines[0].top is always 0. */ + int marginAscent; /* Maximum of all total ascents + (including margin: hence the name) + of the words in this line. */ + int marginDescent; /* Maximum of all total decents + (including margin: hence the name) + of the words in this line. */ + int borderAscent; /* Maximum of all ascents minus margin + (but including padding and border: + hence the name) of the words in + this line. */ + int borderDescent; /* Maximum of all descents minus margin + (but including padding and border: + hence the name) of the words in + this line. */ + int contentAscent; /* ??? (depricated?) */ + int contentDescent; /* ??? (depricated?) */ + int breakSpace; /* Space between this line and the next one. */ + int textOffset; /* ??? (to be documented) */ + + /** + * \brief Returns the difference between two vertical lines + * positions: height of this line plus space below this + * line. The margin of the next line (marginAscent - + * borderAscent) must be passed seperately. + */ + inline int totalHeight (int marginNextLine) + { return borderAscent + borderDescent + // Collapsing of the margins of adjacent lines is done here: + + lout::misc::max (marginDescent - borderDescent, marginNextLine, + breakSpace); } /* Maximum of all line widths, including this line. Does not * include the last space, but the last hyphen width. Please @@ -323,6 +410,18 @@ protected: * changed line breaking), the values were accumulated up to the * last line, not this line.*/ int maxLineWidth; + + /* The word index of the last OOF reference (most importantly: + * float) whic is positioned before this line, or -1, if there + * is no OOF reference positioned before. + * + * **Important:** These references may still be part of this or + * even a following line, when positioned before (this is the + * reason this attribute exists); see \ref dw-out-of-flow. */ + int lastOofRefPositionedBeforeThisLine; + + int leftOffset, rightOffset; + enum { LEFT, RIGHT, CENTER } alignment; }; struct Word @@ -409,13 +508,14 @@ protected: class TextblockIterator: public core::Iterator { private: + bool oofm; int index; public: TextblockIterator (Textblock *textblock, core::Content::Type mask, bool atEnd); TextblockIterator (Textblock *textblock, core::Content::Type mask, - int index); + bool oofm, int index); lout::object::Object *clone(); int compareTo(lout::object::Comparable *other); @@ -425,14 +525,18 @@ protected: void highlight (int start, int end, core::HighlightLayer layer); void unhighlight (int direction, core::HighlightLayer layer); void getAllocation (int start, int end, core::Allocation *allocation); + void print (); }; friend class TextblockIterator; + // See sizeAllocateImpl for details. It is also used elsewhere. + core::Allocation childBaseAllocation; + /* These fields provide some ad-hoc-functionality, used by sub-classes. */ bool hasListitemValue; /* If true, the first word of the page is treated specially (search in source). */ - int innerPadding; /* This is an additional padding on the left side + int leftInnerPadding; /* This is an additional padding on the left side (used by ListItem). */ int line1Offset; /* This is an additional offset of the first line. May be negative (shift to left) or positive @@ -449,7 +553,7 @@ protected: * (which is used by DwTable!), and * (ii) line1_offset is ignored (line1_offset_eff is set to 0), * when line1_offset plus the width of the first word is - * greater than the the available witdh. + * greater than the the line break witdh. * * \todo Eliminate all these ad-hoc features by a new, simpler and * more elegant design. ;-) @@ -476,10 +580,35 @@ protected: int redrawY; int lastWordDrawn; - /* These values are set by set_... */ - int availWidth, availAscent, availDescent; + /* This value is (currently) set by setAscent(). */ + int lineBreakWidth; + + // Additional vertical offset, used for the "clear" attribute. + int verticalOffset; + + int wrapRefLines, wrapRefParagraphs; /* 0-based. Important: Both + are the line numbers, not + the value stored in + parentRef. */ + + // These four values are calculated by containingBlock->outOfFlowMgr + // (when defined; otherwise, they are false, or 0, respectively), for + // the newly constructed line, only when needed: when a new line is + // added, or if something in the line currently constucted has + // changed, e. g. a float has been added. + + bool newLineHasFloatLeft, newLineHasFloatRight; + int newLineLeftBorder, newLineRightBorder; /* As returned by + outOfFlowMgr->get...Border, + or 0, if outOfFlowMgr + is NULL */ + int newLineLeftFloatHeight, newLineRightFloatHeight; - int wrapRefLines, wrapRefParagraphs; /* [0 based] */ + // Ascent and descent of the newly constructed line, i. e. maximum + // of all words ascent/descent since the end of the last line. Not + // neccessary the ascent and descent of the newly added line, since + // not all words are added to it. + int newLineAscent, newLineDescent; lout::misc::SimpleVector <Line> *lines; lout::misc::SimpleVector <Paragraph> *paragraphs; @@ -487,21 +616,26 @@ protected: lout::misc::NotSoSimpleVector <Word> *words; lout::misc::SimpleVector <Anchor> *anchors; - struct {int index, nChar;} + struct { int index, nChar; } hlStart[core::HIGHLIGHT_NUM_LAYERS], hlEnd[core::HIGHLIGHT_NUM_LAYERS]; int hoverLink; /* The link under the mouse pointer */ - void queueDrawRange (int index1, int index2); + int calcVerticalBorder (int widgetPadding, int widgetBorder, + int widgetMargin, int lineBorderTotal, + int lineMarginTotal); void getWordExtremes (Word *word, core::Extremes *extremes); void justifyLine (Line *line, int diff); - Line *addLine (int firstWord, int lastWord, bool temporary); - void calcWidgetSize (core::Widget *widget, core::Requisition *size); + Line *addLine (int firstWord, int lastWord, int newLastOofPos, + bool temporary, int minHeight); void rewrap (); void fillParagraphs (); + void initNewLine (); + void calcBorders (int lastOofRef, int height); void showMissingLines (); void removeTemporaryLines (); + void setVerticalOffset (int verticalOffset); void decorateText (core::View *view, core::style::Style *style, core::style::Color::Shading shading, @@ -520,13 +654,18 @@ protected: int xWidget, int yWidgetBase); void drawLine (Line *line, core::View *view, core::Rectangle *area); int findLineIndex (int y); + int findLineIndexWhenNotAllocated (int y); + int findLineIndexWhenAllocated (int y); + int findLineIndex (int y, int ascent); int findLineOfWord (int wordIndex); int findParagraphOfWord (int wordIndex); Word *findWord (int x, int y, bool *inSpace); Word *addWord (int width, int ascent, int descent, short flags, core::style::Style *style); + void breakAdded (); void initWord (int wordNo); + void cleanupWord (int wordNo); void removeWordImgRenderer (int wordNo); void setWordImgRenderer (int wordNo); void removeSpaceImgRenderer (int wordNo); @@ -536,52 +675,47 @@ protected: void fillSpace (int wordNo, core::style::Style *style); void setBreakOption (Word *word, core::style::Style *style, int breakPenalty1, int breakPenalty2, bool forceBreak); - bool isBreakAllowed (Word *word); + bool isBreakAllowedInWord (Word *word) + { return isBreakAllowed (word->style); } + bool isBreakAllowed (core::style::Style *style); int textWidth (const char *text, int start, int len, core::style::Style *style, bool isStart, bool isEnd); void calcTextSize (const char *text, size_t len, core::style::Style *style, core::Requisition *size, bool isStart, bool isEnd); /** - * \brief Returns the x offset (the indentation plus any offset needed for - * centering or right justification) for the line. - * - * The offset returned is relative to the page *content* (i.e. without - * border etc.). + * Of nested text blocks, only the most inner one must regard the + * borders of floats. */ - inline int lineXOffsetContents (Line *line) + inline bool mustBorderBeRegarded (Line *line) { - return innerPadding + line->leftOffset + - (line == lines->getFirstRef() ? line1OffsetEff : 0); + return getTextblockForLine (line) == NULL; } - /** - * \brief Like lineXOffset, but relative to the allocation (i.e. - * including border etc.). - */ - inline int lineXOffsetWidget (Line *line) + inline bool mustBorderBeRegarded (int lineNo) { - return lineXOffsetContents (line) + getStyle()->boxOffsetX (); + return getTextblockForLine (lineNo) == NULL; } - inline int lineYOffsetWidgetAllocation (Line *line, - core::Allocation *allocation) + inline int _lineYOffsetWidgetAllocation (Line *line, + core::Allocation *allocation) { - return line->top + (allocation->ascent - lines->getRef(0)->boxAscent); + return line->top + (allocation->ascent - lines->getRef(0)->borderAscent); } inline int lineYOffsetWidget (Line *line) { - return lineYOffsetWidgetAllocation (line, &allocation); + return _lineYOffsetWidgetAllocation (line, &childBaseAllocation); } /** - * Like lineYOffsetCanvas, but with the allocation as parameter. + * Like lineYOffsetCanvas, but with the allocation as parameter. Rarely used + * outside of lineYOffsetCanvas. */ - inline int lineYOffsetCanvasAllocation (Line *line, - core::Allocation *allocation) + inline int _lineYOffsetCanvasAllocation (Line *line, + core::Allocation *allocation) { - return allocation->y + lineYOffsetWidgetAllocation(line, allocation); + return allocation->y + _lineYOffsetWidgetAllocation (line, allocation); } /** @@ -589,7 +723,7 @@ protected: */ inline int lineYOffsetCanvas (Line *line) { - return lineYOffsetCanvasAllocation(line, &allocation); + return _lineYOffsetCanvasAllocation (line, &childBaseAllocation); } inline int lineYOffsetWidgetI (int lineIndex) @@ -597,57 +731,98 @@ protected: return lineYOffsetWidget (lines->getRef (lineIndex)); } + inline int lineYOffsetWidgetIAllocation (int lineIndex, + core::Allocation *allocation) + { + return _lineYOffsetWidgetAllocation (lines->getRef (lineIndex), + allocation); + } + inline int lineYOffsetCanvasI (int lineIndex) { return lineYOffsetCanvas (lines->getRef (lineIndex)); } - + inline int calcPenaltyIndexForNewLine () { if (lines->size() == 0) return 0; - else - return - (words->getRef(lines->getLastRef()->lastWord)->flags & - (Word::DIV_CHAR_AT_EOL | Word::PERM_DIV_CHAR)) ? 1 : 0; + else { + Line *line = lines->getLastRef(); + if (line->firstWord <= line->lastWord) + return + (words->getRef(line->lastWord)->flags & + (Word::DIV_CHAR_AT_EOL | Word::PERM_DIV_CHAR)) ? 1 : 0; + else + // empty line + return 0; + } } + Textblock *getTextblockForLine (Line *line); + Textblock *getTextblockForLine (int lineNo); + Textblock *getTextblockForLine (int firstWord, int lastWord); + void printBorderChangedErrorAndAbort (int y, Widget *vloat, + int wrapLineIndex); + int yOffsetOfLineToBeCreated (); + bool sendSelectionEvent (core::SelectionState::EventType eventType, core::MousePositionEvent *event); - void accumulateWordExtremes (int firstWord, int lastWord, - int *maxOfMinWidth, int *sumOfMaxWidth); void processWord (int wordIndex); - virtual bool wordWrap (int wordIndex, bool wrapAll); + virtual int wordWrap (int wordIndex, bool wrapAll); + int wrapWordInFlow (int wordIndex, bool wrapAll); + void balanceBreakPosAndHeight (int wordIndex, int firstIndex, + int *searchUntil, bool tempNewLine, + int penaltyIndex, bool borderIsCalculated, + bool *thereWillBeMoreSpace, bool wrapAll, + int *diffWords, int *wordIndexEnd, + int *lastFloatPos, bool regardBorder, + int *height, int *breakPos); + int searchBreakPos (int wordIndex, int firstIndex, int *searchUntil, + bool tempNewLine, int penaltyIndex, + bool thereWillBeMoreSpace, bool wrapAll, + int *diffWords, int *wordIndexEnd, + int *addIndex1 = NULL); int searchMinBap (int firstWord, int lastWordm, int penaltyIndex, - bool correctAtEnd); + bool thereWillBeMoreSpace, bool correctAtEnd); int considerHyphenation (int firstIndex, int breakPos); bool isHyphenationCandidate (Word *word); + int calcLinePartHeight (int firstWord, int lastWord); void handleWordExtremes (int wordIndex); void correctLastWordExtremes (); static int getSpaceShrinkability(struct Word *word); static int getSpaceStretchability(struct Word *word); - static int getLineShrinkability(Word *lastWord); - static int getLineStretchability(Word *lastWord); - int hyphenateWord (int wordIndex); + int getLineShrinkability(int lastWordIndex); + int getLineStretchability(int lastWordIndex); + int hyphenateWord (int wordIndex, int *addIndex1 = NULL); + void moveWordIndices (int wordIndex, int num, int *addIndex1 = NULL); void accumulateWordForLine (int lineIndex, int wordIndex); void accumulateWordData (int wordIndex); - int calcAvailWidth (int lineIndex); + int calcLineBreakWidth (int lineIndex); void initLine1Offset (int wordIndex); void alignLine (int lineIndex); + void calcTextOffset (int lineIndex, int totalWidth); void sizeRequestImpl (core::Requisition *requisition); void getExtremesImpl (core::Extremes *extremes); void sizeAllocateImpl (core::Allocation *allocation); + int getAvailWidthOfChild (Widget *child, bool forceValue); + void containerSizeChangedForChildren (); + bool affectsSizeChangeContainerChild (Widget *child); + bool usesAvailWidth (); void resizeDrawImpl (); void markSizeChange (int ref); void markExtremesChange (int ref); - void setWidth (int width); - void setAscent (int ascent); - void setDescent (int descent); + + void notifySetAsTopLevel(); + void notifySetParent(); + + bool isBlockLevel (); + void draw (core::View *view, core::Rectangle *area); bool buttonPressImpl (core::EventButton *event); @@ -664,6 +839,20 @@ protected: core::style::Style *style, int numBreaks, int *breakPos, core::Requisition *wordSize); + static bool isContainingBlock (Widget *widget); + + inline bool mustBeWidenedToAvailWidth () { + DBG_OBJ_ENTER0 ("resize", 0, "mustBeWidenedToAvailWidth"); + bool toplevel = getParent () == NULL, + block = getStyle()->display == core::style::DISPLAY_BLOCK, + vloat = getStyle()->vloat != core::style::FLOAT_NONE, + result = toplevel || (block && !vloat); + DBG_OBJ_MSGF ("resize", 0, "=> %s (toplevel: %s, block: %s, float: %s)", + result ? "true" : "false", toplevel ? "true" : "false", + block ? "true" : "false", vloat ? "true" : "false"); + DBG_OBJ_LEAVE (); + return result; + } public: static int CLASS_ID; @@ -684,9 +873,9 @@ public: void addText (const char *text, size_t len, core::style::Style *style); inline void addText (const char *text, core::style::Style *style) - { - addText (text, strlen(text), style); - } + { addText (text, strlen(text), style); } + static bool isStyleOutOfFlow (core::style::Style *style) + { return OutOfFlowMgr::isStyleOutOfFlow (style); } void addWidget (core::Widget *widget, core::style::Style *style); bool addAnchor (const char *name, core::style::Style *style); void addSpace (core::style::Style *style); @@ -699,8 +888,84 @@ public: void changeLinkColor (int link, int newColor); void changeWordStyle (int from, int to, core::style::Style *style, bool includeFirstSpace, bool includeLastSpace); + + void borderChanged (int y, core::Widget *vloat); + void clearPositionChanged (); + void oofSizeChanged (bool extremesChanged); + inline int getLineBreakWidth () { return lineBreakWidth; } }; +#define DBG_SET_WORD_PENALTY(n, i, is) \ + D_STMT_START { \ + if (words->getRef(n)->badnessAndPenalty.getPenalty (i) == INT_MIN) \ + DBG_OBJ_ARRATTRSET_SYM ("words", n, "penalty." is, "-inf"); \ + else if (words->getRef(n)->badnessAndPenalty.getPenalty (i) == INT_MAX) \ + DBG_OBJ_ARRATTRSET_SYM ("words", n, "penalty." is, "inf"); \ + else \ + DBG_OBJ_ARRATTRSET_NUM ("words", n, "penalty." is, \ + words->getRef(n)->badnessAndPenalty \ + .getPenalty (i)); \ + } D_STMT_END + +#define DBG_SET_WORD(n) \ + D_STMT_START { \ + switch (words->getRef(n)->content.type) { \ + case ::dw::core::Content::TEXT: \ + DBG_OBJ_ARRATTRSET_SYM ("words", n, "type", "TEXT"); \ + DBG_OBJ_ARRATTRSET_STR ("words", n, "text/widget/breakSpace", \ + words->getRef(n)->content.text); \ + break; \ + case ::dw::core::Content::WIDGET_IN_FLOW: \ + DBG_OBJ_ARRATTRSET_SYM ("words", n, "type", "WIDGET_IN_FLOW"); \ + DBG_OBJ_ARRATTRSET_PTR ("words", n, "text/widget/breakSpace", \ + words->getRef(n)->content.widget); \ + break; \ + case ::dw::core::Content::WIDGET_OOF_REF: \ + DBG_OBJ_ARRATTRSET_SYM ("words", n, "type", "WIDGET_OOF_REF"); \ + DBG_OBJ_ARRATTRSET_PTR ("words", n, "text/widget/breakSpace", \ + words->getRef(n)->content.widget); \ + break; \ + case ::dw::core::Content::BREAK: \ + DBG_OBJ_ARRATTRSET_SYM ("words", n, "type", "BREAK"); \ + DBG_OBJ_ARRATTRSET_NUM ("words", n, "text/widget/breakSpace", \ + words->getRef(n)->content.breakSpace); \ + break; \ + default: \ + DBG_OBJ_ARRATTRSET_SYM ("words", n, "type", "???"); \ + DBG_OBJ_ARRATTRSET_SYM ("words", n, "text/widget/breakSpace", "???"); \ + } \ + DBG_SET_WORD_PENALTY (n, 0, "0"); \ + DBG_SET_WORD_PENALTY (n, 1, "1"); \ + } D_STMT_END + +#define DBG_MSG_WORD(aspect, prio, prefix, n, suffix) \ + D_STMT_START { \ + if ((n) < 0 || (n) >= words->size ()) \ + DBG_OBJ_MSG (aspect, prio, prefix "undefined (wrong index)" suffix); \ + else { \ + switch (words->getRef(n)->content.type) { \ + case ::dw::core::Content::TEXT: \ + DBG_OBJ_MSGF (aspect, prio, prefix "TEXT / \"%s\"" suffix, \ + words->getRef(n)->content.text); \ + break; \ + case ::dw::core::Content::WIDGET_IN_FLOW: \ + DBG_OBJ_MSGF (aspect, prio, prefix "WIDGET_IN_FLOW / %p" suffix, \ + words->getRef(n)->content.widget); \ + break; \ + case ::dw::core::Content::WIDGET_OOF_REF: \ + DBG_OBJ_MSGF (aspect, prio, prefix "WIDGET_OOF_REF / %p" suffix, \ + words->getRef(n)->content.widget); \ + break; \ + case ::dw::core::Content::BREAK: \ + DBG_OBJ_MSGF (aspect, prio, prefix "BREAK / %d" suffix, \ + words->getRef(n)->content.breakSpace); \ + break; \ + default: \ + DBG_OBJ_MSG (aspect, prio, prefix "??? / ???" suffix); \ + } \ + } \ + } D_STMT_END + } // namespace dw #endif // __DW_TEXTBLOCK_HH__ diff --git a/dw/textblock_iterator.cc b/dw/textblock_iterator.cc index 7531ecd5..b6423a29 100644 --- a/dw/textblock_iterator.cc +++ b/dw/textblock_iterator.cc @@ -35,20 +35,34 @@ Textblock::TextblockIterator::TextblockIterator (Textblock *textblock, bool atEnd): core::Iterator (textblock, mask, atEnd) { - index = atEnd ? textblock->words->size () : -1; + if (atEnd) { + if (textblock->outOfFlowMgr) { + oofm = true; + index = textblock->outOfFlowMgr->getNumWidgets(); + } else { + oofm = false; + index = textblock->words->size(); + } + } else { + oofm = false; + index = -1; + } + content.type = atEnd ? core::Content::END : core::Content::START; } Textblock::TextblockIterator::TextblockIterator (Textblock *textblock, core::Content::Type mask, - int index): + bool oofm, int index): core::Iterator (textblock, mask, false) { + this->oofm = oofm; this->index = index; + // TODO To be completely exact, oofm should be considered here. if (index < 0) content.type = core::Content::START; - else if (index >= textblock->words->size ()) + else if (index >= textblock->words->size()) content.type = core::Content::END; else content = textblock->words->getRef(index)->content; @@ -56,12 +70,20 @@ Textblock::TextblockIterator::TextblockIterator (Textblock *textblock, object::Object *Textblock::TextblockIterator::clone() { - return new TextblockIterator ((Textblock*)getWidget(), getMask(), index); + return + new TextblockIterator ((Textblock*)getWidget(), getMask(), oofm, index); } int Textblock::TextblockIterator::compareTo(object::Comparable *other) { - return index - ((TextblockIterator*)other)->index; + TextblockIterator *otherTI = (TextblockIterator*)other; + + if (oofm && !otherTI->oofm) + return +1; + else if (!oofm && otherTI->oofm) + return -1; + else + return index - otherTI->index; } bool Textblock::TextblockIterator::next () @@ -71,15 +93,52 @@ bool Textblock::TextblockIterator::next () if (content.type == core::Content::END) return false; + short type; + do { index++; - if (index >= textblock->words->size ()) { - content.type = core::Content::END; - return false; + + if (oofm) { + // Iterating over OOFM. + if (index >= textblock->outOfFlowMgr->getNumWidgets()) { + // End of OOFM list reached. + content.type = core::Content::END; + return false; + } + type = core::Content::WIDGET_OOF_CONT; + } else { + // Iterating over words list. + if (index < textblock->words->size ()) + // Still words left. + type = textblock->words->getRef(index)->content.type; + else { + // End of words list reached. + if (textblock->outOfFlowMgr) { + oofm = true; + index = 0; + if (textblock->outOfFlowMgr->getNumWidgets() > 0) + // Start with OOFM widgets. + type = core::Content::WIDGET_OOF_CONT; + else { + // No OOFM widgets (number is 0). + content.type = core::Content::END; + return false; + } + } else { + // No OOFM widgets (no OOFM agt all). + content.type = core::Content::END; + return false; + } + } } - } while ((textblock->words->getRef(index)->content.type & getMask()) == 0); + } while ((type & getMask()) == 0); + + if (oofm) { + content.type = core::Content::WIDGET_OOF_CONT; + content.widget = textblock->outOfFlowMgr->getWidget (index); + } else + content = textblock->words->getRef(index)->content; - content = textblock->words->getRef(index)->content; return true; } @@ -90,132 +149,225 @@ bool Textblock::TextblockIterator::prev () if (content.type == core::Content::START) return false; + short type; + do { index--; - if (index < 0) { - content.type = core::Content::START; - return false; + + if (oofm) { + // Iterating over OOFM. + if (index >= 0) + // Still widgets left. + type = core::Content::WIDGET_OOF_CONT; + else { + // Beginning of OOFM list reached. Continue with words. + oofm = false; + index = textblock->words->size() - 1; + if (index < 0) { + // There are no words. (Actually, this case should not + // happen: When there are OOF widgets, ther must be OOF + // references, or widgets in flow, which contain + // references. + content.type = core::Content::END; + return false; + } + type = textblock->words->getRef(index)->content.type; + } + } else { + // Iterating over words list. + if (index < 0) { + // Beginning of words list reached. + content.type = core::Content::START; + return false; + } + type = textblock->words->getRef(index)->content.type; } - } while ((textblock->words->getRef(index)->content.type & getMask()) == 0); + } while ((type & getMask()) == 0); + + if (oofm) { + content.type = core::Content::WIDGET_OOF_CONT; + content.type = false; + content.widget = textblock->outOfFlowMgr->getWidget (index); + } else + content = textblock->words->getRef(index)->content; - content = textblock->words->getRef(index)->content; return true; } void Textblock::TextblockIterator::highlight (int start, int end, core::HighlightLayer layer) { - Textblock *textblock = (Textblock*)getWidget(); - int index1 = index, index2 = index; - - int oldStartIndex = textblock->hlStart[layer].index; - int oldStartChar = textblock->hlStart[layer].nChar; - int oldEndIndex = textblock->hlEnd[layer].index; - int oldEndChar = textblock->hlEnd[layer].nChar; + if (!oofm) { + Textblock *textblock = (Textblock*)getWidget(); + int index1 = index, index2 = index; + + int oldStartIndex = textblock->hlStart[layer].index; + int oldStartChar = textblock->hlStart[layer].nChar; + int oldEndIndex = textblock->hlEnd[layer].index; + int oldEndChar = textblock->hlEnd[layer].nChar; + + if (textblock->hlStart[layer].index > textblock->hlEnd[layer].index) { + /* nothing is highlighted */ + textblock->hlStart[layer].index = index; + textblock->hlEnd[layer].index = index; + } - if (textblock->hlStart[layer].index > textblock->hlEnd[layer].index) { - /* nothing is highlighted */ - textblock->hlStart[layer].index = index; - textblock->hlEnd[layer].index = index; - } + if (textblock->hlStart[layer].index >= index) { + index2 = textblock->hlStart[layer].index; + textblock->hlStart[layer].index = index; + textblock->hlStart[layer].nChar = start; + } - if (textblock->hlStart[layer].index >= index) { - index2 = textblock->hlStart[layer].index; - textblock->hlStart[layer].index = index; - textblock->hlStart[layer].nChar = start; - } + if (textblock->hlEnd[layer].index <= index) { + index2 = textblock->hlEnd[layer].index; + textblock->hlEnd[layer].index = index; + textblock->hlEnd[layer].nChar = end; + } - if (textblock->hlEnd[layer].index <= index) { - index2 = textblock->hlEnd[layer].index; - textblock->hlEnd[layer].index = index; - textblock->hlEnd[layer].nChar = end; + if (oldStartIndex != textblock->hlStart[layer].index || + oldStartChar != textblock->hlStart[layer].nChar || + oldEndIndex != textblock->hlEnd[layer].index || + oldEndChar != textblock->hlEnd[layer].nChar) + textblock->queueDrawRange (index1, index2); } - if (oldStartIndex != textblock->hlStart[layer].index || - oldStartChar != textblock->hlStart[layer].nChar || - oldEndIndex != textblock->hlEnd[layer].index || - oldEndChar != textblock->hlEnd[layer].nChar) - textblock->queueDrawRange (index1, index2); + // TODO What about OOF widgets? } void Textblock::TextblockIterator::unhighlight (int direction, core::HighlightLayer layer) { - Textblock *textblock = (Textblock*)getWidget(); - int index1 = index, index2 = index; - - if (textblock->hlStart[layer].index > textblock->hlEnd[layer].index) - return; - - int oldStartIndex = textblock->hlStart[layer].index; - int oldStartChar = textblock->hlStart[layer].nChar; - int oldEndIndex = textblock->hlEnd[layer].index; - int oldEndChar = textblock->hlEnd[layer].nChar; - - if (direction == 0) { - index1 = textblock->hlStart[layer].index; - index2 = textblock->hlEnd[layer].index; - textblock->hlStart[layer].index = 1; - textblock->hlEnd[layer].index = 0; - } else if (direction > 0 && textblock->hlStart[layer].index <= index) { - index1 = textblock->hlStart[layer].index; - textblock->hlStart[layer].index = index + 1; - textblock->hlStart[layer].nChar = 0; - } else if (direction < 0 && textblock->hlEnd[layer].index >= index) { - index1 = textblock->hlEnd[layer].index; - textblock->hlEnd[layer].index = index - 1; - textblock->hlEnd[layer].nChar = INT_MAX; + if (!oofm) { + Textblock *textblock = (Textblock*)getWidget(); + int index1 = index, index2 = index; + + if (textblock->hlStart[layer].index > textblock->hlEnd[layer].index) + return; + + int oldStartIndex = textblock->hlStart[layer].index; + int oldStartChar = textblock->hlStart[layer].nChar; + int oldEndIndex = textblock->hlEnd[layer].index; + int oldEndChar = textblock->hlEnd[layer].nChar; + + if (direction == 0) { + index1 = textblock->hlStart[layer].index; + index2 = textblock->hlEnd[layer].index; + textblock->hlStart[layer].index = 1; + textblock->hlEnd[layer].index = 0; + } else if (direction > 0 && textblock->hlStart[layer].index <= index) { + index1 = textblock->hlStart[layer].index; + textblock->hlStart[layer].index = index + 1; + textblock->hlStart[layer].nChar = 0; + } else if (direction < 0 && textblock->hlEnd[layer].index >= index) { + index1 = textblock->hlEnd[layer].index; + textblock->hlEnd[layer].index = index - 1; + textblock->hlEnd[layer].nChar = INT_MAX; + } + + if (oldStartIndex != textblock->hlStart[layer].index || + oldStartChar != textblock->hlStart[layer].nChar || + oldEndIndex != textblock->hlEnd[layer].index || + oldEndChar != textblock->hlEnd[layer].nChar) + textblock->queueDrawRange (index1, index2); } - if (oldStartIndex != textblock->hlStart[layer].index || - oldStartChar != textblock->hlStart[layer].nChar || - oldEndIndex != textblock->hlEnd[layer].index || - oldEndChar != textblock->hlEnd[layer].nChar) - textblock->queueDrawRange (index1, index2); + // TODO What about OOF widgets? } void Textblock::TextblockIterator::getAllocation (int start, int end, core::Allocation *allocation) { Textblock *textblock = (Textblock*)getWidget(); - int lineIndex = textblock->findLineOfWord (index); - Line *line = textblock->lines->getRef (lineIndex); - Word *word = textblock->words->getRef (index); - allocation->x = - textblock->allocation.x + textblock->lineXOffsetWidget (line); + if (oofm) { + // TODO Consider start and end? + *allocation = + *(textblock->outOfFlowMgr->getWidget(index)->getAllocation()); + } else { + Word *word = textblock->words->getRef (index); + int firstWordOfLine, textOffset, lineYOffsetCanvas, lineBorderAscent; + + int lineIndex = textblock->findLineOfWord (index); + + // It may be that the line does not exist yet. + if (lineIndex != -1) { + // Line exists: simple. + Line *line = textblock->lines->getRef (lineIndex); + firstWordOfLine = line->firstWord; + textOffset = line->textOffset; + lineYOffsetCanvas = textblock->lineYOffsetCanvas (line); + lineBorderAscent = line->borderAscent; + } else { + // Line does not exist. Calculate the values in a similar way as in + // Textblock::addLine(). + Line *prevLine = textblock->lines->size () > 0 ? + textblock->lines->getLastRef () : NULL; + firstWordOfLine = prevLine ? prevLine->lastWord + 1 : 0; + + // The variable textOffset, defined below, is what Line::leftOffset + // will be for the next line; Line::textOffset itself cannot be + // calculated before the line is complete. + bool regardBorder = + textblock->mustBorderBeRegarded (textblock->lines->size ()); + textOffset = + misc::max (regardBorder ? textblock->newLineLeftBorder : 0, + textblock->boxOffsetX () + textblock->leftInnerPadding + + (textblock->lines->size () == 0 ? + textblock->line1OffsetEff : 0)); + + lineYOffsetCanvas = textblock->yOffsetOfLineToBeCreated (); + + lineBorderAscent = 0; + for (int i = firstWordOfLine; i < textblock->words->size (); i++) { + Word *w = textblock->words->getRef (i); + int borderAscent = + w->content.type == core::Content::WIDGET_IN_FLOW ? + w->size.ascent - w->content.widget->getStyle()->margin.top : + w->size.ascent; + lineBorderAscent = misc::max (lineBorderAscent, borderAscent); + } + } - for (int i = line->firstWord; i < index; i++) { - Word *w = textblock->words->getRef(i); - allocation->x += w->size.width + w->effSpace; - } - if (start > 0 && word->content.type == core::Content::TEXT) { - allocation->x += textblock->textWidth (word->content.text, 0, start, - word->style, - word->flags & Word::WORD_START, - (word->flags & Word::WORD_END) - && word->content.text[start] == 0); - } - allocation->y = textblock->lineYOffsetCanvas (line) + line->boxAscent - - word->size.ascent; - - allocation->width = word->size.width; - if (word->content.type == core::Content::TEXT) { - int wordEnd = strlen(word->content.text); - - if (start > 0 || end < wordEnd) { - end = misc::min(end, wordEnd); /* end could be INT_MAX */ - allocation->width = - textblock->textWidth (word->content.text, start, end - start, - word->style, - (word->flags & Word::WORD_START) - && start == 0, - (word->flags & Word::WORD_END) - && word->content.text[end] == 0); + allocation->x = textblock->allocation.x + textOffset; + for (int i = firstWordOfLine; i < index; i++) { + Word *w = textblock->words->getRef(i); + allocation->x += w->size.width + w->effSpace; + } + if (start > 0 && word->content.type == core::Content::TEXT) { + allocation->x += textblock->textWidth (word->content.text, 0, start, + word->style, + word->flags & Word::WORD_START, + (word->flags & Word::WORD_END) + && word->content.text[start] + == 0); + } + allocation->y = lineYOffsetCanvas + lineBorderAscent - word->size.ascent; + + allocation->width = word->size.width; + if (word->content.type == core::Content::TEXT) { + int wordEnd = strlen(word->content.text); + + if (start > 0 || end < wordEnd) { + end = misc::min(end, wordEnd); /* end could be INT_MAX */ + allocation->width = + textblock->textWidth (word->content.text, start, end - start, + word->style, + (word->flags & Word::WORD_START) + && start == 0, + (word->flags & Word::WORD_END) + && word->content.text[end] == 0); + } } + allocation->ascent = word->size.ascent; + allocation->descent = word->size.descent; } - allocation->ascent = word->size.ascent; - allocation->descent = word->size.descent; +} + +void Textblock::TextblockIterator::print () +{ + Iterator::print (); + printf (", oofm = %s, index = %d", oofm ? "true" : "false", index); + } } // namespace dw diff --git a/dw/textblock_linebreaking.cc b/dw/textblock_linebreaking.cc index 32e400fa..38d325bc 100644 --- a/dw/textblock_linebreaking.cc +++ b/dw/textblock_linebreaking.cc @@ -1,7 +1,7 @@ /* * Dillo Widget * - * Copyright 2005-2007, 2012-2013 Sebastian Geerken <sgeerken@dillo.org> + * Copyright 2005-2007, 2012-2014 Sebastian Geerken <sgeerken@dillo.org> * * (Parts of this file were originally part of textblock.cc.) * @@ -23,6 +23,7 @@ #include "textblock.hh" #include "hyphenator.hh" #include "../lout/msg.h" +#include "../lout/debug.hh" #include "../lout/misc.hh" #include <stdio.h> @@ -91,7 +92,7 @@ void Textblock::BadnessAndPenalty::calcBadness (int totalWidth, int idealWidth, badness = ratio * ratio * ratio; } } - } else { // if (totalWidth > availWidth) + } else { // if (totalWidth > idealWidth) if (totalShrinkability == 0) badnessState = TOO_TIGHT; else { @@ -186,67 +187,69 @@ int Textblock::BadnessAndPenalty::compareTo (int penaltyIndex, if (thisValue != otherValue) return thisValue - otherValue; } - + return 0; } void Textblock::BadnessAndPenalty::print () { + misc::StringBuffer sb; + intoStringBuffer(&sb); + printf ("%s", sb.getChars ()); +} + +void Textblock::BadnessAndPenalty::intoStringBuffer(misc::StringBuffer *sb) +{ switch (badnessState) { case NOT_STRETCHABLE: - printf ("not stretchable"); + sb->append ("not stretchable"); break; case TOO_TIGHT: - printf ("too tight"); + sb->append ("too tight"); break; case QUITE_LOOSE: - printf ("quite loose (ratio = %d)", ratio); + sb->append ("quite loose (ratio = "); + sb->appendInt (ratio); + sb->append (")"); break; case BADNESS_VALUE: - printf ("%d", badness); + sb->appendInt (badness); break; } #ifdef DEBUG - printf (" [%d + %d - %d vs. %d]", - totalWidth, totalStretchability, totalShrinkability, idealWidth); + sb->append (" ["); + sb->appendInt (totalWidth); + sb->append (" + "); + sb->appendInt (totalStretchability); + sb->append (" - "); + sb->appendInt (totalShrinkability); + sb->append (" vs. "); + sb->appendInt (idealWidth); + sb->append ("]"); #endif - printf (" + ("); + sb->append (" + ("); for (int i = 0; i < 2; i++) { if (penalty[i] == INT_MIN) - printf ("-inf"); + sb->append ("-inf"); else if (penalty[i] == INT_MAX) - printf ("inf"); + sb->append ("inf"); else - printf ("%d", penalty[i]); + sb->appendInt (penalty[i]); if (i == 0) - printf (", "); + sb->append (", "); } - printf (")"); + sb->append (")"); } void Textblock::printWordShort (Word *word) { - switch(word->content.type) { - case core::Content::TEXT: - printf ("\"%s\"", word->content.text); - break; - case core::Content::WIDGET: - printf ("<widget: %p (%s)>", - word->content.widget, word->content.widget->getClassName()); - break; - case core::Content::BREAK: - printf ("<break>"); - break; - default: - printf ("<?>"); - break; - } + core::Content::print (&(word->content)); } void Textblock::printWordFlags (short flags) @@ -287,8 +290,10 @@ void Textblock::printWord (Word *word) */ void Textblock::justifyLine (Line *line, int diff) { - /* To avoid rounding errors, the calculation is based on accumulated - * values. */ + DBG_OBJ_ENTER ("construct.line", 0, "justifyLine", "..., %d", diff); + + // To avoid rounding errors, the calculation is based on accumulated + // values. See doc/rounding-errors.doc. if (diff > 0) { int spaceStretchabilitySum = 0; @@ -306,8 +311,8 @@ void Textblock::justifyLine (Line *line, int diff) - spaceDiffCum; spaceDiffCum += spaceDiff; - PRINTF (" %d (of %d): diff = %d\n", i, words->size (), - spaceDiff); + DBG_OBJ_MSGF ("construct.line", 1, "%d (of %d): diff = %d", + i, words->size (), spaceDiff); word->effSpace = word->origSpace + spaceDiff; } @@ -328,89 +333,156 @@ void Textblock::justifyLine (Line *line, int diff) - spaceDiffCum; spaceDiffCum += spaceDiff; + DBG_OBJ_MSGF ("construct.line", 1, "%d (of %d): diff = %d", + i, words->size (), spaceDiff); + word->effSpace = word->origSpace + spaceDiff; } } } + + DBG_OBJ_LEAVE (); } Textblock::Line *Textblock::addLine (int firstWord, int lastWord, - bool temporary) + int newLastOofPos, bool temporary, + int minHeight) { - PRINTF ("[%p] ADD_LINE (%d, %d) => %d\n", - this, firstWord, lastWord, lines->size ()); + DBG_OBJ_ENTER ("construct.line", 0, "addLine", "%d, %d, %d, %s, %d", + firstWord, lastWord, newLastOofPos, + temporary ? "true" : "false", minHeight); + DBG_OBJ_MSGF ("construct.line", 0, "=> %d", lines->size ()); + + int lineWidth; + if (lastWord >= firstWord) { + DBG_MSG_WORD ("construct.line", 1, "<i>first word:</i> ", firstWord, ""); + DBG_MSG_WORD ("construct.line", 1, "<i>last word:</i> ", lastWord, ""); + + Word *lastWordOfLine = words->getRef(lastWord); + // Word::totalWidth includes the hyphen (which is what we want here). + lineWidth = lastWordOfLine->totalWidth; + DBG_OBJ_MSGF ("construct.line", 1, "lineWidth (from last word): %d", + lineWidth); + } else { + // empty line + lineWidth = 0; + DBG_OBJ_MSGF ("construct.line", 1, "lineWidth (empty line): %d", + lineWidth); + } - Word *lastWordOfLine = words->getRef(lastWord); - // Word::totalWidth includes the hyphen (which is what we want here). - int lineWidth = lastWordOfLine->totalWidth; // "lineWidth" is relative to leftOffset, so we may have to add // "line1OffsetEff" (remember: this is, for list items, negative). - if (lines->size () == 0) + if (lines->size () == 0) { lineWidth += line1OffsetEff; - - 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); + DBG_OBJ_MSGF ("construct.line", 1, "lineWidth (line1OffsetEff): %d", + lineWidth); + } lines->increase (); + DBG_OBJ_SET_NUM ("lines.size", lines->size ()); + 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) + if (lines->size () == 1 || nonTemporaryLines == lines->size () - 1) nonTemporaryLines = lines->size (); } - PRINTF ("nonTemporaryLines = %d\n", nonTemporaryLines); + DBG_OBJ_MSGF ("construct.line", 1, "nonTemporaryLines = %d", + 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; + + DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "firstWord", line->firstWord); + DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "lastWord", line->lastWord); + + line->borderAscent = line->contentAscent = 0; + line->borderDescent = line->contentDescent = 0; + line->marginAscent = 0; line->marginDescent = 0; line->breakSpace = 0; - line->leftOffset = 0; + + bool regardBorder = mustBorderBeRegarded (line); + line->leftOffset = misc::max (regardBorder ? newLineLeftBorder : 0, + boxOffsetX () + leftInnerPadding + + (lineIndex == 0 ? line1OffsetEff : 0)); + line->rightOffset = misc::max (regardBorder ? newLineRightBorder : 0, + boxRestWidth ()); + + DBG_OBJ_MSGF ("construct.line", 1, + "regardBorder = %s, newLineLeftBorder = %d, " + "newLineRightBorder = %d", + regardBorder ? "true" : "false", newLineLeftBorder, + newLineRightBorder); + + DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "leftOffset", line->leftOffset); + DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "rightOffset", + line->rightOffset); alignLine (lineIndex); + calcTextOffset (lineIndex, lineBreakWidth); + for (int i = line->firstWord; i < line->lastWord; i++) { Word *word = words->getRef (i); lineWidth += (word->effSpace - word->origSpace); + DBG_OBJ_MSGF ("construct.line", 1, + "lineWidth [corrected space (%d - %d) after word %d]: %d", + word->effSpace, word->origSpace, i, lineWidth); } - + if (lines->size () == 1) { // first line - line->top = 0; line->maxLineWidth = lineWidth; + line->lastOofRefPositionedBeforeThisLine = -1; } 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->lastOofRefPositionedBeforeThisLine = + prevLine->lastOofRefPositionedBeforeThisLine; } - + + DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "maxLineWidth", + line->maxLineWidth); + 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); + if (lines->size () == 1) + line->top = 0; + else { + // See comment in Line::totalHeight for collapsing of the + // margins of adjacent lines. + Line *prevLine = lines->getRef (lines->size () - 2); + line->top = prevLine->top + + prevLine->totalHeight (line->marginAscent - line->borderAscent); + } - PRINTF (" line[%d].maxLineWidth = %d\n", - lines->size () - 1, line->maxLineWidth); + DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "top", line->top); + + // Especially empty lines (possible when there are floats) have + // zero height, which may cause endless loops. For this reasons, + // the height should be positive (assuming the caller passed + // minHeight > 0). + line->borderAscent = misc::max (line->borderAscent, minHeight); + line->marginAscent = misc::max (line->marginAscent, minHeight); + + DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "borderAscent", + line->borderAscent); + DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "borderDescent", + line->borderDescent); + DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "marginAscent", + line->marginAscent); + DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "marginDescent", + line->marginDescent); + DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "contentAscent", + line->contentAscent); + DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "contentDescent", + line->contentDescent); mustQueueResize = true; @@ -421,7 +493,7 @@ Textblock::Line *Textblock::addLine (int firstWord, int lastWord, //words->getRef(line->lastWord)->badnessAndPenalty.print (); //printf ("\n"); - int xWidget = lineXOffsetWidget(line); + int xWidget = line->textOffset; for (int i = firstWord; i <= lastWord; i++) { Word *word = words->getRef (i); if (word->wordImgRenderer) @@ -430,71 +502,67 @@ Textblock::Line *Textblock::addLine (int firstWord, int lastWord, word->spaceImgRenderer->setData (xWidget, lines->size () - 1); xWidget += word->size.width + word->effSpace; } - - 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(). - // TODO: Again, index 1 is used for lineCanBeBroken(). See getExtremes(). - if (word->badnessAndPenalty.lineCanBeBroken (1) || 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"); + line->lastOofRefPositionedBeforeThisLine = + misc::max (line->lastOofRefPositionedBeforeThisLine, newLastOofPos); + DBG_OBJ_SET_NUM ("lastLine.lastOofRefPositionedBeforeThisLine", + line->lastOofRefPositionedBeforeThisLine); - //printf ("[%p] (%d / %d) => parMin = %d, maxOfMinWidth = %d\n", - // this, extremes.minWidth, extremes.maxWidth, parMin, - // *maxOfMinWidth); + initNewLine (); - *sumOfMaxWidth += (extremes.maxWidth + word->origSpace); - // Notice that the last space is added. See also: Line::parMax. - } + DBG_OBJ_LEAVE (); + return line; } void Textblock::processWord (int wordIndex) { - bool wordListChanged = wordWrap (wordIndex, false); + DBG_OBJ_ENTER ("construct.all", 0, "processWord", "%d", wordIndex); + DBG_MSG_WORD ("construct.all", 1, "<i>processed word:</i>", wordIndex, ""); - if (wordListChanged) { + int diffWords = wordWrap (wordIndex, false); + + if (diffWords == 0) + handleWordExtremes (wordIndex); + else { // If wordWrap has called hyphenateWord here, this has an effect // on the call of handleWordExtremes. To avoid adding values // more than one time (original un-hyphenated word, plus all // parts of the hyphenated word, except the first one), the // whole paragraph is recalculated again. + // + // (Note: the hyphenated word is often *before* wordIndex, and + // it may be even more than one word, which makes it nearly + // impossible to reconstruct what has happend. Therefore, there + // is no simpler approach to handle this.) + + DBG_OBJ_MSGF ("construct.paragraph", 1, + "word list has become longer by %d", diffWords); + DBG_MSG_WORD ("construct.all", 1, "<i>processed word now:</i>", + wordIndex, ""); int firstWord; if (paragraphs->size() > 0) { firstWord = paragraphs->getLastRef()->firstWord; paragraphs->setSize (paragraphs->size() - 1); + DBG_OBJ_MSG ("construct.paragraph", 1, "removing last paragraph"); } else firstWord = 0; - for (int i = firstWord; i <= wordIndex - 1; i++) + int lastIndex = wordIndex + diffWords; + DBG_OBJ_MSGF ("construct.paragraph", 1, + "processing words again from %d to %d", + firstWord, lastIndex); + + // Furthermore, some more words have to be processed, so we + // iterate until wordIndex + diffWords, not only + // wordIndex. + DBG_OBJ_MSG_START (); + for (int i = firstWord; i <= lastIndex; i++) handleWordExtremes (i); + DBG_OBJ_MSG_END (); } - handleWordExtremes (wordIndex); + DBG_OBJ_LEAVE (); } /* @@ -505,65 +573,136 @@ void Textblock::processWord (int wordIndex) * Returns whether the words list has changed at, or before, the word * index. */ -bool Textblock::wordWrap (int wordIndex, bool wrapAll) +int Textblock::wordWrap (int wordIndex, bool wrapAll) { - PRINTF ("[%p] WORD_WRAP (%d, %s)\n", - this, wordIndex, wrapAll ? "true" : "false"); - - Word *word; - bool wordListChanged = false; + DBG_OBJ_ENTER ("construct.word", 0, "wordWrap", "%d, %s", + wordIndex, wrapAll ? "true" : "false"); + DBG_MSG_WORD ("construct.word", 1, "<i>wrapped word:</i> ", wordIndex, ""); if (!wrapAll) removeTemporaryLines (); initLine1Offset (wordIndex); - word = words->getRef (wordIndex); + Word *word = words->getRef (wordIndex); word->effSpace = word->origSpace; accumulateWordData (wordIndex); - //printf (" "); - //printWord (word); - //printf ("\n"); + int n; + if (word->content.type == core::Content::WIDGET_OOF_REF) + n = 0; + else + n = wrapWordInFlow (wordIndex, wrapAll); + + DBG_OBJ_MSGF ("construct.word", 1, "=> %d", n); + DBG_OBJ_LEAVE (); + + return n; +} + +int Textblock::wrapWordInFlow (int wordIndex, bool wrapAll) +{ + DBG_OBJ_ENTER ("construct.word", 0, "wrapWordInFlow", "%d, %s", + wordIndex, wrapAll ? "true" : "false"); + + Word *word = words->getRef (wordIndex); + int diffWords = 0; int penaltyIndex = calcPenaltyIndexForNewLine (); bool newLine; do { + // This variable, thereWillBeMoreSpace, is set to true, if, due + // to floats, this line is smaller than following lines will be + // (and, at the end, there will be surely lines without + // floats). If this is the case, lines may, in an extreme case, + // be left empty. + + // (In other cases, lines are never left empty, even if this means + // that the contents is wider than the line break width. Leaving + // lines empty does not make sense without floats, since there will + // be no possibility with more space anymore.) + + bool regardBorder = mustBorderBeRegarded (lines->size ()); + bool thereWillBeMoreSpace = regardBorder ? + newLineHasFloatLeft || newLineHasFloatRight : false; + + DBG_OBJ_MSGF ("construct.word", 1, + "thereWillBeMoreSpace = %s ? %s || %s : false = %s", + regardBorder ? "true" : "false", + newLineHasFloatLeft ? "true" : "false", + newLineHasFloatRight ? "true" : "false", + thereWillBeMoreSpace ? "true" : "false"); + + bool tempNewLine = false; int firstIndex = lines->size() == 0 ? 0 : lines->getLastRef()->lastWord + 1; int searchUntil; - if (wrapAll && wordIndex >= firstIndex && wordIndex == words->size() -1) { + if (wordIndex < firstIndex) + // Current word is already part of a line (ending with + // firstIndex - 1), so no new line has to be added. + newLine = false; + else if (wrapAll && wordIndex >= firstIndex && + wordIndex == words->size() -1) { newLine = true; searchUntil = wordIndex; tempNewLine = true; - PRINTF (" NEW LINE: last word\n"); + DBG_OBJ_MSG ("construct.word", 1, "<b>new line:</b> last word"); } else if (wordIndex >= firstIndex && // TODO: lineMustBeBroken should be independent of // the penalty index? word->badnessAndPenalty.lineMustBeBroken (penaltyIndex)) { newLine = true; searchUntil = wordIndex; - PRINTF (" NEW LINE: forced break\n"); + DBG_OBJ_MSG ("construct.word", 1, "<b>new line:</b> forced break"); } else { // Break the line when too tight, but only when there is a // possible break point so far. (TODO: I've forgotten the // original bug which is fixed by this.) + + // Exception of the latter rule: thereWillBeMoreSpace; see + // above, where it is defined. + + DBG_OBJ_MSGF ("construct.word", 1, + "possible line break between %d and %d?", + firstIndex, wordIndex - 1); + DBG_OBJ_MSG_START (); + bool possibleLineBreak = false; - for (int i = firstIndex; !possibleLineBreak && i <= wordIndex - 1; i++) + for (int i = firstIndex; + !(thereWillBeMoreSpace || possibleLineBreak) + && i <= wordIndex - 1; + i++) { + DBG_OBJ_MSGF ("construct.word", 2, "examining word %d", i); if (words->getRef(i)->badnessAndPenalty - .lineCanBeBroken (penaltyIndex)) + .lineCanBeBroken (penaltyIndex)) { + DBG_MSG_WORD ("construct.word", 2, "break possible for word:", + i, ""); possibleLineBreak = true; + } + } - if (possibleLineBreak && word->badnessAndPenalty.lineTooTight ()) { + DBG_OBJ_MSG_END (); + DBG_OBJ_MSGF ("construct.word", 1, "=> %s", + possibleLineBreak ? "true" : "false"); + + DBG_OBJ_MSGF ("construct.word", 1, "word->... too tight: %s", + word->badnessAndPenalty.lineTooTight () ? + "true" : "false"); + + if ((thereWillBeMoreSpace || possibleLineBreak) + && word->badnessAndPenalty.lineTooTight ()) { newLine = true; searchUntil = wordIndex - 1; - PRINTF (" NEW LINE: line too tight\n"); - } else + DBG_OBJ_MSG ("construct.word", 1, + "<b>new line:</b> line too tight"); + } else { + DBG_OBJ_MSG ("construct.word", 1, "no <b>new line</b>"); newLine = false; + } } if(!newLine && !wrapAll) @@ -575,65 +714,117 @@ bool Textblock::wordWrap (int wordIndex, bool wrapAll) // newLine is calculated as "true". mustQueueResize = true; - if(newLine) { + PRINTF ("[%p] special case? newLine = %s, wrapAll = %s => " + "mustQueueResize = %s\n", this, newLine ? "true" : "false", + wrapAll ? "true" : "false", mustQueueResize ? "true" : "false"); + + if (newLine) { accumulateWordData (wordIndex); + int wordIndexEnd = wordIndex; + int height = 1; // assumed by calcBorders before (see there) + int breakPos; + int lastFloatPos = lines->size() > 0 ? + lines->getLastRef()->lastOofRefPositionedBeforeThisLine : -1; + DBG_OBJ_MSGF ("construct.word", 2, "lastFloatPos = %d", lastFloatPos); + + balanceBreakPosAndHeight (wordIndex, firstIndex, &searchUntil, + tempNewLine, penaltyIndex, true, + &thereWillBeMoreSpace, wrapAll, + &diffWords, &wordIndexEnd, + &lastFloatPos, regardBorder, &height, + &breakPos); + + bool floatHandled; + int yNewLine = yOffsetOfLineToBeCreated (); - bool lineAdded; do { - int breakPos = - searchMinBap (firstIndex, searchUntil, penaltyIndex, wrapAll); - int hyphenatedWord = considerHyphenation (firstIndex, breakPos); - - //printf ("[%p] breakPos = %d (", this, breakPos); - //printWordShort (words->getRef (breakPos)); - //printf ("), hyphenatedWord = %d", hyphenatedWord); - //if (hyphenatedWord != -1) { - // printf (" ("); - // printWordShort (words->getRef (hyphenatedWord)); - // printf (")"); - //} - //printf ("\n"); - - 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; - penaltyIndex = calcPenaltyIndexForNewLine (); - } else { - // TODO hyphenateWord() should return whether something has - // changed at all. So that a second run, with - // !word->canBeHyphenated, is unnecessary. - // 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); - - if (n > 0 && hyphenatedWord <= wordIndex) - wordListChanged = true; + DBG_OBJ_MSG ("construct.word", 1, "<i>floatHandled loop cycle</i>"); + DBG_OBJ_MSG_START (); + + DBG_OBJ_MSGF ("construct.word", 2, + "breakPos = %d, height = %d, lastFloatPos = %d", + breakPos, height, lastFloatPos); + + int startSearch = misc::max (firstIndex, lastFloatPos + 1); + int newFloatPos = -1; + + // Step 1: search for the next float. + DBG_OBJ_MSGF ("construct.word", 2, "searching from %d to %d", + startSearch, breakPos); + for (int i = startSearch; newFloatPos == -1 && i <= breakPos; i++) { + core::Content *content = &(words->getRef(i)->content); + if (content->type == core::Content::WIDGET_OOF_REF && + // Later, absolutepositioned elements (which do not affect + // borders) can be ignored at this point. + (containingBlock->outOfFlowMgr->affectsLeftBorder + (content->widget) || + containingBlock->outOfFlowMgr->affectsRightBorder + (content->widget))) + newFloatPos = i; } - - 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); + DBG_OBJ_MSGF ("construct.word", 2, "newFloatPos = %d", newFloatPos); + + if (newFloatPos == -1) + floatHandled = false; + else { + floatHandled = true; + + // Step 2: position the float and re-calculate the line. + lastFloatPos = newFloatPos; + + containingBlock->outOfFlowMgr->tellPosition + (words->getRef(lastFloatPos)->content.widget, yNewLine); + + balanceBreakPosAndHeight (wordIndex, firstIndex, &searchUntil, + tempNewLine, penaltyIndex, false, + &thereWillBeMoreSpace, wrapAll, + &diffWords, &wordIndexEnd, + &lastFloatPos, regardBorder, &height, + &breakPos); + } + + DBG_OBJ_MSG_END (); + } while (floatHandled); + + int minHeight; + if (firstIndex <= breakPos) + // Not an empty line: calculate line height from contents. + minHeight = 1; + else { + // Empty line. Too avoid too many lines one pixel high, we + // use the float heights. + if (newLineHasFloatLeft && newLineHasFloatRight) + minHeight = misc::max (misc::min (newLineLeftFloatHeight, + newLineRightFloatHeight), + 1); + else if (newLineHasFloatLeft && !newLineHasFloatRight) + minHeight = misc::max (newLineLeftFloatHeight, 1); + else if (!newLineHasFloatLeft && newLineHasFloatRight) + minHeight = misc::max (newLineRightFloatHeight, 1); + else + // May this happen? + minHeight = 1; + } + + addLine (firstIndex, breakPos, lastFloatPos, tempNewLine, minHeight); + + DBG_OBJ_MSGF ("construct.word", 1, + "accumulating again from %d to %d\n", + breakPos + 1, wordIndexEnd); + for(int i = breakPos + 1; i <= wordIndexEnd; i++) + accumulateWordData (i); + + // update word pointer as hyphenateWord() can trigger a + // reorganization of the words structure + word = words->getRef (wordIndex); + + penaltyIndex = calcPenaltyIndexForNewLine (); } } while (newLine); - if(word->content.type == core::Content::WIDGET) { + if(word->content.type == core::Content::WIDGET_IN_FLOW) { // Set parentRef for the child, when necessary. // // parentRef is set for the child already, when a line is @@ -648,43 +839,259 @@ bool Textblock::wordWrap (int wordIndex, bool wrapAll) firstWordWithoutLine = 0; else firstWordWithoutLine = lines->getLastRef()->lastWord + 1; - + if (wordIndex >= firstWordWithoutLine) { - word->content.widget->parentRef = lines->size (); - PRINTF ("The %s %p is assigned parentRef = %d.\n", - word->content.widget->getClassName(), word->content.widget, - word->content.widget->parentRef); + word->content.widget->parentRef = + OutOfFlowMgr::createRefNormalFlow (lines->size ()); + DBG_OBJ_SET_NUM_O (word->content.widget, "parentRef", + word->content.widget->parentRef); + } + } + + DBG_OBJ_LEAVE (); + + return diffWords; +} + +// *height must be initialized, but not *breakPos. +// *wordIndexEnd must be initialized (initially to wordIndex) +void Textblock::balanceBreakPosAndHeight (int wordIndex, int firstIndex, + int *searchUntil, bool tempNewLine, + int penaltyIndex, + bool borderIsCalculated, + bool *thereWillBeMoreSpace, + bool wrapAll, int *diffWords, + int *wordIndexEnd, int *lastFloatPos, + bool regardBorder, int *height, + int *breakPos) +{ + DBG_OBJ_ENTER ("construct.word", 0, "balanceBreakPosAndHeight", + "%d, %d. %d, %s, %d, %s, ..., %s, ..., %d, %s, %d, ...", + wordIndex, firstIndex, *searchUntil, + tempNewLine ? "true" : "false", penaltyIndex, + borderIsCalculated ? "true" : "false", + wrapAll ? "true" : "false", *lastFloatPos, + regardBorder ? "true" : "false", *height); + + // The height of this part of the line (until the new break + // position) may change with the break position, but the break + // position may depend on the height. We try to let these values + // converge. + // + // The height, as a function of the break position, is + // monotonically (but not strictly) increasing, since more words + // may make the line higher (but not flatter). The break position, + // as a function of the height, is, however, monotonically (but not + // strictly) *de*creasing, since flatter lines may fit easier + // between floats (although this is a rare case). So a convergence + // is not necessary. + // + // For this reason, we iterate only as long as the height does not + // increase again, and stop if it remains the same. As the minimum + // is 1, this approach will force the iteration to stop. + // + // (As a side effect, this will lead to a larger break position, + // and so place as much words as possible in the line.) + + int runNo = 1; + while (true) { + if (!(borderIsCalculated && runNo == 1)) { + // borderIsCalculated is, of course, only valid in the first run + calcBorders (*lastFloatPos, *height); + *thereWillBeMoreSpace = regardBorder ? + newLineHasFloatLeft || newLineHasFloatRight : false; + + for(int i = firstIndex; i <= *wordIndexEnd; i++) + accumulateWordData (i); + } + + DBG_OBJ_MSGF ("construct.word", 1, "thereWillBeMoreSpace = %s", + *thereWillBeMoreSpace ? "true" : "false"); + + int newBreakPos = + searchBreakPos (wordIndex, firstIndex, searchUntil, + tempNewLine, penaltyIndex, *thereWillBeMoreSpace, + wrapAll, diffWords, wordIndexEnd, lastFloatPos); + int newHeight = calcLinePartHeight (firstIndex, newBreakPos); + + DBG_OBJ_MSGF ("construct.word", 1, + "runNo = %d, newBreakPos = %d, newHeight = %d", + runNo, newBreakPos, newHeight); + if (runNo == 1) + DBG_OBJ_MSGF ("construct.word", 1, + "old: height = %d, breakPos undefined", *height); + else + DBG_OBJ_MSGF ("construct.word", 1, + "old: height = %d, breakPos = %d", *height, *breakPos); + + if (runNo != 1 /* Since *some* value are needed, the results + from the first run are never discarded. */ + && newHeight >= *height) { + if (newHeight == *height) { + // newHeight == height: convergence, stop here. The new break + // position is, nevertheless, adopted. + DBG_OBJ_MSG ("construct.word", 1, "stopping, adopting new values"); + *breakPos = newBreakPos; + } else + // newHeight > height: do not proceed, discard new values, + // which are less desirable than the old ones (see above). + DBG_OBJ_MSG ("construct.word", 1, + "stopping, discarding new values"); + break; + } else { + DBG_OBJ_MSG ("construct.word", 1, "adopting new values, continuing"); + *height = newHeight; + *breakPos = newBreakPos; } + + runNo++; } - return wordListChanged; + DBG_OBJ_LEAVE (); +} + +// *wordIndexEnd must be initialized (initially to wordIndex) +int Textblock::searchBreakPos (int wordIndex, int firstIndex, int *searchUntil, + bool tempNewLine, int penaltyIndex, + bool thereWillBeMoreSpace, bool wrapAll, + int *diffWords, int *wordIndexEnd, + int *addIndex1) +{ + DBG_OBJ_ENTER ("construct.word", 0, "searchBreakPos", + "%d, %d. %d, %s, %d, %s, %s, ...", + wordIndex, firstIndex, *searchUntil, + tempNewLine ? "true" : "false", penaltyIndex, + thereWillBeMoreSpace ? "true" : "false", + wrapAll ? "true" : "false"); + DBG_MSG_WORD ("construct.word", 0, "<i>first word:</i> ", firstIndex, ""); + + int result; + bool lineAdded; + + do { + DBG_OBJ_MSG ("construct.word", 1, "<i>searchBreakPos loop cycle</i>"); + DBG_OBJ_MSG_START (); + + if (firstIndex > *searchUntil) { + // empty line + DBG_OBJ_MSG ("construct.word", 1, "empty line"); + assert (*searchUntil == firstIndex - 1); + result = firstIndex - 1; + lineAdded = true; + } else if (thereWillBeMoreSpace && + words->getRef(firstIndex)->badnessAndPenalty.lineTooTight ()) { + int hyphenatedWord = considerHyphenation (firstIndex, firstIndex); + DBG_OBJ_MSGF ("construct.word", 1, "too tight ... hyphenatedWord = %d", + hyphenatedWord); + + if (hyphenatedWord == -1) { + DBG_OBJ_MSG ("construct.word", 1, "... => empty line"); + result = firstIndex - 1; + lineAdded = true; + } else { + DBG_OBJ_MSG ("construct.word", 1, + "... => hyphenate word and try again"); + int n = hyphenateWord (hyphenatedWord, addIndex1); + *searchUntil += n; + if (hyphenatedWord <= wordIndex) + *wordIndexEnd += n; + DBG_OBJ_MSGF ("construct.word", 1, "new searchUntil = %d", + *searchUntil); + + lineAdded = false; + } + } else { + DBG_OBJ_MSG ("construct.word", 1, "non-empty line"); + + int breakPos = + searchMinBap (firstIndex, *searchUntil, penaltyIndex, + thereWillBeMoreSpace, wrapAll); + int hyphenatedWord = considerHyphenation (firstIndex, breakPos); + + DBG_OBJ_MSGF ("construct.word", 1, "breakPos = %d", breakPos); + DBG_MSG_WORD ("construct.word", 1, "<i>break at word:</i> ", + breakPos, ""); + DBG_OBJ_MSGF ("construct.word", 1, "hyphenatedWord = %d", + hyphenatedWord); + if (hyphenatedWord != -1) + DBG_MSG_WORD ("construct.word", 1, + "<i>hyphenate at word:</i> ", + hyphenatedWord, ""); + + if(hyphenatedWord == -1) { + result = breakPos; + lineAdded = true; + } else { + // TODO hyphenateWord() should return whether something + // has changed at all. So that a second run, with + // !word->canBeHyphenated, is unnecessary. + // TODO Update: The return value of hyphenateWord() should + // be checked. + DBG_OBJ_MSGF ("construct.word", 1, "old searchUntil = %d", + *searchUntil); + int n = hyphenateWord (hyphenatedWord, addIndex1); + *searchUntil += n; + if (hyphenatedWord <= wordIndex) + *wordIndexEnd += n; + DBG_OBJ_MSGF ("construct.word", 1, "new searchUntil = %d", + *searchUntil); + lineAdded = false; + + if (hyphenatedWord <= wordIndex) + *diffWords += n; + + DBG_OBJ_MSGF ("construct.word", 1, + "accumulating again from %d to %d\n", + breakPos + 1, *wordIndexEnd); + for(int i = breakPos + 1; i <= *wordIndexEnd; i++) + accumulateWordData (i); + } + } + + DBG_OBJ_MSG_END (); + } while(!lineAdded); + + DBG_OBJ_MSGF ("construct.word", 1, "=> %d", result); + DBG_OBJ_LEAVE (); + + return result; } int Textblock::searchMinBap (int firstWord, int lastWord, int penaltyIndex, - bool correctAtEnd) + bool thereWillBeMoreSpace, bool correctAtEnd) { - PRINTF (" searching from %d to %d\n", firstWord, lastWord); + DBG_OBJ_ENTER ("construct.word", 0, "searchMinBap", "%d, %d, %d, %s, %s", + firstWord, lastWord, penaltyIndex, + thereWillBeMoreSpace ? "true" : "false", + correctAtEnd ? "true" : "false"); int pos = -1; + DBG_OBJ_MSG_START (); for (int i = firstWord; i <= lastWord; i++) { Word *w = words->getRef(i); - - //printf (" %d (of %d): ", i, words->size ()); - //printWord (w); - //printf ("\n"); - + + DBG_IF_RTFL { + misc::StringBuffer sb; + w->badnessAndPenalty.intoStringBuffer (&sb); + DBG_OBJ_MSGF ("construct.word", 2, "%d (of %d): b+p: %s", + i, words->size (), sb.getChars ()); + DBG_MSG_WORD ("construct.word", 2, "(<i>i. e.:</i> ", i, ")"); + } + + // "<=" instead of "<" in the next lines (see also + // "correctedBap.compareTo ...) tends to result in more words + // per line -- theoretically. Practically, the case "==" will + // never occur. if (pos == -1 || w->badnessAndPenalty.compareTo (penaltyIndex, &words->getRef(pos) ->badnessAndPenalty) <= 0) - // "<=" instead of "<" in the next lines tends to result in - // more words per line -- theoretically. Practically, the - // case "==" will never occur. pos = i; } + DBG_OBJ_MSG_END (); - PRINTF (" found at %d\n", pos); + DBG_OBJ_MSGF ("construct.word", 1, "found at %d\n", pos); if (correctAtEnd && lastWord == words->size () - 1) { // Since no break and no space is added, the last word will have @@ -692,28 +1099,31 @@ int Textblock::searchMinBap (int firstWord, int lastWord, int penaltyIndex, // the last word. However, since more words may follow, the // penalty is not changed, but here, the search is corrected // (maybe only temporary). - + // (Notice that it was once (temporally) set to -inf, not 0, but // this will make e.g. test/table-1.html not work.) Word *w = words->getRef (lastWord); BadnessAndPenalty correctedBap = w->badnessAndPenalty; correctedBap.setPenalty (0); - //printf (" corrected bap: "); - //correctedBap.print (); - //printf ("\n"); + DBG_IF_RTFL { + misc::StringBuffer sb; + correctedBap.intoStringBuffer (&sb); + DBG_OBJ_MSGF ("construct.word", 1, "corrected b+p: %s", + sb.getChars ()); + } if (correctedBap.compareTo(penaltyIndex, &words->getRef(pos)->badnessAndPenalty) <= 0) { pos = lastWord; - PRINTF (" corrected => %d\n", pos); + DBG_OBJ_MSGF ("construct.word", 1, "corrected: %d\n", pos); } } - + + DBG_OBJ_LEAVE (); return pos; } - /** * Suggest a word to hyphenate, when breaking at breakPos is * planned. Return a word index or -1, when hyphenation makes no @@ -733,7 +1143,7 @@ int Textblock::considerHyphenation (int firstIndex, int breakPos) if (wordBreak->badnessAndPenalty.lineTight ()) { // Sometimes, it is not the last word, which must be hyphenated, // but some word before. Here, we search for the first word - // which can be hyphenated, *and* makes the line too tight. + // which can be hyphenated, *and* makes the line too tight. for (int i = breakPos; i >= firstIndex; i--) { Word *word1 = words->getRef (i); if (word1->badnessAndPenalty.lineTight () && @@ -758,12 +1168,25 @@ bool Textblock::isHyphenationCandidate (Word *word) { return (word->flags & Word::CAN_BE_HYPHENATED) && word->style->x_lang[0] && - isBreakAllowed(word) && + isBreakAllowedInWord (word) && word->content.type == core::Content::TEXT && Hyphenator::isHyphenationCandidate (word->content.text); } +int Textblock::calcLinePartHeight (int firstWord, int lastWord) +{ + int ascent = 0, descent = 0; + + for (int i = firstWord; i <= lastWord; i++) { + Word *word = words->getRef (i); + ascent = misc::max (ascent, word->size.ascent); + descent = misc::max (descent, word->size.descent); + } + + return misc::max (ascent + descent, 1); +} + /** * Counter part to wordWrap(), but for extremes, not size calculation. */ @@ -771,17 +1194,26 @@ void Textblock::handleWordExtremes (int wordIndex) { // TODO Overall, clarify penalty index. + DBG_OBJ_ENTER ("construct.paragraph", 0, "handleWordExtremes", "%d", + wordIndex); + + initLine1Offset (wordIndex); + Word *word = words->getRef (wordIndex); + DBG_MSG_WORD ("construct.paragraph", 1, + "<i>handled word:</i> ", wordIndex, ""); + core::Extremes wordExtremes; getWordExtremes (word, &wordExtremes); - - //printf ("[%p] HANDLE_WORD_EXTREMES (%d): ", this, wordIndex); - //printWordWithFlags (word); - //printf (" => %d / %d\n", wordExtremes.minWidth, wordExtremes.maxWidth); + DBG_OBJ_MSGF ("construct.paragraph", 1, "extremes: %d (%d) / %d (%d)", + wordExtremes.minWidth, wordExtremes.minWidthIntrinsic, + wordExtremes.maxWidth, wordExtremes.maxWidthIntrinsic); if (wordIndex == 0) { - wordExtremes.minWidth += line1Offset; - wordExtremes.maxWidth += line1Offset; + wordExtremes.minWidth += line1OffsetEff; + wordExtremes.minWidthIntrinsic += line1OffsetEff; + wordExtremes.maxWidth += line1OffsetEff; + wordExtremes.maxWidthIntrinsic += line1OffsetEff; } if (paragraphs->size() == 0 || @@ -794,18 +1226,24 @@ void Textblock::handleWordExtremes (int wordIndex) Paragraph *par = paragraphs->getLastRef(); par->firstWord = par->lastWord = wordIndex; - par->parMin = par->parMax = 0; + par->parMin = par->parMinIntrinsic = par->parMax = par->parMaxIntrinsic + = 0; if (prevPar) { par->maxParMin = prevPar->maxParMin; + par->maxParMinIntrinsic = prevPar->maxParMinIntrinsic; par->maxParMax = prevPar->maxParMax; + par->maxParMaxIntrinsic = prevPar->maxParMaxIntrinsic; } else - par->maxParMin = par->maxParMax = 0; + par->maxParMin = par->maxParMinIntrinsic = par->maxParMax = + par->maxParMaxIntrinsic = 0; - PRINTF (" new par: %d\n", paragraphs->size() - 1); + DBG_OBJ_MSGF ("construct.paragraph", 1, "new par: %d", + paragraphs->size() - 1); } - PRINTF (" last par: %d\n", paragraphs->size() - 1); + DBG_OBJ_MSGF ("construct.paragraph", 1, "last par: %d", + paragraphs->size() - 1); Paragraph *lastPar = paragraphs->getLastRef(); int corrDiffMin, corrDiffMax; @@ -816,31 +1254,54 @@ void Textblock::handleWordExtremes (int wordIndex) corrDiffMin = 0; else corrDiffMin = lastWord->origSpace - lastWord->hyphenWidth; - + corrDiffMax = lastWord->origSpace - lastWord->hyphenWidth; } else corrDiffMin = corrDiffMax = 0; - PRINTF (" (lastPar from %d to %d; corrDiffMin = %d, corDiffMax = %d)\n", - lastPar->firstWord, lastPar->lastWord, corrDiffMin, corrDiffMax); + DBG_OBJ_MSGF ("construct.paragraph", 1, + "(lastPar from %d to %d; corrDiffMin = %d, corDiffMax = %d)", + lastPar->firstWord, lastPar->lastWord, corrDiffMin, + corrDiffMax); + + DBG_OBJ_MSGF ("construct.paragraph", 1, + "before: parMin = %d (%d) (max = %d (%d)), " + "parMax = %d (%d) (max = %d (%d))", + lastPar->parMin, lastPar->parMinIntrinsic, + lastPar->maxParMin, lastPar->maxParMinIntrinsic, + lastPar->parMax, lastPar->parMaxIntrinsic, + lastPar->maxParMax, lastPar->maxParMaxIntrinsic); // Minimum: between two *possible* breaks. // Shrinkability could be considered, but really does not play a role. lastPar->parMin += wordExtremes.minWidth + word->hyphenWidth + corrDiffMin; + lastPar->parMinIntrinsic += + wordExtremes.minWidthIntrinsic + word->hyphenWidth + corrDiffMin; lastPar->maxParMin = misc::max (lastPar->maxParMin, lastPar->parMin); + lastPar->maxParMinIntrinsic = + misc::max (lastPar->maxParMinIntrinsic, lastPar->parMinIntrinsic); if (word->badnessAndPenalty.lineCanBeBroken (1) && (word->flags & Word::UNBREAKABLE_FOR_MIN_WIDTH) == 0) - lastPar->parMin = 0; + lastPar->parMin = lastPar->parMinIntrinsic = 0; // Maximum: between two *necessary* breaks. lastPar->parMax += wordExtremes.maxWidth + word->hyphenWidth + corrDiffMax; + lastPar->parMaxIntrinsic += + wordExtremes.maxWidthIntrinsic + word->hyphenWidth + corrDiffMax; lastPar->maxParMax = misc::max (lastPar->maxParMax, lastPar->parMax); + lastPar->maxParMaxIntrinsic = + misc::max (lastPar->maxParMaxIntrinsic, lastPar->parMaxIntrinsic); - PRINTF (" => parMin = %d (max = %d), parMax = %d (max = %d)\n", - lastPar->parMin, lastPar->maxParMin, lastPar->parMax, - lastPar->maxParMax); + DBG_OBJ_MSGF ("construct.paragraph", 1, + "after: parMin = %d (%d) (max = %d (%d)), " + "parMax = %d (%d) (max = %d (%d))", + lastPar->parMin, lastPar->parMinIntrinsic, + lastPar->maxParMin, lastPar->maxParMinIntrinsic, + lastPar->parMax, lastPar->parMaxIntrinsic, + lastPar->maxParMax, lastPar->maxParMaxIntrinsic); lastPar->lastWord = wordIndex; + DBG_OBJ_LEAVE (); } /** @@ -852,7 +1313,8 @@ void Textblock::correctLastWordExtremes () Word *word = words->getLastRef (); if (word->badnessAndPenalty.lineCanBeBroken (1) && (word->flags & Word::UNBREAKABLE_FOR_MIN_WIDTH) == 0) { - paragraphs->getLastRef()->parMin = 0; + paragraphs->getLastRef()->parMin = + paragraphs->getLastRef()->parMinIntrinsic = 0; PRINTF (" => corrected; parMin = %d\n", paragraphs->getLastRef()->parMin); } @@ -860,10 +1322,10 @@ void Textblock::correctLastWordExtremes () } -int Textblock::hyphenateWord (int wordIndex) +int Textblock::hyphenateWord (int wordIndex, int *addIndex1) { Word *hyphenatedWord = words->getRef(wordIndex); - char lang[3] = { hyphenatedWord->style->x_lang[0], + char lang[3] = { hyphenatedWord->style->x_lang[0], hyphenatedWord->style->x_lang[1], 0 }; Hyphenator *hyphenator = Hyphenator::getHyphenator (lang); PRINTF ("[%p] considering to hyphenate word %d, '%s', in language '%s'\n", @@ -879,20 +1341,28 @@ int Textblock::hyphenateWord (int wordIndex) 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); + + DBG_IF_RTFL { + for (int i = wordIndex + numBreaks; i < words->size (); i++) + DBG_SET_WORD (i); + } + for (int i = 0; i < numBreaks; i++) initWord (wordIndex + i); PRINTF ("[%p] ... => %d words\n", this, words->size ()); + moveWordIndices (wordIndex, numBreaks, addIndex1); + // 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 (wordIndex + i, wordSize[i].width, wordSize[i].ascent, @@ -907,17 +1377,16 @@ int Textblock::hyphenateWord (int wordIndex) 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 == 0) w->flags |= Word::WORD_START; - else + else w->flags &= ~Word::WORD_START; if (i == numBreaks) w->flags |= Word::WORD_END; - else + else w->flags &= ~Word::WORD_END; if (i < numBreaks) { @@ -930,26 +1399,25 @@ int Textblock::hyphenateWord (int wordIndex) strlen (hyphenDrawChar)); w->flags |= (Word::DRAW_AS_ONE_TEXT | Word::DIV_CHAR_AT_EOL | Word::UNBREAKABLE_FOR_MIN_WIDTH); - - PRINTF (" [%d] + hyphen\n", wordIndex + i); } else { - if (origWord.content.space) { + if (origWord.content.space) fillSpace (wordIndex + i, origWord.spaceStyle); - PRINTF (" [%d] + space\n", wordIndex + i); - } else { - PRINTF (" [%d] + nothing\n", wordIndex + i); - } } - accumulateWordData (wordIndex + i); - - //printf ("[%p] %d: hyphenated word part: ", this, wordIndex + i); - //printWordWithFlags (w); - //printf ("\n"); + DBG_SET_WORD (wordIndex + i); } + // AccumulateWordData() will calculate the width, which depends + // on the borders (possibly limited by floats), which depends on + // the widgeds so far. For this reason, it is important to first + // make all words consistent before calling + // accumulateWordData(); therefore the second loop. + + for (int i = 0; i < numBreaks + 1; i++) + accumulateWordData (wordIndex + i); + PRINTF (" finished\n"); - + //delete origword->content.text; TODO: Via textZone? origWord.style->unref (); origWord.spaceStyle->unref (); @@ -961,66 +1429,116 @@ int Textblock::hyphenateWord (int wordIndex) return numBreaks; } +void Textblock::moveWordIndices (int wordIndex, int num, int *addIndex1) +{ + DBG_OBJ_ENTER ("construct.word", 0, "moveWordIndices", "%d, %d", + wordIndex, num); + + if (containingBlock->outOfFlowMgr) + containingBlock->outOfFlowMgr->moveExternalIndices (this, wordIndex, num); + + for (int i = lines->size () - 1; i >= 0; i--) { + Line *line = lines->getRef (i); + if (line->lastOofRefPositionedBeforeThisLine < wordIndex) { + // Since lastOofRefPositionedBeforeThisLine are ascending, + // the search can be stopped here. + DBG_OBJ_MSGF ("construct.word", 1, + "lines[%d]->lastOofRef = %d < %d => stop", + i, line->lastOofRefPositionedBeforeThisLine, wordIndex); + break; + } else { + DBG_OBJ_MSGF ("construct.word", 1, + "adding %d to lines[%d]->lastOofRef...: %d -> %d", + num, i, line->lastOofRefPositionedBeforeThisLine, + line->lastOofRefPositionedBeforeThisLine + num); + line->lastOofRefPositionedBeforeThisLine += num; + } + } + + // Unlike the last line, the last paragraph is already constructed. (To + // make sure we cover all cases, we iterate over the last paragraphs.) + Paragraph *par; + for (int parNo = paragraphs->size () - 1; + parNo >= 0 && + (par = paragraphs->getRef(parNo)) && par->lastWord > wordIndex; + parNo--) { + par->lastWord += num; + if (par->firstWord > wordIndex) + par->firstWord += num; + } + + // Addiditional indices. When needed, the number can be extended. + if (addIndex1 && *addIndex1 >= wordIndex) + *addIndex1 += num; + + DBG_OBJ_LEAVE (); +} + void Textblock::accumulateWordForLine (int lineIndex, int wordIndex) { + DBG_OBJ_ENTER ("construct.line", 1, "accumulateWordForLine", "%d, %d", + lineIndex, wordIndex); + DBG_MSG_WORD ("construct.line", 2, "<i>word:</i> ", 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; - } + int borderAscent, borderDescent, marginAscent, marginDescent; + + DBG_OBJ_MSGF ("construct.line", 2, "size.ascent = %d, size.descent = %d", + word->size.ascent, word->size.descent); - line->boxAscent = - misc::max (line->boxAscent, - word->size.ascent, - word->size.ascent - + word->content.widget->getStyle()->margin.top - - collapseMarginTop); + if (word->content.type == core::Content::WIDGET_IN_FLOW) { + // TODO Consider extraSpace? + marginAscent = word->size.ascent; + marginDescent = word->size.descent; + borderAscent = + marginAscent - word->content.widget->getStyle()->margin.top; + borderDescent = + marginDescent - word->content.widget->getStyle()->margin.bottom; - word->content.widget->parentRef = lineIndex; + word->content.widget->parentRef = + OutOfFlowMgr::createRefNormalFlow (lineIndex); + DBG_OBJ_SET_NUM_O (word->content.widget, "parentRef", + word->content.widget->parentRef); } else { - line->marginDescent = - misc::max (line->marginDescent, line->boxDescent); + borderAscent = marginAscent = word->size.ascent; + borderDescent = marginDescent = word->size.descent; if (word->content.type == core::Content::BREAK) line->breakSpace = - misc::max (word->content.breakSpace, - line->marginDescent - line->boxDescent, - line->breakSpace); + misc::max (word->content.breakSpace, line->breakSpace); } + + DBG_OBJ_MSGF ("construct.line", 2, + "borderAscent = %d, borderDescent = %d, marginAscent = %d, " + "marginDescent = %d", + borderAscent, borderDescent, marginAscent, marginDescent); + + line->borderAscent = misc::max (line->borderAscent, borderAscent); + line->borderDescent = misc::max (line->borderDescent, borderDescent); + line->marginAscent = misc::max (line->marginAscent, marginAscent); + line->marginDescent = misc::max (line->marginDescent, marginDescent); + + DBG_OBJ_LEAVE (); } void Textblock::accumulateWordData (int wordIndex) { + DBG_OBJ_ENTER ("construct.word.accum", 1, "accumulateWordData", "%d", + wordIndex); + DBG_MSG_WORD ("construct.word.accum", 1, "<i>word:</i> ", 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. @@ -1035,14 +1553,15 @@ void Textblock::accumulateWordData (int wordIndex) firstWordOfLine = lines->getRef(lineIndex - 1)->lastWord + 1; Word *word = words->getRef (wordIndex); - PRINTF ("[%p] ACCUMULATE_WORD_DATA (%d); lineIndex = %d: ...\n", - this, wordIndex, lineIndex); + DBG_OBJ_MSGF ("construct.word.accum", 2, "lineIndex = %d", lineIndex); - int availWidth = calcAvailWidth (lineIndex); + int lineBreakWidth = calcLineBreakWidth (lineIndex); - PRINTF (" (%s existing line %d starts with word %d)\n", - lineIndex < lines->size () ? "already" : "not yet", - lineIndex, firstWordOfLine); + DBG_OBJ_MSGF ("construct.word.accum", 2, + "(%s existing line %d starts with word %d; " + "lineBreakWidth = %d)", + lineIndex < lines->size () ? "already" : "not yet", + lineIndex, firstWordOfLine, lineBreakWidth); if (wordIndex == firstWordOfLine) { // first word of the (not neccessarily yet existing) line @@ -1051,49 +1570,94 @@ void Textblock::accumulateWordData (int wordIndex) word->maxDescent = word->size.descent; word->totalSpaceStretchability = 0; word->totalSpaceShrinkability = 0; + + DBG_OBJ_MSGF ("construct.word.accum", 1, + "first word of line: words[%d].totalWidth = %d + %d = %d; " + "maxAscent = %d, maxDescent = %d", + wordIndex, word->size.width, word->hyphenWidth, + word->totalWidth, word->maxAscent, word->maxDescent); } else { Word *prevWord = words->getRef (wordIndex - 1); word->totalWidth = prevWord->totalWidth + prevWord->origSpace - prevWord->hyphenWidth + word->size.width + word->hyphenWidth; - word->maxAscent = misc::max (prevWord->size.ascent, word->size.ascent); - word->maxDescent = misc::max (prevWord->size.descent, word->size.descent); + word->maxAscent = misc::max (prevWord->maxAscent, word->size.ascent); + word->maxDescent = misc::max (prevWord->maxDescent, word->size.descent); word->totalSpaceStretchability = prevWord->totalSpaceStretchability + getSpaceStretchability(prevWord); word->totalSpaceShrinkability = prevWord->totalSpaceShrinkability + getSpaceShrinkability(prevWord); + + DBG_OBJ_MSGF ("construct.word.accum", 1, + "not first word of line: words[%d].totalWidth = %d + %d - " + "%d + %d + %d = %d; maxAscent = max (%d, %d) = %d, " + "maxDescent = max (%d, %d) = %d", + wordIndex, prevWord->totalWidth, prevWord->origSpace, + prevWord->hyphenWidth, word->size.width, + word->hyphenWidth, word->totalWidth, + prevWord->maxAscent, word->size.ascent, word->maxAscent, + prevWord->maxDescent, word->size.descent, word->maxDescent); } int totalStretchability = - word->totalSpaceStretchability + getLineStretchability (word); + word->totalSpaceStretchability + getLineStretchability (wordIndex); int totalShrinkability = - word->totalSpaceShrinkability + getLineShrinkability (word); - word->badnessAndPenalty.calcBadness (word->totalWidth, availWidth, + word->totalSpaceShrinkability + getLineShrinkability (wordIndex); + + DBG_OBJ_MSGF ("construct.word.accum", 1, + "totalStretchability = %d + ... = %d", + word->totalSpaceStretchability, totalStretchability); + DBG_OBJ_MSGF ("construct.word.accum", 1, + "totalShrinkability = %d + ... = %d", + word->totalSpaceShrinkability, totalShrinkability); + + word->badnessAndPenalty.calcBadness (word->totalWidth, lineBreakWidth, totalStretchability, totalShrinkability); - //printf (" => "); - //printWord (word); - //printf ("\n"); + DBG_IF_RTFL { + misc::StringBuffer sb; + word->badnessAndPenalty.intoStringBuffer (&sb); + DBG_OBJ_MSGF ("construct.word.accum", 1, "b+p: %s", sb.getChars ()); + } + + DBG_OBJ_LEAVE (); } -int Textblock::calcAvailWidth (int lineIndex) +int Textblock::calcLineBreakWidth (int lineIndex) { - int availWidth = - this->availWidth - getStyle()->boxDiffWidth() - innerPadding; + DBG_OBJ_ENTER ("construct.word.width", 1, "calcLineBreakWidth", + "%d <i>of %d</i>", lineIndex, lines->size()); + + int lineBreakWidth = this->lineBreakWidth - leftInnerPadding; if (limitTextWidth && layout->getUsesViewport () && - availWidth > layout->getWidthViewport () - 10) - availWidth = layout->getWidthViewport () - 10; + // margin/border/padding will be subtracted later, via OOFM. + lineBreakWidth - getStyle()->boxDiffWidth() + > layout->getWidthViewport () - 10) + lineBreakWidth = layout->getWidthViewport () - 10; if (lineIndex == 0) - availWidth -= line1OffsetEff; + lineBreakWidth -= line1OffsetEff; + + int leftBorder, rightBorder; + if (mustBorderBeRegarded (lineIndex)) { + leftBorder = newLineLeftBorder; + rightBorder = newLineRightBorder; + } else + leftBorder = rightBorder = 0; + + leftBorder = misc::max (leftBorder, getStyle()->boxOffsetX()); + rightBorder = misc::max (rightBorder, getStyle()->boxRestWidth()); + + lineBreakWidth -= (leftBorder + rightBorder); - //PRINTF("[%p] CALC_AVAIL_WIDTH => %d - %d - %d = %d\n", - // this, this->availWidth, getStyle()->boxDiffWidth(), innerPadding, - // availWidth); + DBG_OBJ_MSGF ("construct.word.width", 2, "=> %d - %d - (%d + %d) = %d\n", + this->lineBreakWidth, leftInnerPadding, leftBorder, + rightBorder, lineBreakWidth); - return availWidth; + DBG_OBJ_LEAVE (); + return lineBreakWidth; } void Textblock::initLine1Offset (int wordIndex) @@ -1103,18 +1667,18 @@ void Textblock::initLine1Offset (int wordIndex) /* Test whether line1Offset can be used. */ if (wordIndex == 0) { if (ignoreLine1OffsetSometimes && - line1Offset + word->size.width > availWidth) { + line1Offset + word->size.width > lineBreakWidth) { line1OffsetEff = 0; } else { int indent = 0; - if (word->content.type == core::Content::WIDGET && - word->content.widget->blockLevel() == true) { + if (word->content.type == core::Content::WIDGET_IN_FLOW && + word->content.widget->isBlockLevel()) { /* don't use text-indent when nesting blocks */ } else { if (core::style::isPerLength(getStyle()->textIndent)) { indent = core::style::multiplyWithPerLengthRounded - (this->availWidth, getStyle()->textIndent); + (lineBreakWidth, getStyle()->textIndent); } else { indent = core::style::absLengthVal (getStyle()->textIndent); } @@ -1131,49 +1695,109 @@ void Textblock::initLine1Offset (int wordIndex) */ void Textblock::alignLine (int lineIndex) { + DBG_OBJ_ENTER ("construct.line", 0, "alignLine", "%d", 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; - // Do not justify the last line of a paragraph (which ends on a - // BREAK or with the last word of the page). - if(!(lastWord->content.type == core::Content::BREAK || - line->lastWord == words->size () - 1) || - // In some cases, however, an unjustified line would be too wide: - // when the line would be shrunken otherwise. (This solution is - // far from perfect, but a better solution would make changes in - // the line breaking algorithm necessary.) - availWidth < lastWord->totalWidth) - 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; + if (line->firstWord <= line->lastWord) { + Word *firstWord = words->getRef (line->firstWord); + Word *lastWord = words->getRef (line->lastWord); + int lineBreakWidth = + this->lineBreakWidth - (line->leftOffset + line->rightOffset); + + for (int i = line->firstWord; i < line->lastWord; i++) + words->getRef(i)->effSpace = words->getRef(i)->origSpace; + + if (firstWord->content.type != core::Content::BREAK) { + switch (firstWord->style->textAlign) { + case core::style::TEXT_ALIGN_LEFT: + DBG_OBJ_MSG ("construct.line", 1, + "first word has 'text-align: left'"); + line->alignment = Line::LEFT; + break; + case core::style::TEXT_ALIGN_STRING: /* handled elsewhere (in the + * future)? */ + DBG_OBJ_MSG ("construct.line", 1, + "first word has 'text-align: string'"); + line->alignment = Line::LEFT; + break; + case core::style::TEXT_ALIGN_JUSTIFY: /* see some lines above */ + DBG_OBJ_MSG ("construct.line", 1, + "first word has 'text-align: justify'"); + line->alignment = Line::LEFT; + // Do not justify the last line of a paragraph (which ends on a + // BREAK or with the last word of the page). + if(!(lastWord->content.type == core::Content::BREAK || + line->lastWord == words->size () - 1) || + // In some cases, however, an unjustified line would be too wide: + // when the line would be shrunken otherwise. (This solution is + // far from perfect, but a better solution would make changes in + // the line breaking algorithm necessary.) + lineBreakWidth < lastWord->totalWidth) + justifyLine (line, lineBreakWidth - lastWord->totalWidth); + break; + case core::style::TEXT_ALIGN_RIGHT: + DBG_OBJ_MSG ("construct.line", 1, + "first word has 'text-align: right'"); + line->alignment = Line::RIGHT; + break; + case core::style::TEXT_ALIGN_CENTER: + DBG_OBJ_MSG ("construct.line", 1, + "first word has 'text-align: center'"); + line->alignment = Line::CENTER; + break; + default: + // compiler happiness + line->alignment = Line::LEFT; + } + + } else + // empty line (only line break); + line->alignment = Line::LEFT; + } else + // empty line + line->alignment = Line::LEFT; + + DBG_OBJ_LEAVE (); +} + +void Textblock::calcTextOffset (int lineIndex, int totalWidth) +{ + DBG_OBJ_ENTER ("construct.line", 0, "calcTextOffset", "%d, %d", + lineIndex, totalWidth); + + Line *line = lines->getRef (lineIndex); + int lineWidth = line->firstWord <= line->lastWord ? + words->getRef(line->lastWord)->totalWidth : 0; + + DBG_OBJ_MSGF ("construct.line", 1, "leftOffset = %d, lineWidth = %d", + line->leftOffset, lineWidth); + + switch (line->alignment) { + case Line::LEFT: + line->textOffset = line->leftOffset; + break; + + case Line::RIGHT: + line->textOffset = totalWidth - line->rightOffset - lineWidth; + break; + + case Line::CENTER: + line->textOffset = + (line->leftOffset + totalWidth - line->rightOffset - lineWidth) / 2; + break; + + default: + misc::assertNotReached (); + break; } + + // For large lines (images etc), which do not fit into the viewport: + if (line->textOffset < line->leftOffset) + line->textOffset = line->leftOffset; + + DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "textOffset", line->textOffset); + DBG_OBJ_LEAVE (); } /** @@ -1184,43 +1808,55 @@ void Textblock::alignLine (int lineIndex) */ void Textblock::rewrap () { - PRINTF ("[%p] REWRAP: wrapRef = %d\n", this, wrapRef); + DBG_OBJ_ENTER0 ("construct.line", 0, "rewrap"); if (wrapRefLines == -1) - /* page does not have to be rewrapped */ - return; + DBG_OBJ_MSG ("construct.line", 0, "does not have to be rewrapped"); + else { + // All lines up from wrapRef will be rebuild from the word list, + // the line list up from this position is rebuild. + lines->setSize (wrapRefLines); + DBG_OBJ_SET_NUM ("lines.size", lines->size ()); + nonTemporaryLines = misc::min (nonTemporaryLines, wrapRefLines); - /* All lines up from wrapRef will be rebuild from the word list, - * the line list up from this position is rebuild. */ - lines->setSize (wrapRefLines); - nonTemporaryLines = misc::min (nonTemporaryLines, wrapRefLines); + initNewLine (); - int firstWord; - if (lines->size () > 0) - firstWord = lines->getLastRef()->lastWord + 1; - else - firstWord = 0; + int firstWord; + if (lines->size () > 0) { + Line *lastLine = lines->getLastRef(); + firstWord = lastLine->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) - calcWidgetSize (word->content.widget, &word->size); - - wordWrap (i, false); + DBG_OBJ_MSGF ("construct.line", 0, "starting with word %d", firstWord); - // Somewhat historical, but still important, note: - // - // For the case that something else is done with this word, it - // is important that 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. - // - // So this is necessary: word = words->getRef (i); + lastWordDrawn = misc::min (lastWordDrawn, firstWord - 1); + DBG_OBJ_SET_NUM ("lastWordDrawn", lastWordDrawn); + + for (int i = firstWord; i < words->size (); i++) { + Word *word = words->getRef (i); + + if (word->content.type == core::Content::WIDGET_IN_FLOW) + word->content.widget->sizeRequest (&word->size); + + wordWrap (i, false); + + // Somewhat historical, but still important, note: + // + // For the case that something else is done with this word, it + // is important that 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. + // + // So this is necessary: word = words->getRef (i); + } + + // Next time, the page will not have to be rewrapped. + wrapRefLines = -1; + DBG_OBJ_SET_NUM ("wrapRefLines", wrapRefLines); } - /* Next time, the page will not have to be rewrapped. */ - wrapRefLines = -1; + DBG_OBJ_LEAVE (); } /** @@ -1228,62 +1864,255 @@ void Textblock::rewrap () */ void Textblock::fillParagraphs () { - if (wrapRefParagraphs == -1) - return; + DBG_OBJ_ENTER0 ("resize", 0, "fillParagraphs"); + + DBG_OBJ_MSGF ("resize", 1, "wrapRefParagraphs = %d", wrapRefParagraphs); + + if (wrapRefParagraphs != -1) { + // Notice that wrapRefParagraphs refers to the lines, not to the + // paragraphs. + int firstWordOfLine; + if (lines->size () > 0 && wrapRefParagraphs > 0) { + // Sometimes, wrapRefParagraphs is larger than lines->size(), due to + // floats? (Has to be clarified.) + int lineNo = misc::min (wrapRefParagraphs, lines->size ()) - 1; + firstWordOfLine = lines->getRef(lineNo)->lastWord + 1; + } else + firstWordOfLine = 0; + + int parNo; + if (paragraphs->size() > 0 && + firstWordOfLine > paragraphs->getLastRef()->firstWord) + // A special case: the paragraphs list has been partly built, but + // not yet the paragraph containing the word in question. In + // this case, only the rest of the paragraphs list must be + // constructed. (Without this check, findParagraphOfWord would + // return -1 in this case, so that all paragraphs would be + // rebuilt.) + parNo = paragraphs->size (); + else + // If there are no paragraphs yet, findParagraphOfWord will return + // -1: use 0 then instead. + parNo = misc::max (0, findParagraphOfWord (firstWordOfLine)); - // Notice that wrapRefParagraphs refers to the lines, not to the paragraphs. - int firstWordOfLine; - if (lines->size () > 0 && wrapRefParagraphs > 0) - firstWordOfLine = lines->getRef(wrapRefParagraphs - 1)->lastWord + 1; - else - firstWordOfLine = 0; + paragraphs->setSize (parNo); - int parNo; - if (paragraphs->size() > 0 && - firstWordOfLine > paragraphs->getLastRef()->firstWord) - // A special case: the paragraphs list has been partly built, but - // not yet the paragraph containing the word in question. In - // this case, only the rest of the paragraphs list must be - // constructed. (Without this check, findParagraphOfWord would - // return -1 in this case, so that all paragraphs would be - // rebuilt.) - parNo = paragraphs->size (); - else - // If there are no paragraphs yet, findParagraphOfWord will return - // -1: use 0 then instead. - parNo = misc::max (0, findParagraphOfWord (firstWordOfLine)); + int firstWord; + if (paragraphs->size () > 0) + firstWord = paragraphs->getLastRef()->lastWord + 1; + else + firstWord = 0; - paragraphs->setSize (parNo); + DBG_OBJ_MSGF ("resize", 1, "firstWord = %d, words->size() = %d [before]", + firstWord, words->size ()); - int firstWord; - if (paragraphs->size () > 0) - firstWord = paragraphs->getLastRef()->lastWord + 1; - else - firstWord = 0; + for (int i = firstWord; i < words->size (); i++) + handleWordExtremes (i); - PRINTF ("[%p] FILL_PARAGRAPHS: now %d paragraphs; starting from word %d\n", - this, parNo, firstWord); + DBG_OBJ_MSGF ("resize", 1, "words->size() = %d [after]", words->size ()); - for (int i = firstWord; i < words->size (); i++) - handleWordExtremes (i); + wrapRefParagraphs = -1; + DBG_OBJ_SET_NUM ("wrapRefParagraphs", wrapRefParagraphs); + } - wrapRefParagraphs = -1; + DBG_OBJ_LEAVE (); +} + +void Textblock::initNewLine () +{ + DBG_OBJ_ENTER0 ("construct.line", 0, "initNewLine"); + + // At the very beginning, in Textblock::Textblock, where this + // method is called, containingBlock is not yet defined. + + if (containingBlock && containingBlock->outOfFlowMgr) { + if (lines->size () == 0) { + int clearPosition = + containingBlock->outOfFlowMgr->getClearPosition (this); + setVerticalOffset (misc::max (clearPosition, 0)); + } + } + + calcBorders (lines->size() > 0 ? + lines->getLastRef()->lastOofRefPositionedBeforeThisLine : -1, + 1); + + newLineAscent = newLineDescent = 0; + + DBG_OBJ_SET_NUM ("newLineAscent", newLineAscent); + DBG_OBJ_SET_NUM ("newLineDescent", newLineDescent); + + DBG_OBJ_LEAVE (); +} + +void Textblock::calcBorders (int lastOofRef, int height) +{ + DBG_OBJ_ENTER ("construct.line", 0, "calcBorders", "%d, %d", + lastOofRef, height); + + if (containingBlock && containingBlock->outOfFlowMgr) { + // Consider the example: + // + // <div> + // Some text A ... + // <p> Some text B ... <img style="float:right" ...> </p> + // Some more text C ... + // </div> + // + // If the image is large enough, it should float around the last + // paragraph, "Some more text C ...": + // + // Some more text A ... + // + // Some more ,---------. + // text B ... | | + // | <img> | + // Some more | | <---- Consider this line! + // text C ... '---------' + // + // Since this float is generated in the <p> element, not in the- + // <div> element, and since they are represented by different + // instances of dw::Textblock, lastOofRefPositionedBeforeThisLine, + // and so lastOofRef, is -1 for the line marked with an arrow; + // this would result in ignoring the float, because -1 is + // equivalent to the very beginning of the <div> element ("Some + // more text A ..."), which is not affected by the float. + // + // On the other hand, the only relevant values of + // Line::lastOofRefPositionedBeforeThisLine are those greater + // than the first word of the new line, so a solution is to use + // the maximum of both. + + + int firstWordOfLine = lines->size() > 0 ? + lines->getLastRef()->lastWord + 1 : 0; + int effOofRef = misc::max (lastOofRef, firstWordOfLine - 1); + + int y = yOffsetOfLineToBeCreated (); + + newLineHasFloatLeft = + containingBlock->outOfFlowMgr->hasFloatLeft (this, y, height, this, + effOofRef); + newLineHasFloatRight = + containingBlock->outOfFlowMgr->hasFloatRight (this, y, height, this, + effOofRef); + newLineLeftBorder = + containingBlock->outOfFlowMgr->getLeftBorder (this, y, height, this, + effOofRef); + newLineRightBorder = + containingBlock->outOfFlowMgr->getRightBorder (this, y, height, this, + effOofRef); + newLineLeftFloatHeight = newLineHasFloatLeft ? + containingBlock->outOfFlowMgr->getLeftFloatHeight (this, y, height, + this, effOofRef) : + 0; + newLineRightFloatHeight = newLineHasFloatRight ? + containingBlock->outOfFlowMgr->getRightFloatHeight (this, y, height, + this, effOofRef) : + 0; + + DBG_OBJ_MSGF ("construct.line", 1, + "%d * %d (%s) / %d * %d (%s), at %d (%d), until %d = " + "max (%d, %d - 1)", + newLineLeftBorder, newLineLeftFloatHeight, + newLineHasFloatLeft ? "true" : "false", + newLineRightBorder, newLineRightFloatHeight, + newLineHasFloatRight ? "true" : "false", + y, height, effOofRef, lastOofRef, firstWordOfLine); + } else { + newLineHasFloatLeft = newLineHasFloatRight = false; + newLineLeftBorder = newLineRightBorder = 0; + newLineLeftFloatHeight = newLineRightFloatHeight = 0; + + DBG_OBJ_MSG ("construct.line", 0, "<i>no CB of OOFM</i>"); + } + + DBG_OBJ_SET_BOOL ("newLineHasFloatLeft", newLineHasFloatLeft); + DBG_OBJ_SET_BOOL ("newLineHasFloatRight", newLineHasFloatRight); + DBG_OBJ_SET_NUM ("newLineLeftBorder", newLineLeftBorder); + DBG_OBJ_SET_NUM ("newLineRightBorder", newLineRightBorder); + DBG_OBJ_SET_NUM ("newLineLeftFloatHeight", newLineLeftFloatHeight); + DBG_OBJ_SET_NUM ("newLineRightFloatHeight", newLineRightFloatHeight); + + DBG_OBJ_LEAVE (); } 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); + DBG_OBJ_ENTER0 ("construct.line", 0, "showMissingLines"); + + // "Temporary word": when the last word is an OOF reference, it is + // not processed, and not part of any line. For this reason, we + // introduce a "temporary word", which is in flow, after this last + // OOF reference, and later removed again. + bool tempWord = words->size () > 0 && + words->getLastRef()->content.type == core::Content::WIDGET_OOF_REF; + int firstWordToWrap = + lines->size () > 0 ? lines->getLastRef()->lastWord + 1 : 0; + + DBG_OBJ_MSGF ("construct.line", 1, + "words->size() = %d, firstWordToWrap = %d, tempWord = %s", + words->size (), firstWordToWrap, tempWord ? "true" : "false"); + + if (tempWord) { + core::Requisition size = { 0, 0, 0 }; + addText0 ("", 0, Word::WORD_START | Word::WORD_END, getStyle (), &size); + } + for (int i = firstWordToWrap; i < words->size (); i++) wordWrap (i, true); + + // Remove temporary word again. The only reference should be the line. + if (tempWord) { + cleanupWord (words->size () - 1); + words->setSize (words->size () - 1); + if (lines->getLastRef()->lastWord > words->size () - 1) + lines->getLastRef()->lastWord = words->size () - 1; + } + + // The following old code should not be necessary anymore, after + // the introduction of the "virtual word". Instead, test the + // condition. + assert (lines->size () == 0 || + lines->getLastRef()->lastWord == words->size () - 1); + /* + // In some cases, there are some words of type WIDGET_OOF_REF left, which + // are not added to line, since addLine() is only called within + // wrapWordInFlow(), but not within wrapWordOofRef(). The missing line + // is created here, so it is ensured that the last line ends with the last + // word. + + int firstWordNotInLine = + lines->size () > 0 ? lines->getLastRef()->lastWord + 1: 0; + DBG_OBJ_MSGF ("construct.line", 1, "firstWordNotInLine = %d (of %d)", + firstWordNotInLine, words->size ()); + if (firstWordNotInLine < words->size ()) + addLine (firstWordNotInLine, words->size () - 1, -1, true); + */ + + DBG_OBJ_LEAVE (); } void Textblock::removeTemporaryLines () { - lines->setSize (nonTemporaryLines); + DBG_OBJ_ENTER0 ("construct.line", 0, "removeTemporaryLines"); + + if (nonTemporaryLines < lines->size ()) { + lines->setSize (nonTemporaryLines); + DBG_OBJ_SET_NUM ("lines.size", lines->size ()); + + // For words which will be added, the values calculated before in + // accumulateWordData() are wrong, so it is called again. (Actually, the + // words from the first temporary line are correct, but for simplicity, + // we re-calculate all.) + int firstWord = + lines->size () > 0 ? lines->getLastRef()->lastWord + 1 : 0; + for (int i = firstWord; i < words->size (); i++) + accumulateWordData (i); + } + + DBG_OBJ_LEAVE (); } int Textblock::getSpaceShrinkability(struct Word *word) @@ -1304,18 +2133,35 @@ int Textblock::getSpaceStretchability(struct Word *word) // Alternative: return word->origSpace / 2; } -int Textblock::getLineShrinkability(Word *lastWord) +int Textblock::getLineShrinkability(int lastWordIndex) { return 0; } -int Textblock::getLineStretchability(Word *lastWord) +int Textblock::getLineStretchability(int lastWordIndex) { - if (lastWord->spaceStyle->textAlign == core::style::TEXT_ALIGN_JUSTIFY) - return 0; - else - return stretchabilityFactor * (lastWord->maxAscent - + lastWord->maxDescent) / 100; + DBG_OBJ_ENTER ("construct.word.accum", 0, "getLineStretchability", "%d", + lastWordIndex); + DBG_MSG_WORD ("construct.word.accum", 1, "<i>last word:</i> ", + lastWordIndex, ""); + + Word *lastWord = words->getRef (lastWordIndex); + int str; + + if (lastWord->spaceStyle->textAlign == core::style::TEXT_ALIGN_JUSTIFY) { + str = 0; + DBG_OBJ_MSG ("construct.word.accum", 1, "justified => 0"); + } else { + str = stretchabilityFactor * (lastWord->maxAscent + + lastWord->maxDescent) / 100; + DBG_OBJ_MSGF ("construct.word.accum", 1, + "not justified => %d * (%d + %d) / 100 = %d", + stretchabilityFactor, lastWord->maxAscent, + lastWord->maxDescent, str); + } + + DBG_OBJ_LEAVE (); + return str; // Alternative: return 0; } diff --git a/dw/types.cc b/dw/types.cc index 86836bc1..56af66d1 100644 --- a/dw/types.cc +++ b/dw/types.cc @@ -268,5 +268,90 @@ void Region::addRectangle (Rectangle *rPointer) rectangleList->append (r); } +Content::Type Content::maskForSelection (bool followReferences) +{ + Content::Type widgetMask = (Content::Type) + (Content::WIDGET_IN_FLOW | + (followReferences ? Content::WIDGET_OOF_REF : Content::WIDGET_OOF_CONT)); + return (Content::Type)(Content::SELECTION_CONTENT | widgetMask); +} + +void Content::intoStringBuffer(Content *content, misc::StringBuffer *sb) +{ + switch(content->type) { + case START: + sb->append ("<start>"); + break; + case END: + sb->append ("<end>"); + break; + case TEXT: + sb->append ("\""); + sb->append (content->text); + sb->append ("\""); + break; + case WIDGET_IN_FLOW: + sb->append ("<widget in flow: "); + sb->appendPointer (content->widget); + sb->append (" ("); + sb->append (content->widget->getClassName()); + sb->append (")>"); + break; + case WIDGET_OOF_REF: + sb->append ("<widget oof ref: "); + sb->appendPointer (content->widget); + sb->append (" ("); + sb->append (content->widget->getClassName()); + sb->append (")>"); + break; + case WIDGET_OOF_CONT: + sb->append ("<widget oof cont: "); + sb->appendPointer (content->widget); + sb->append (" ("); + sb->append (content->widget->getClassName()); + sb->append (")>"); + break; + case BREAK: + sb->append ("<break>"); + break; + default: + sb->append ("<"); + sb->appendInt (content->type); + sb->append ("?>"); + break; + } +} + +void Content::maskIntoStringBuffer(Type mask, misc::StringBuffer *sb) +{ + sb->append ((mask & START) ? "st" : "--"); + sb->append (":"); + sb->append ((mask & END) ? "en" : "--"); + sb->append (":"); + sb->append ((mask & TEXT) ? "tx" : "--"); + sb->append (":"); + sb->append ((mask & WIDGET_IN_FLOW) ? "wf" : "--"); + sb->append (":"); + sb->append ((mask & WIDGET_OOF_REF) ? "Wr" : "--"); + sb->append (":"); + sb->append ((mask & WIDGET_OOF_CONT) ? "Wc" : "--"); + sb->append (":"); + sb->append ((mask & BREAK) ? "br" : "--"); +} + +void Content::print (Content *content) +{ + misc::StringBuffer sb; + intoStringBuffer (content, &sb); + printf ("%s", sb.getChars ()); +} + +void Content::printMask (Type mask) +{ + misc::StringBuffer sb; + maskIntoStringBuffer (mask, &sb); + printf ("%s", sb.getChars ()); +} + } // namespace core } // namespace dw diff --git a/dw/types.hh b/dw/types.hh index f04fc138..36d6caa1 100644 --- a/dw/types.hh +++ b/dw/types.hh @@ -180,6 +180,8 @@ struct Extremes { int minWidth; int maxWidth; + int minWidthIntrinsic; + int maxWidthIntrinsic; }; struct Content @@ -188,11 +190,27 @@ struct Content START = 1 << 0, END = 1 << 1, TEXT = 1 << 2, - WIDGET = 1 << 3, - BREAK = 1 << 4, + + /** \brief widget in normal flow, so that _this_ widget + (containing this content) is both container (parent) and + generator */ + WIDGET_IN_FLOW = 1 << 3, + + /** \brief widget out of flow (OOF); _this_ widget (containing + this content) is only the container (parent), but _not_ + generator */ + WIDGET_OOF_CONT = 1 << 4, + + /** \brief reference to a widget out of flow (OOF); _this_ + widget (containing this content) is only the generator + (parent), but _not_ container */ + WIDGET_OOF_REF = 1 << 5, + BREAK = 1 << 6, + ALL = 0xff, REAL_CONTENT = 0xff ^ (START | END), - SELECTION_CONTENT = TEXT | WIDGET | BREAK + SELECTION_CONTENT = TEXT | BREAK, // WIDGET_* must be set additionally + ANY_WIDGET = WIDGET_IN_FLOW | WIDGET_OOF_CONT | WIDGET_OOF_REF, }; /* Content is embedded in struct Word therefore we @@ -205,6 +223,13 @@ struct Content Widget *widget; int breakSpace; }; + + static Content::Type maskForSelection (bool followReferences); + + static void intoStringBuffer(Content *content, lout::misc::StringBuffer *sb); + static void maskIntoStringBuffer(Type mask, lout::misc::StringBuffer *sb); + static void print (Content *content); + static void printMask (Type mask); }; } // namespace core @@ -39,6 +39,7 @@ Embed::Embed(Resource *resource) registerName ("dw::core::ui::Embed", &CLASS_ID); this->resource = resource; resource->setEmbed (this); + DBG_OBJ_ASSOC_CHILD (resource); } Embed::~Embed() @@ -55,6 +56,7 @@ void Embed::sizeRequestImpl (Requisition *requisition) void Embed::getExtremesImpl (Extremes *extremes) { resource->getExtremes (extremes); + correctExtremes (extremes); } void Embed::sizeAllocateImpl (Allocation *allocation) @@ -62,6 +64,35 @@ void Embed::sizeAllocateImpl (Allocation *allocation) resource->sizeAllocate (allocation); } +int Embed::getAvailWidthOfChild (Widget *child, bool forceValue) +{ + return resource->getAvailWidthOfChild (child, forceValue); +} + +int Embed::getAvailHeightOfChild (Widget *child, bool forceValue) +{ + return resource->getAvailHeightOfChild (child, forceValue); +} + +void Embed::correctRequisitionOfChild (Widget *child, + Requisition *requisition, + void (*splitHeightFun) (int, int*, int*)) +{ + resource->correctRequisitionOfChild (child, requisition, splitHeightFun); +} + +void Embed::correctExtremesOfChild (Widget *child, Extremes *extremes) +{ + resource->correctExtremesOfChild (child, extremes); +} + +void Embed::containerSizeChangedForChildren () +{ + DBG_OBJ_ENTER0 ("resize", 0, "containerSizeChangedForChildren"); + resource->containerSizeChangedForChildren (); + DBG_OBJ_LEAVE (); +} + void Embed::enterNotifyImpl (core::EventCrossing *event) { resource->emitEnter(); @@ -87,21 +118,6 @@ bool Embed::buttonPressImpl (core::EventButton *event) return handled; } -void Embed::setWidth (int width) -{ - resource->setWidth (width); -} - -void Embed::setAscent (int ascent) -{ - resource->setAscent (ascent); -} - -void Embed::setDescent (int descent) -{ - resource->setDescent (descent); -} - void Embed::setDisplayed (bool displayed) { resource->setDisplayed (displayed); @@ -180,6 +196,7 @@ void Resource::ActivateEmitter::emitLeave (Resource *resource) Resource::~Resource () { + DBG_OBJ_DELETE (); } void Resource::setEmbed (Embed *embed) @@ -189,26 +206,56 @@ void Resource::setEmbed (Embed *embed) void Resource::getExtremes (Extremes *extremes) { + DBG_OBJ_ENTER0 ("resize", 0, "getExtremes"); + /* Simply return the requisition width */ Requisition requisition; sizeRequest (&requisition); extremes->minWidth = extremes->maxWidth = requisition.width; + extremes->minWidthIntrinsic = extremes->minWidth; + extremes->maxWidthIntrinsic = extremes->maxWidth; + + DBG_OBJ_MSGF ("resize", 1, "result: %d / %d", + extremes->minWidth, extremes->maxWidth); + DBG_OBJ_LEAVE (); } void Resource::sizeAllocate (Allocation *allocation) { } -void Resource::setWidth (int width) +int Resource::getAvailWidthOfChild (Widget *child, bool forceValue) +{ + // Only used when the resource contains other dillo widgets. + misc::assertNotReached (); + return 0; +} + +int Resource::getAvailHeightOfChild (Widget *child, bool forceValue) +{ + // Only used when the resource contains other dillo widgets. + misc::assertNotReached (); + return 0; +} + +void Resource::correctRequisitionOfChild (Widget *child, + Requisition *requisition, + void (*splitHeightFun) (int, int*, + int*)) { + // Only used when the resource contains other dillo widgets. + misc::assertNotReached (); } -void Resource::setAscent (int ascent) +void Resource::correctExtremesOfChild (Widget *child, Extremes *extremes) { + // Only used when the resource contains other dillo widgets. + misc::assertNotReached (); } -void Resource::setDescent (int descent) +void Resource::containerSizeChangedForChildren () { + // No children by default. } void Resource::setDisplayed (bool displayed) @@ -264,18 +311,17 @@ Iterator *LabelButtonResource::iterator (Content::Type mask, bool atEnd) // ---------------------------------------------------------------------- -void ComplexButtonResource::LayoutReceiver::canvasSizeChanged (int width, - int ascent, - int descent) +void ComplexButtonResource::LayoutReceiver::resizeQueued (bool extremesChanged) { - /** - * \todo Verify that this is correct. - */ - resource->queueResize (resource->childWidget->extremesChanged ()); + DBG_OBJ_ENTER ("resize", 0, "LayoutReceiver/resizeQueued", "%s", + extremesChanged ? "true" : "false"); + resource->queueResize (extremesChanged); + DBG_OBJ_LEAVE (); } ComplexButtonResource::ComplexButtonResource () { + DBG_OBJ_CREATE ("dw::core::ui::ComplexButtonResource"); layout = NULL; layoutReceiver.resource = this; click_x = click_y = -1; @@ -283,61 +329,136 @@ ComplexButtonResource::ComplexButtonResource () void ComplexButtonResource::init (Widget *widget) { - this->childWidget = widget; + childWidget = widget; layout = new Layout (createPlatform ()); setLayout (layout); + DBG_OBJ_ASSOC_CHILD (layout); layout->setWidget (widget); layout->connect (&layoutReceiver); + + if (getEmbed ()) + childWidget->setQuasiParent (getEmbed ()); } void ComplexButtonResource::setEmbed (Embed *embed) { ButtonResource::setEmbed (embed); - if (childWidget->usesHints ()) - embed->setUsesHints (); + if (childWidget) + childWidget->setQuasiParent (getEmbed ()); } ComplexButtonResource::~ComplexButtonResource () { delete layout; + DBG_OBJ_DELETE (); } void ComplexButtonResource::sizeRequest (Requisition *requisition) { + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest"); + Requisition widgetRequisition; childWidget->sizeRequest (&widgetRequisition); requisition->width = widgetRequisition.width + 2 * reliefXThickness (); requisition->ascent = widgetRequisition.ascent + reliefYThickness (); requisition->descent = widgetRequisition.descent + reliefYThickness (); + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_LEAVE (); } void ComplexButtonResource::getExtremes (Extremes *extremes) { + DBG_OBJ_ENTER0 ("resize", 0, "getExtremes"); + Extremes widgetExtremes; childWidget->getExtremes (&widgetExtremes); extremes->minWidth = widgetExtremes.minWidth + 2 * reliefXThickness (); extremes->maxWidth = widgetExtremes.maxWidth + 2 * reliefXThickness (); + extremes->minWidthIntrinsic = extremes->minWidth; + extremes->maxWidthIntrinsic = extremes->maxWidth; + + DBG_OBJ_MSGF ("resize", 1, "result: %d / %d", + extremes->minWidth, extremes->maxWidth); + DBG_OBJ_LEAVE (); } void ComplexButtonResource::sizeAllocate (Allocation *allocation) { } -void ComplexButtonResource::setWidth (int width) +int ComplexButtonResource::getAvailWidthOfChild (Widget *child, bool forceValue) +{ + int embedWidth = getEmbed()->getAvailWidth (forceValue); + if (embedWidth == -1) + return -1; + else + return misc::max (embedWidth - 2 * reliefXThickness (), 0); +} + +int ComplexButtonResource::getAvailHeightOfChild (Widget *child, + bool forceValue) +{ + int embedHeight = getEmbed()->getAvailHeight (forceValue); + if (embedHeight == -1) + return -1; + else + return misc::max (embedHeight - 2 * reliefYThickness (), 0); +} + +void ComplexButtonResource::correctRequisitionOfChild (Widget *child, + Requisition *requisition, + void (*splitHeightFun) + (int, int*, int*)) { - childWidget->setWidth (width - 2 * reliefXThickness ()); + // Similar to Widget::correctRequisitionOfChild, but for percentage + // the relief has to be considered. + + if (style::isPerLength (child->getStyle()->width)) { + int availWidth = getEmbed()->getAvailHeight (false); + if (availWidth != -1) { + int baseWidth = misc::max (availWidth + - getEmbed()->boxDiffWidth () + - 2 * reliefXThickness (), + 0); + requisition->width = + child->applyPerWidth (baseWidth, child->getStyle()->width); + } + } else + getEmbed()->correctReqWidthOfChildNoRec (child, requisition); + + // TODO Percentage heights are ignored again. + getEmbed()->correctReqHeightOfChildNoRec (child, requisition, + splitHeightFun); + } -void ComplexButtonResource::setAscent (int ascent) +void ComplexButtonResource::correctExtremesOfChild (Widget *child, + Extremes *extremes) { - childWidget->setAscent (ascent - reliefYThickness ()); + // Similar to Widget::correctExtremesOfChild, but for percentage + // the relief has to be considered. + + if (style::isPerLength (child->getStyle()->width)) { + int availWidth = getEmbed()->getAvailHeight (false); + if (availWidth != -1) { + int baseWidth = misc::max (availWidth + - getEmbed()->boxDiffWidth () + - 2 * reliefXThickness (), + 0); + extremes->minWidth = extremes->maxWidth = + child->applyPerWidth (baseWidth, child->getStyle()->width); + } + } else + getEmbed()->correctExtremesOfChildNoRec (child, extremes); } -void ComplexButtonResource::setDescent (int descent) +void ComplexButtonResource::containerSizeChangedForChildren () { - childWidget->setDescent (descent - reliefYThickness ()); + layout->containerSizeChanged (); } Iterator *ComplexButtonResource::iterator (Content::Type mask, bool atEnd) @@ -231,6 +231,16 @@ protected: void sizeRequestImpl (Requisition *requisition); void getExtremesImpl (Extremes *extremes); void sizeAllocateImpl (Allocation *allocation); + + int getAvailWidthOfChild (Widget *child, bool forceValue); + int getAvailHeightOfChild (Widget *child, bool forceValue); + void correctRequisitionOfChild (Widget *child, + Requisition *requisition, + void (*splitHeightFun) (int, int*, int*)); + void correctExtremesOfChild (Widget *child, Extremes *extremes); + + void containerSizeChangedForChildren (); + void enterNotifyImpl (core::EventCrossing *event); void leaveNotifyImpl (core::EventCrossing *event); bool buttonPressImpl (core::EventButton *event); @@ -241,18 +251,26 @@ public: Embed(Resource *resource); ~Embed(); - void setWidth (int width); - void setAscent (int ascent); - void setDescent (int descent); void setDisplayed (bool displayed); void setEnabled (bool enabled); void draw (View *view, Rectangle *area); Iterator *iterator (Content::Type mask, bool atEnd); void setStyle (style::Style *style); - inline void setUsesHints () { setFlags (USES_HINTS); } - inline Resource *getResource () { return resource; } + + inline void correctReqWidthOfChildNoRec (Widget *child, + Requisition *requisition) + { Widget::correctReqWidthOfChild (child, requisition); } + + inline void correctReqHeightOfChildNoRec (Widget *child, + Requisition *requisition, + void (*splitHeightFun) (int, int*, + int*)) + { Widget::correctReqHeightOfChild (child, requisition, splitHeightFun); } + + virtual void correctExtremesOfChildNoRec (Widget *child, Extremes *extremes) + { Widget::correctExtremesOfChild (child, extremes); } }; /** @@ -329,16 +347,24 @@ protected: clickedEmitter.emitClicked (this, event); } public: - inline Resource () { embed = NULL; } + inline Resource () + { embed = NULL; DBG_OBJ_CREATE ("dw::core::ui::Resource"); } virtual ~Resource (); virtual void sizeRequest (Requisition *requisition) = 0; virtual void getExtremes (Extremes *extremes); virtual void sizeAllocate (Allocation *allocation); - virtual void setWidth (int width); - virtual void setAscent (int ascent); - virtual void setDescent (int descent); + + virtual int getAvailWidthOfChild (Widget *child, bool forceValue); + virtual int getAvailHeightOfChild (Widget *child, bool forceValue); + virtual void correctRequisitionOfChild (Widget *child, + Requisition *requisition, + void (*splitHeightFun) (int, int*, + int*)); + virtual void correctExtremesOfChild (Widget *child, Extremes *extremes); + virtual void containerSizeChangedForChildren (); + virtual void setDisplayed (bool displayed); virtual void draw (View *view, Rectangle *area); virtual Iterator *iterator (Content::Type mask, bool atEnd) = 0; @@ -377,7 +403,7 @@ private: public: ComplexButtonResource *resource; - void canvasSizeChanged (int width, int ascent, int descent); + void resizeQueued (bool extremesChanged); }; friend class LayoutReceiver; @@ -406,9 +432,15 @@ public: void sizeRequest (Requisition *requisition); void getExtremes (Extremes *extremes); void sizeAllocate (Allocation *allocation); - void setWidth (int width); - void setAscent (int ascent); - void setDescent (int descent); + + int getAvailWidthOfChild (Widget *child, bool forceValue); + int getAvailHeightOfChild (Widget *child, bool forceValue); + void correctRequisitionOfChild (Widget *child, + Requisition *requisition, + void (*splitHeightFun) (int, int*, int*)); + void correctExtremesOfChild (Widget *child, Extremes *extremes); + void containerSizeChangedForChildren (); + Iterator *iterator (Content::Type mask, bool atEnd); int getClickX () {return click_x;}; int getClickY () {return click_y;}; diff --git a/dw/widget.cc b/dw/widget.cc index 385bdb97..ed2c2b8e 100644 --- a/dw/widget.cc +++ b/dw/widget.cc @@ -17,8 +17,6 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - - #include "core.hh" #include "../lout/msg.h" @@ -62,6 +60,7 @@ void Widget::WidgetImgRenderer::draw (int x, int y, int width, int height) // ---------------------------------------------------------------------- +bool Widget::adjustMinWidth = false; int Widget::CLASS_ID = -1; Widget::Widget () @@ -69,8 +68,10 @@ Widget::Widget () DBG_OBJ_CREATE ("dw::core::Widget"); registerName ("dw::core::Widget", &CLASS_ID); - flags = (Flags)(NEEDS_RESIZE | EXTREMES_CHANGED | HAS_CONTENTS); - parent = NULL; + flags = (Flags)(NEEDS_RESIZE | EXTREMES_CHANGED); + parent = quasiParent = generator = container = NULL; + DBG_OBJ_SET_PTR ("container", container); + layout = NULL; allocation.x = -1; @@ -79,6 +80,8 @@ Widget::Widget () allocation.ascent = 1; allocation.descent = 0; + extraSpace.top = extraSpace.right = extraSpace.bottom = extraSpace.left = 0; + style = NULL; bgColor = NULL; buttonSensitive = true; @@ -106,7 +109,7 @@ Widget::~Widget () if (parent) parent->removeChild (this); - else + else if (layout) layout->removeWidget (); DBG_OBJ_DELETE (); @@ -150,59 +153,320 @@ void Widget::setParent (Widget *parent) buttonSensitive = parent->buttonSensitive; DBG_OBJ_ASSOC_PARENT (parent); - //printf ("The %s %p becomes a child of the %s %p\n", // getClassName(), this, parent->getClassName(), parent); + + // Determine the container. Currently rather simple; will become + // more complicated when absolute and fixed positions are + // supported. + container = NULL; + for (Widget *widget = getParent (); widget != NULL && container == NULL; + widget = widget->getParent()) + if (widget->isPossibleContainer ()) + container = widget; + // If there is no possible container widget, there is + // (surprisingly!) also no container (i. e. the viewport is + // used). Does not occur in dillo, where the toplevel widget is a + // Textblock. + DBG_OBJ_SET_PTR ("container", container); + + notifySetParent(); +} + +void Widget::setQuasiParent (Widget *quasiParent) +{ + this->quasiParent = quasiParent; + + // More to do? Compare with setParent(). + + DBG_OBJ_SET_PTR ("quasiParent", quasiParent); } void Widget::queueDrawArea (int x, int y, int width, int height) { /** \todo Maybe only the intersection? */ - layout->queueDraw (x + allocation.x, y + allocation.y, width, height); - _MSG("Widget::queueDrawArea x=%d y=%d w=%d h=%d\n", x, y, width, height); + + DBG_OBJ_ENTER ("draw", 0, "queueDrawArea", "%d, %d, %d, %d", + x, y, width, height); + + _MSG("Widget::queueDrawArea alloc(%d %d %d %d) wid(%d %d %d %d)\n", + allocation.x, allocation.y, + allocation.width, allocation.ascent + allocation.descent, + x, y, width, height); + if (layout) + layout->queueDraw (x + allocation.x, y + allocation.y, width, height); + + DBG_OBJ_LEAVE (); } /** * \brief This method should be called, when a widget changes its size. + * + * A "fast" queueResize will ignore the anchestors, and furthermore + * not trigger the idle function. Used only within + * viewportSizeChanged, and not available outside Layout and Widget. */ -void Widget::queueResize (int ref, bool extremesChanged) +void Widget::queueResize (int ref, bool extremesChanged, bool fast) { + DBG_OBJ_ENTER ("resize", 0, "queueResize", "%d, %s, %s", + ref, extremesChanged ? "true" : "false", + fast ? "true" : "false"); + + // queueResize() can be called recursively; calls are queued, so + // that actualQueueResize() is clean. + + if (queueResizeEntered ()) { + DBG_OBJ_MSG ("resize", 1, "put into queue"); + layout->queueQueueResizeList->pushUnder (new Layout::QueueResizeItem + (this, ref, extremesChanged, + fast)); + } else { + actualQueueResize (ref, extremesChanged, fast); + + DBG_IF_RTFL { + if (layout == NULL) + DBG_OBJ_MSG ("resize", 1, "layout is not set"); + else if (layout->queueQueueResizeList->size () == 0) + DBG_OBJ_MSG ("resize", 1, "queue item list is empty"); + } + + while (layout != NULL && layout->queueQueueResizeList->size () > 0) { + DBG_IF_RTFL { + DBG_OBJ_MSGF ("resize", 1, "queue item list has %d elements:", + layout->queueQueueResizeList->size ()); +#if 0 + // TODO This worked when queueQueueResizeList was a Vector; now, + // iterators should be used. + DBG_OBJ_MSG_START (); + for (int i = 0; i < layout->queueQueueResizeList->size (); i++) { + DBG_OBJ_MSGF + ("resize", 1, + "#%d: widget = %p, ref = %d, extremesChanged = %s, " + "fast = %s", + i, layout->queueQueueResizeList->get(i)->widget, + layout->queueQueueResizeList->get(i)->ref, + layout->queueQueueResizeList->get(i)->extremesChanged ? + "true" : "false", + layout->queueQueueResizeList->get(i)->fast ? + "true" : "false"); + } + DBG_OBJ_MSG_END (); + DBG_OBJ_MSG ("resize", 1, "taking #0 out of list"); +#endif + } + + Layout::QueueResizeItem *item = + layout->queueQueueResizeList->getTop (); + item->widget->actualQueueResize (item->ref, item->extremesChanged, + item->fast); + layout->queueQueueResizeList->pop (); + } + } + + DBG_OBJ_LEAVE (); +} + +void Widget::actualQueueResize (int ref, bool extremesChanged, bool fast) +{ + assert (!queueResizeEntered ()); + + DBG_OBJ_ENTER ("resize", 0, "actualQueueResize", "%d, %s, %s", + ref, extremesChanged ? "true" : "false", + fast ? "true" : "false"); + + enterQueueResize (); + Widget *widget2, *child; - //printf("The %stop-level %s %p with parentRef = %d has changed its size.\n", - // parent ? "non-" : "", getClassName(), this, parentRef); + Flags resizeFlag, extremesFlag; + + if (layout) { + // If RESIZE_QUEUED is set, this widget is already in the list. + if (!resizeQueued ()) + layout->queueResizeList->put (this); - setFlags (NEEDS_RESIZE); - setFlags (NEEDS_ALLOCATE); + resizeFlag = RESIZE_QUEUED; + extremesFlag = EXTREMES_QUEUED; + } else { + resizeFlag = NEEDS_RESIZE; + extremesFlag = EXTREMES_CHANGED; + } + + setFlags (resizeFlag); + setFlags (ALLOCATE_QUEUED); markSizeChange (ref); if (extremesChanged) { - setFlags (EXTREMES_CHANGED); + setFlags (extremesFlag); markExtremesChange (ref); } - for (widget2 = parent, child = this; - widget2; - child = widget2, widget2 = widget2->parent) { - widget2->setFlags (NEEDS_RESIZE); - widget2->markSizeChange (child->parentRef); - widget2->setFlags (NEEDS_ALLOCATE); - - //printf (" Setting DW_NEEDS_RESIZE and NEEDS_ALLOCATE for the " - // "%stop-level %s %p with parentRef = %d\n", - // widget2->parent ? "non-" : "", widget2->getClassName(), widget2, - // widget2->parentRef); - - if (extremesChanged) { - widget2->setFlags (EXTREMES_CHANGED); - widget2->markExtremesChange (child->parentRef); + if (fast) { + if (parent) { + // In this case, queueResize is called from top (may be a + // random entry point) to bottom, so markSizeChange and + // markExtremesChange have to be called explicitly for the + // parent. The tests (needsResize etc.) are uses to check + // whether queueResize has been called for the parent, or + // whether this widget is the enty point. + if (parent->needsResize () || parent->resizeQueued ()) + parent->markSizeChange (parentRef); + if (parent->extremesChanged () || parent->extremesQueued ()) + parent->markExtremesChange (parentRef); } + } else { + for (widget2 = parent, child = this; widget2; + child = widget2, widget2 = widget2->parent) { + if (layout && !widget2->resizeQueued ()) + layout->queueResizeList->put (widget2); + + DBG_OBJ_MSGF ("resize", 2, "setting %s and ALLOCATE_QUEUED for %p", + resizeFlag == RESIZE_QUEUED ? + "RESIZE_QUEUED" : "NEEDS_RESIZE", + widget2); + + widget2->setFlags (resizeFlag); + widget2->markSizeChange (child->parentRef); + widget2->setFlags (ALLOCATE_QUEUED); + + if (extremesChanged) { + widget2->setFlags (extremesFlag); + widget2->markExtremesChange (child->parentRef); + } + } + + if (layout) + layout->queueResize (extremesChanged); } - if (layout) - layout->queueResize (); + leaveQueueResize (); + + DBG_OBJ_LEAVE (); +} + +void Widget::containerSizeChanged () +{ + DBG_OBJ_ENTER0 ("resize", 0, "containerSizeChanged"); + + // If there is a container widget (not the viewport), which has not + // changed its size (which can be determined by the respective + // flags: this method is called recursively), this widget will + // neither change its size. Also, the recursive iteration can be + // stopped, since the children of this widget will + if (container == NULL || + container->needsResize () || container->resizeQueued () || + container->extremesChanged () || container->extremesQueued ()) { + // Viewport (container == NULL) or container widget has changed + // its size. + if (affectedByContainerSizeChange ()) + queueResizeFast (0, true); + + // Even if *this* widget is not affected, children may be, so + // iterate over children. + containerSizeChangedForChildren (); + } + + DBG_OBJ_LEAVE (); +} + +bool Widget::affectedByContainerSizeChange () +{ + DBG_OBJ_ENTER0 ("resize", 0, "affectedByContainerSizeChange"); + + bool ret; + + // This standard implementation is suitable for all widgets which + // call correctRequisition() and correctExtremes(), even in the way + // how Textblock and Image do (see comments there). Has to be kept + // in sync. + + if (container == NULL) { + if (style::isAbsLength (getStyle()->width) && + style::isAbsLength (getStyle()->height)) + // Both absolute, i. e. fixed: no dependency. + ret = false; + else if (style::isPerLength (getStyle()->width) || + style::isPerLength (getStyle()->height)) { + // Any percentage: certainly dependenant. + ret = true; + } else + // One or both is "auto": depends ... + ret = + (getStyle()->width == style::LENGTH_AUTO ? + usesAvailWidth () : false) || + (getStyle()->height == style::LENGTH_AUTO ? + usesAvailHeight () : false); + } else + ret = container->affectsSizeChangeContainerChild (this); + + DBG_OBJ_MSGF ("resize", 1, "=> %s", ret ? "true" : "false"); + DBG_OBJ_LEAVE (); + return ret; +} + +bool Widget::affectsSizeChangeContainerChild (Widget *child) +{ + DBG_OBJ_ENTER ("resize", 0, "affectsSizeChangeContainerChild", "%p", child); + + bool ret; + + // From the point of view of the container. This standard + // implementation should be suitable for most (if not all) + // containers. + + if (style::isAbsLength (child->getStyle()->width) && + style::isAbsLength (child->getStyle()->height)) + // Both absolute, i. e. fixed: no dependency. + ret = false; + else if (style::isPerLength (child->getStyle()->width) || + style::isPerLength (child->getStyle()->height)) { + // Any percentage: certainly dependenant. + ret = true; + } else + // One or both is "auto": depends ... + ret = + (child->getStyle()->width == style::LENGTH_AUTO ? + child->usesAvailWidth () : false) || + (child->getStyle()->height == style::LENGTH_AUTO ? + child->usesAvailHeight () : false); + + DBG_OBJ_MSGF ("resize", 1, "=> %s", ret ? "true" : "false"); + DBG_OBJ_LEAVE (); + return ret; +} + +void Widget::containerSizeChangedForChildren () +{ + DBG_OBJ_ENTER0 ("resize", 0, "containerSizeChangedForChildren"); + + // Working, but inefficient standard implementation. + Iterator *it = iterator ((Content::Type)(Content::WIDGET_IN_FLOW | + Content::WIDGET_OOF_CONT), + false); + while (it->next ()) + it->getContent()->widget->containerSizeChanged (); + it->unref (); + + DBG_OBJ_LEAVE (); +} + +/** + * \brief Must be implemengted by a method returning true, when + * getAvailWidth() is called. + */ +bool Widget::usesAvailWidth () +{ + return false; } +/** + * \brief Must be implemengted by a method returning true, when + * getAvailHeight() is called. + */ +bool Widget::usesAvailHeight () +{ + return false; +} /** * \brief This method is a wrapper for Widget::sizeRequestImpl(); it calls @@ -210,6 +474,21 @@ void Widget::queueResize (int ref, bool extremesChanged) */ void Widget::sizeRequest (Requisition *requisition) { + assert (!queueResizeEntered ()); + + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest"); + + enterSizeRequest (); + + if (resizeQueued ()) { + // This method is called outside of Layout::resizeIdle. + setFlags (NEEDS_RESIZE); + unsetFlags (RESIZE_QUEUED); + // The widget is not taken out of Layout::queueResizeList, since + // other *_QUEUED flags may still be set and processed in + // Layout::resizeIdle. + } + if (needsResize ()) { /** \todo Check requisition == &(this->requisition) and do what? */ sizeRequestImpl (requisition); @@ -221,6 +500,377 @@ void Widget::sizeRequest (Requisition *requisition) DBG_OBJ_SET_NUM ("requisition.descent", requisition->descent); } else *requisition = this->requisition; + + leaveSizeRequest (); + + DBG_OBJ_LEAVE (); +} + +/** + * \brief Used to evaluate Widget::adjustMinWidth. + * + * If extremes == NULL, getExtremes is called. ForceValue is the same + * value passed to getAvailWidth etc.; if false, getExtremes is not + * called. + */ +int Widget::getMinWidth (Extremes *extremes, bool forceValue) +{ + DBG_OBJ_ENTER ("resize", 0, "getMinWidth", "..., %s", + forceValue ? "true" : "false"); + int minWidth; + + if (getAdjustMinWidth ()) { + Extremes extremes2; + if (extremes == NULL) { + if (forceValue) { + getExtremes (&extremes2); + extremes = &extremes2; + } + } + + // TODO Not completely clear whether this is feasable: Within + // the context of getAvailWidth(false) etc., getExtremes may not + // be called. We ignore the minimal width then. + minWidth = extremes ? extremes->minWidthIntrinsic : 0; + } else + minWidth = 0; + + DBG_OBJ_MSGF ("resize", 1, "=> %d", minWidth); + DBG_OBJ_LEAVE (); + + return minWidth; +} + +/** + * Return available width including margin/border/padding + * (extraSpace?), not only the content width. + */ +int Widget::getAvailWidth (bool forceValue) +{ + DBG_OBJ_ENTER ("resize", 0, "getAvailWidth", "%s", + forceValue ? "true" : "false"); + + int width; + + if (parent == NULL && quasiParent == NULL) { + DBG_OBJ_MSG ("resize", 1, "no parent, regarding viewport"); + DBG_OBJ_MSG_START (); + + // TODO Consider nested layouts (e. g. <button>). + + int viewportWidth = + layout->viewportWidth - (layout->canvasHeightGreater ? + layout->vScrollbarThickness : 0); + width = -1; + calcFinalWidth (getStyle (), viewportWidth, NULL, 0, forceValue, &width); + if (width == -1) + width = viewportWidth; + + DBG_OBJ_MSG_END (); + } else if (parent) { + DBG_OBJ_MSG ("resize", 1, "delegated to parent"); + DBG_OBJ_MSG_START (); + width = parent->getAvailWidthOfChild (this, forceValue); + DBG_OBJ_MSG_END (); + } else /* if (quasiParent) */ { + DBG_OBJ_MSG ("resize", 1, "delegated to quasiParent"); + DBG_OBJ_MSG_START (); + width = quasiParent->getAvailWidthOfChild (this, forceValue); + DBG_OBJ_MSG_END (); + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d", width); + DBG_OBJ_LEAVE (); + + return width; +} + +/** + * Return available height including margin/border/padding + * (extraSpace?), not only the content height. + */ +int Widget::getAvailHeight (bool forceValue) +{ + // TODO Correct by ... not extremes, but ...? (Height extremes?) + + // TODO Consider 'min-height' and 'max-height'. (Minor priority, as long as + // "getAvailHeight (true)" is not used. + + DBG_OBJ_ENTER ("resize", 0, "getAvailHeight", "%s", + forceValue ? "true" : "false"); + + int height; + + if (parent == NULL && quasiParent == NULL) { + DBG_OBJ_MSG ("resize", 1, "no parent, regarding viewport"); + DBG_OBJ_MSG_START (); + + // TODO Consider nested layouts (e. g. <button>). + if (style::isAbsLength (getStyle()->height)) { + DBG_OBJ_MSGF ("resize", 1, "absolute height: %dpx", + style::absLengthVal (getStyle()->height)); + height = style::absLengthVal (getStyle()->height) + boxDiffHeight (); + } else if (style::isPerLength (getStyle()->height)) { + DBG_OBJ_MSGF ("resize", 1, "percentage height: %g%%", + 100 * style::perLengthVal_useThisOnlyForDebugging + (getStyle()->height)); + // Notice that here -- unlike getAvailWidth() -- + // layout->hScrollbarThickness is not considered here; + // something like canvasWidthGreater (analogue to + // canvasHeightGreater) would be complicated and lead to + // possibly contradictory self-references. + height = applyPerHeight (layout->viewportHeight, getStyle()->height); + } else { + DBG_OBJ_MSG ("resize", 1, "no specification"); + height = layout->viewportHeight; + } + + DBG_OBJ_MSG_END (); + } else if (parent) { + DBG_OBJ_MSG ("resize", 1, "delegated to parent"); + DBG_OBJ_MSG_START (); + height = parent->getAvailHeightOfChild (this, forceValue); + DBG_OBJ_MSG_END (); + } else /* if (quasiParent) */ { + DBG_OBJ_MSG ("resize", 1, "delegated to quasiParent"); + DBG_OBJ_MSG_START (); + height = quasiParent->getAvailHeightOfChild (this, forceValue); + DBG_OBJ_MSG_END (); + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d", height); + DBG_OBJ_LEAVE (); + + return height; +} + +void Widget::correctRequisition (Requisition *requisition, + void (*splitHeightFun) (int, int *, int *)) +{ + // TODO Correct height by ... not extremes, but ...? (Height extremes?) + + DBG_OBJ_ENTER ("resize", 0, "correctRequisition", "%d * (%d + %d), ...", + requisition->width, requisition->ascent, + requisition->descent); + + if (parent == NULL && quasiParent == NULL) { + DBG_OBJ_MSG ("resize", 1, "no parent, regarding viewport"); + DBG_OBJ_MSG_START (); + + int limitMinWidth = getMinWidth (NULL, true); + int viewportWidth = + layout->viewportWidth - (layout->canvasHeightGreater ? + layout->vScrollbarThickness : 0); + calcFinalWidth (getStyle (), viewportWidth, NULL, limitMinWidth, false, + &requisition->width); + + // For layout->viewportHeight, see comment in getAvailHeight(). + int height = calcHeight (getStyle()->height, false, + layout->viewportHeight, NULL, false); + int minHeight = calcHeight (getStyle()->minHeight, false, + layout->viewportHeight, NULL, false); + int maxHeight = calcHeight (getStyle()->maxHeight, false, + layout->viewportHeight, NULL, false); + + // TODO Perhaps split first, then add box ascent and descent. + if (height != -1) + splitHeightFun (height, &requisition->ascent, &requisition->descent); + if (minHeight != -1 && + requisition->ascent + requisition->descent < minHeight) + splitHeightFun (minHeight, &requisition->ascent, + &requisition->descent); + if (maxHeight != -1 && + requisition->ascent + requisition->descent > maxHeight) + splitHeightFun (maxHeight, &requisition->ascent, + &requisition->descent); + + DBG_OBJ_MSG_END (); + } else if (parent) { + DBG_OBJ_MSG ("resize", 1, "delegated to parent"); + DBG_OBJ_MSG_START (); + parent->correctRequisitionOfChild (this, requisition, splitHeightFun); + DBG_OBJ_MSG_END (); + } else /* if (quasiParent) */ { + DBG_OBJ_MSG ("resize", 1, "delegated to quasiParent"); + DBG_OBJ_MSG_START (); + quasiParent->correctRequisitionOfChild (this, requisition, + splitHeightFun); + DBG_OBJ_MSG_END (); + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d * (%d + %d)", + requisition->width, requisition->ascent, + requisition->descent); + DBG_OBJ_LEAVE (); +} + +void Widget::correctExtremes (Extremes *extremes) +{ + DBG_OBJ_ENTER ("resize", 0, "correctExtremes", "%d (%d) / %d (%d)", + extremes->minWidth, extremes->minWidthIntrinsic, + extremes->maxWidth, extremes->maxWidthIntrinsic); + + if (container == NULL && quasiParent == NULL) { + DBG_OBJ_MSG ("resize", 1, "no parent, regarding viewport"); + DBG_OBJ_MSG_START (); + + int limitMinWidth = getMinWidth (extremes, false); + int viewportWidth = + layout->viewportWidth - (layout->canvasHeightGreater ? + layout->vScrollbarThickness : 0); + + int width = calcWidth (getStyle()->width, viewportWidth, NULL, + limitMinWidth, false); + int minWidth = calcWidth (getStyle()->minWidth, viewportWidth, NULL, + limitMinWidth, false); + int maxWidth = calcWidth (getStyle()->maxWidth, viewportWidth, NULL, + limitMinWidth, false); + + DBG_OBJ_MSGF ("resize", 1, "width = %d, minWidth = %d, maxWidth = %d", + width, minWidth, maxWidth); + + if (width != -1) + extremes->minWidth = extremes->maxWidth = width; + if (minWidth != -1) + extremes->minWidth = minWidth; + if (maxWidth != -1) + extremes->maxWidth = maxWidth; + + DBG_OBJ_MSG_END (); + } else if (parent) { + DBG_OBJ_MSG ("resize", 1, "delegated to parent"); + DBG_OBJ_MSG_START (); + parent->correctExtremesOfChild (this, extremes); + DBG_OBJ_MSG_END (); + } else /* if (quasiParent) */ { + DBG_OBJ_MSG ("resize", 1, "delegated to quasiParent"); + DBG_OBJ_MSG_START (); + quasiParent->correctExtremesOfChild (this, extremes); + DBG_OBJ_MSG_END (); + } + + if (extremes->maxWidth < extremes->minWidth) + extremes->maxWidth = extremes->minWidth; + + DBG_OBJ_MSGF ("resize", 1, "=> %d / %d", + extremes->minWidth, extremes->maxWidth); + DBG_OBJ_LEAVE (); +} + +int Widget::calcWidth (style::Length cssValue, int refWidth, Widget *refWidget, + int limitMinWidth, bool forceValue) +{ + DBG_OBJ_ENTER ("resize", 0, "calcWidth", "0x%x, %d, %p, %d", + cssValue, refWidth, refWidget, limitMinWidth); + + assert (refWidth != -1 || refWidget != NULL); + + int width; + + if (style::isAbsLength (cssValue)) { + DBG_OBJ_MSGF ("resize", 1, "absolute width: %dpx", + style::absLengthVal (cssValue)); + width = misc::max (style::absLengthVal (cssValue) + boxDiffWidth (), + limitMinWidth); + } else if (style::isPerLength (cssValue)) { + DBG_OBJ_MSGF ("resize", 1, "percentage width: %g%%", + 100 * style::perLengthVal_useThisOnlyForDebugging + (cssValue)); + if (refWidth != -1) + width = misc::max (applyPerWidth (refWidth, cssValue), limitMinWidth); + else { + int availWidth = refWidget->getAvailWidth (forceValue); + if (availWidth != -1) { + int containerWidth = availWidth - refWidget->boxDiffWidth (); + width = misc::max (applyPerWidth (containerWidth, cssValue), + limitMinWidth); + } else + width = -1; + } + } else { + DBG_OBJ_MSG ("resize", 1, "not specified"); + width = -1; + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d", width); + DBG_OBJ_LEAVE (); + return width; +} + +// *finalWidth may be -1. +void Widget::calcFinalWidth (style::Style *style, int refWidth, + Widget *refWidget, int limitMinWidth, + bool forceValue, int *finalWidth) +{ + DBG_OBJ_ENTER ("resize", 0, "calcFinalWidth", "..., %d, %p, %d, [%d]", + refWidth, refWidget, limitMinWidth, *finalWidth); + + int width = calcWidth (style->width, refWidth, refWidget, limitMinWidth, + forceValue); + int minWidth = calcWidth (style->minWidth, refWidth, refWidget, + limitMinWidth, forceValue); + int maxWidth = calcWidth (style->maxWidth, refWidth, refWidget, + limitMinWidth, forceValue); + + DBG_OBJ_MSGF ("resize", 1, "width = %d, minWidth = %d, maxWidth = %d", + width, minWidth, maxWidth); + + if (width != -1) + *finalWidth = width; + if (minWidth != -1 && *finalWidth != -1 && *finalWidth < minWidth) + *finalWidth = minWidth; + if (maxWidth != -1 && *finalWidth == -1 && *finalWidth > maxWidth) + *finalWidth = maxWidth; + + DBG_OBJ_MSGF ("resize", 1, "=> %d", *finalWidth); + DBG_OBJ_LEAVE (); +} + +int Widget::calcHeight (style::Length cssValue, bool usePercentage, + int refHeight, Widget *refWidget, bool forceValue) +{ + // TODO Search for usage of this method and check the value of + // "usePercentage"; this has to be clarified. + + DBG_OBJ_ENTER ("resize", 0, "calcHeight", "0x%x, %s, %d, %p", + cssValue, usePercentage ? "true" : "false", refHeight, + refWidget); + + assert (refHeight != -1 || refWidget != NULL); + + int height; + + if (style::isAbsLength (cssValue)) { + DBG_OBJ_MSGF ("resize", 1, "absolute height: %dpx", + style::absLengthVal (cssValue)); + height = + misc::max (style::absLengthVal (cssValue) + boxDiffHeight (), 0); + } else if (style::isPerLength (cssValue)) { + DBG_OBJ_MSGF ("resize", 1, "percentage height: %g%%", + 100 * + style::perLengthVal_useThisOnlyForDebugging (cssValue)); + if (usePercentage) { + if (refHeight != -1) + height = misc::max (applyPerHeight (refHeight, cssValue), 0); + else { + int availHeight = refWidget->getAvailHeight (forceValue); + if (availHeight != -1) { + int containerHeight = availHeight - refWidget->boxDiffHeight (); + height = + misc::max (applyPerHeight (containerHeight, cssValue), 0); + } else + height = -1; + } + } else + height = -1; + } else { + DBG_OBJ_MSG ("resize", 1, "not specified"); + height = -1; + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d", height); + DBG_OBJ_LEAVE (); + return height; } /** @@ -228,15 +878,48 @@ void Widget::sizeRequest (Requisition *requisition) */ void Widget::getExtremes (Extremes *extremes) { + assert (!queueResizeEntered ()); + + DBG_OBJ_ENTER0 ("resize", 0, "getExtremes"); + + enterGetExtremes (); + + if (extremesQueued ()) { + // This method is called outside of Layout::resizeIdle. + setFlags (EXTREMES_CHANGED); + unsetFlags (EXTREMES_QUEUED); + // The widget is not taken out of Layout::queueResizeList, since + // other *_QUEUED flags may still be set and processed in + // Layout::resizeIdle. + } + if (extremesChanged ()) { + // For backward compatibility (part 1/2): + extremes->minWidthIntrinsic = extremes->maxWidthIntrinsic = -1; + getExtremesImpl (extremes); + + // For backward compatibility (part 2/2): + if (extremes->minWidthIntrinsic == -1) + extremes->minWidthIntrinsic = extremes->minWidth; + if (extremes->maxWidthIntrinsic == -1) + extremes->maxWidthIntrinsic = extremes->maxWidth; + this->extremes = *extremes; unsetFlags (EXTREMES_CHANGED); DBG_OBJ_SET_NUM ("extremes.minWidth", extremes->minWidth); + DBG_OBJ_SET_NUM ("extremes.minWidthIntrinsic", + extremes->minWidthIntrinsic); DBG_OBJ_SET_NUM ("extremes.maxWidth", extremes->maxWidth); + DBG_OBJ_SET_NUM ("extremes.maxWidthIntrinsic", + extremes->maxWidthIntrinsic); } else *extremes = this->extremes; + + leaveGetExtremes (); + + DBG_OBJ_LEAVE (); } /** @@ -245,6 +928,33 @@ void Widget::getExtremes (Extremes *extremes) */ void Widget::sizeAllocate (Allocation *allocation) { + assert (!queueResizeEntered ()); + assert (!sizeRequestEntered ()); + assert (!getExtremesEntered ()); + assert (resizeIdleEntered ()); + + DBG_OBJ_ENTER ("resize", 0, "sizeAllocate", "%d, %d; %d * (%d + %d)", + allocation->x, allocation->y, allocation->width, + allocation->ascent, allocation->descent); + + DBG_OBJ_MSGF ("resize", 1, + "old allocation (%d, %d; %d * (%d + %d)); needsAllocate: %s", + this->allocation.x, this->allocation.y, this->allocation.width, + this->allocation.ascent, this->allocation.descent, + needsAllocate () ? "true" : "false"); + + enterSizeAllocate (); + + /*printf ("The %stop-level %s %p is allocated:\n", + parent ? "non-" : "", getClassName(), this); + printf (" old = (%d, %d, %d + (%d + %d))\n", + this->allocation.x, this->allocation.y, this->allocation.width, + this->allocation.ascent, this->allocation.descent); + printf (" new = (%d, %d, %d + (%d + %d))\n", + allocation->x, allocation->y, allocation->width, allocation->ascent, + allocation->descent); + printf (" NEEDS_ALLOCATE = %s\n", needsAllocate () ? "true" : "false");*/ + if (needsAllocate () || allocation->x != this->allocation.x || allocation->y != this->allocation.y || @@ -285,6 +995,10 @@ void Widget::sizeAllocate (Allocation *allocation) } /*unsetFlags (NEEDS_RESIZE);*/ + + leaveSizeAllocate (); + + DBG_OBJ_LEAVE (); } bool Widget::buttonPress (EventButton *event) @@ -356,6 +1070,59 @@ void Widget::setStyle (style::Style *style) queueResize (0, true); else queueDraw (); + + // These should better be attributed to the style itself, and a + // script processing RTFL messages could transfer it to something + // equivalent: + + DBG_OBJ_SET_NUM ("style.margin.top", style->margin.top); + DBG_OBJ_SET_NUM ("style.margin.bottom", style->margin.bottom); + DBG_OBJ_SET_NUM ("style.margin.left", style->margin.left); + DBG_OBJ_SET_NUM ("style.margin.right", style->margin.right); + + DBG_OBJ_SET_NUM ("style.border-width.top", style->borderWidth.top); + DBG_OBJ_SET_NUM ("style.border-width.bottom", style->borderWidth.bottom); + DBG_OBJ_SET_NUM ("style.border-width.left", style->borderWidth.left); + DBG_OBJ_SET_NUM ("style.border-width.right", style->borderWidth.right); + + DBG_OBJ_SET_NUM ("style.padding.top", style->padding.top); + DBG_OBJ_SET_NUM ("style.padding.bottom", style->padding.bottom); + DBG_OBJ_SET_NUM ("style.padding.left", style->padding.left); + DBG_OBJ_SET_NUM ("style.padding.right", style->padding.right); + + DBG_OBJ_SET_NUM ("style.border-spacing (h)", style->hBorderSpacing); + DBG_OBJ_SET_NUM ("style.border-spacing (v)", style->vBorderSpacing); + + DBG_OBJ_SET_SYM ("style.display", + style->display == style::DISPLAY_BLOCK ? "block" : + style->display == style::DISPLAY_INLINE ? "inline" : + style->display == style::DISPLAY_INLINE_BLOCK ? + "inline-block" : + style->display == style::DISPLAY_LIST_ITEM ? "list-item" : + style->display == style::DISPLAY_NONE ? "none" : + style->display == style::DISPLAY_TABLE ? "table" : + style->display == style::DISPLAY_TABLE_ROW_GROUP ? + "table-row-group" : + style->display == style::DISPLAY_TABLE_HEADER_GROUP ? + "table-header-group" : + style->display == style::DISPLAY_TABLE_FOOTER_GROUP ? + "table-footer-group" : + style->display == style::DISPLAY_TABLE_ROW ? "table-row" : + style->display == style::DISPLAY_TABLE_CELL ? "table-cell" : + "???"); + + DBG_OBJ_SET_NUM ("style.width (raw)", style->width); + DBG_OBJ_SET_NUM ("style.min-width (raw)", style->minWidth); + DBG_OBJ_SET_NUM ("style.max-width (raw)", style->maxWidth); + DBG_OBJ_SET_NUM ("style.height (raw)", style->height); + DBG_OBJ_SET_NUM ("style.min-height (raw)", style->minHeight); + DBG_OBJ_SET_NUM ("style.max-height (raw)", style->maxHeight); + + if (style->backgroundColor) + DBG_OBJ_SET_COL ("style.background-color", + style->backgroundColor->getColor ()); + else + DBG_OBJ_SET_SYM ("style.background-color", "transparent"); } /** @@ -411,6 +1178,10 @@ void Widget::drawBox (View *view, style::Style *style, Rectangle *area, // does not define what here is called "reference area". To make it look // smoothly, the widget padding box is used. + // TODO Handle inverse drawing the same way as in drawWidgetBox? + // Maybe this method (drawBox) is anyway obsolete when extraSpace + // is fully supported (as in the "dillo_grows" repository). + int xPad, yPad, widthPad, heightPad; getPaddingArea (&xPad, &yPad, &widthPad, &heightPad); style::drawBackground @@ -421,7 +1192,8 @@ void Widget::drawBox (View *view, style::Style *style, Rectangle *area, - style->margin.right - style->borderWidth.right, height - style->margin.top - style->borderWidth.top - style->margin.bottom - style->borderWidth.bottom, - xPad, yPad, widthPad, heightPad, style, inverse, false); + xPad, yPad, widthPad, heightPad, style, style->backgroundColor, + inverse, false); } /** @@ -443,10 +1215,26 @@ void Widget::drawWidgetBox (View *view, Rectangle *area, bool inverse) int xPad, yPad, widthPad, heightPad; getPaddingArea (&xPad, &yPad, &widthPad, &heightPad); + + style::Color *bgColor; + if (inverse && style->backgroundColor == NULL) { + // See style::drawBackground: for inverse drawing, we need a + // defined background color. Search through ancestors. + Widget *w = this; + while (w != NULL && w->style->backgroundColor == NULL) + w = w->parent; + + if (w != NULL && w->style->backgroundColor != NULL) + bgColor = w->style->backgroundColor; + else + bgColor = layout->getBgColor (); + } else + bgColor = style->backgroundColor; + style::drawBackground (view, layout, &canvasArea, xPad, yPad, widthPad, heightPad, xPad, yPad, widthPad, heightPad, - style, inverse, parent == NULL); + style, bgColor, inverse, parent == NULL); } /* @@ -507,6 +1295,25 @@ int Widget::getLevel () } /** + * \brief Get the level of the widget within the tree, regarting the + * generators, not the parents. + * + * The root widget has the level 0. + */ +int Widget::getGeneratorLevel () +{ + Widget *widget = this; + int level = 0; + + while (widget->getGenerator ()) { + level++; + widget = widget->getGenerator (); + } + + return level; +} + +/** * \brief Get the widget with the highest level, which is a direct ancestor of * widget1 and widget2. */ @@ -566,11 +1373,15 @@ Widget *Widget::getWidgetAtPoint (int x, int y, int level) * is such a child, it is returned. Otherwise, this widget is returned. */ childAtPoint = NULL; - it = iterator (Content::WIDGET, false); - - while (childAtPoint == NULL && it->next ()) - childAtPoint = it->getContent()->widget->getWidgetAtPoint (x, y, - level + 1); + it = iterator ((Content::Type) + (Content::WIDGET_IN_FLOW | Content::WIDGET_OOF_CONT), + false); + + while (childAtPoint == NULL && it->next ()) { + Widget *child = it->getContent()->widget; + if (child->wasAllocated ()) + childAtPoint = child->getWidgetAtPoint (x, y, level + 1); + } it->unref (); @@ -603,18 +1414,10 @@ void Widget::getPaddingArea (int *xPad, int *yPad, int *widthPad, *yPad = allocation.y + style->margin.top + style->borderWidth.top; *widthPad = allocation.width - style->margin.left - style->borderWidth.left - style->margin.right - style->borderWidth.right; - *heightPad = getHeight () - style->margin.top - style->borderWidth.top + *heightPad = getHeight () - style->margin.top - style->borderWidth.top - style->margin.bottom - style->borderWidth.bottom; } -void Widget::getExtremesImpl (Extremes *extremes) -{ - /* Simply return the requisition width */ - Requisition requisition; - sizeRequest (&requisition); - extremes->minWidth = extremes->maxWidth = requisition.width; -} - void Widget::sizeAllocateImpl (Allocation *allocation) { } @@ -627,16 +1430,277 @@ void Widget::markExtremesChange (int ref) { } -void Widget::setWidth (int width) +int Widget::applyPerWidth (int containerWidth, style::Length perWidth) +{ + return style::multiplyWithPerLength (containerWidth, perWidth) + + boxDiffWidth (); +} + +int Widget::applyPerHeight (int containerHeight, style::Length perHeight) +{ + return style::multiplyWithPerLength (containerHeight, perHeight) + + boxDiffHeight (); +} + +int Widget::getAvailWidthOfChild (Widget *child, bool forceValue) +{ + // This is a halfway suitable implementation for all + // containers. For simplification, this will be used during the + // development; then, a differentiation could be possible. + + DBG_OBJ_ENTER ("resize", 0, "getAvailWidthOfChild", "%p, %s", + child, forceValue ? "true" : "false"); + + int width; + + if (child->getStyle()->width == style::LENGTH_AUTO) { + DBG_OBJ_MSG ("resize", 1, "no specification"); + if (forceValue) + width = misc::max (getAvailWidth (true) - boxDiffWidth (), 0); + else + width = -1; + } else { + // In most cases, the toplevel widget should be a container, so + // the container is non-NULL when the parent is non-NULL. Just + // in case, regard also parent. And quasiParent. + Widget *effContainer = child->quasiParent ? child->quasiParent : + (child->container ? child->container : child->parent); + + if (effContainer == this) { + width = -1; + child->calcFinalWidth (child->getStyle(), -1, this, 0, forceValue, + &width); + } else { + DBG_OBJ_MSG ("resize", 1, "delegated to (effective) container"); + DBG_OBJ_MSG_START (); + width = effContainer->getAvailWidthOfChild (child, forceValue); + DBG_OBJ_MSG_END (); + } + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d", width); + DBG_OBJ_LEAVE (); + + return width; +} + +int Widget::getAvailHeightOfChild (Widget *child, bool forceValue) +{ + // Again, a suitable implementation for all widgets (perhaps). + + // TODO Consider 'min-height' and 'max-height'. (Minor priority, as long as + // "getAvailHeight (true)" is not used. + + DBG_OBJ_ENTER ("resize", 0, "getAvailHeightOfChild", "%p, %s", + child, forceValue ? "true" : "false"); + + int height; + + if (child->getStyle()->height == style::LENGTH_AUTO) { + DBG_OBJ_MSG ("resize", 1, "no specification"); + if (forceValue) + height = misc::max (getAvailHeight (true) - boxDiffHeight (), 0); + else + height = -1; + } else { + // See comment in Widget::getAvailWidthOfChild. + Widget *effContainer = child->quasiParent ? child->quasiParent : + (child->container ? child->container : child->parent); + + if (effContainer == this) { + if (style::isAbsLength (child->getStyle()->height)) { + DBG_OBJ_MSGF ("resize", 1, "absolute height: %dpx", + style::absLengthVal (child->getStyle()->height)); + height = misc::max (style::absLengthVal (child->getStyle()->height) + + child->boxDiffHeight (), 0); + } else { + assert (style::isPerLength (child->getStyle()->height)); + DBG_OBJ_MSGF ("resize", 1, "percentage height: %g%%", + 100 * style::perLengthVal_useThisOnlyForDebugging + (child->getStyle()->height)); + + int availHeight = getAvailHeight (forceValue); + if (availHeight == -1) + height = -1; + else + height = + misc::max (child->applyPerHeight (availHeight - + boxDiffHeight (), + child->getStyle()->height), + 0); + } + } else { + DBG_OBJ_MSG ("resize", 1, "delegated to (effective) container"); + DBG_OBJ_MSG_START (); + height = effContainer->getAvailHeightOfChild (child, forceValue); + DBG_OBJ_MSG_END (); + } + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d", height); + DBG_OBJ_LEAVE (); + + return height; +} + +void Widget::correctRequisitionOfChild (Widget *child, Requisition *requisition, + void (*splitHeightFun) (int, int*, + int*)) +{ + // Again, a suitable implementation for all widgets (perhaps). + + DBG_OBJ_ENTER ("resize", 0, "correctRequisitionOfChild", + "%p, %d * (%d + %d), ...", child, requisition->width, + requisition->ascent, requisition->descent); + + // See comment in Widget::getAvailWidthOfChild. + Widget *effContainer = child->quasiParent ? child->quasiParent : + (child->container ? child->container : child->parent); + + if (effContainer == this) { + correctReqWidthOfChild (child, requisition); + correctReqHeightOfChild (child, requisition, splitHeightFun); + } else { + DBG_OBJ_MSG ("resize", 1, "delegated to (effective) container"); + DBG_OBJ_MSG_START (); + effContainer->correctRequisitionOfChild (child, requisition, + splitHeightFun); + DBG_OBJ_MSG_END (); + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d * (%d + %d)", + requisition->width, requisition->ascent, + requisition->descent); + DBG_OBJ_LEAVE (); +} + +void Widget::correctReqWidthOfChild (Widget *child, Requisition *requisition) { + DBG_OBJ_ENTER ("resize", 0, "correctReqWidthOfChild", "%p, %d * (%d + %d)", + child, requisition->width, requisition->ascent, + requisition->descent); + + assert (this == child->quasiParent || this == child->container); + + int limitMinWidth = child->getMinWidth (NULL, true); + child->calcFinalWidth (child->getStyle(), -1, this, limitMinWidth, false, + &requisition->width); + + DBG_OBJ_MSGF ("resize", 1, "=> %d * (%d + %d)", + requisition->width, requisition->ascent, + requisition->descent); + DBG_OBJ_LEAVE (); } -void Widget::setAscent (int ascent) +void Widget::correctReqHeightOfChild (Widget *child, Requisition *requisition, + void (*splitHeightFun) (int, int*, int*)) { + // TODO Correct height by extremes? (Height extemes?) + + assert (this == child->quasiParent || this == child->container); + + DBG_OBJ_ENTER ("resize", 0, "correctReqHeightOfChild", + "%p, %d * (%d + %d), ...", child, requisition->width, + requisition->ascent, requisition->descent); + + int height = child->calcHeight (child->getStyle()->height, false, -1, this, + false); + int minHeight = child->calcHeight (child->getStyle()->minHeight, false, -1, + this, false); + int maxHeight = child->calcHeight (child->getStyle()->maxHeight, false, -1, + this, false); + + // TODO Perhaps split first, then add box ascent and descent. + if (height != -1) + splitHeightFun (height, &requisition->ascent, &requisition->descent); + if (minHeight != -1 && + requisition->ascent + requisition->descent < minHeight) + splitHeightFun (minHeight, &requisition->ascent, + &requisition->descent); + if (maxHeight != -1 && + requisition->ascent + requisition->descent > maxHeight) + splitHeightFun (maxHeight, &requisition->ascent, + &requisition->descent); + + DBG_OBJ_MSGF ("resize", 1, "=> %d * (%d + %d)", + requisition->width, requisition->ascent, + requisition->descent); + DBG_OBJ_LEAVE (); } -void Widget::setDescent (int descent) +void Widget::correctExtremesOfChild (Widget *child, Extremes *extremes) { + // See comment in correctRequisitionOfChild. + + DBG_OBJ_ENTER ("resize", 0, "correctExtremesOfChild", + "%p, %d (%d) / %d (%d)", + child, extremes->minWidth, extremes->minWidthIntrinsic, + extremes->maxWidth, extremes->maxWidthIntrinsic); + + // See comment in Widget::getAvailWidthOfChild. + Widget *effContainer = child->quasiParent ? child->quasiParent : + (child->container ? child->container : child->parent); + + if (effContainer == this) { + int limitMinWidth = child->getMinWidth (extremes, false); + int width = child->calcWidth (child->getStyle()->width, -1, this, + limitMinWidth, false); + int minWidth = child->calcWidth (child->getStyle()->minWidth, -1, this, + limitMinWidth, false); + int maxWidth = child->calcWidth (child->getStyle()->maxWidth, -1, this, + limitMinWidth, false); + + DBG_OBJ_MSGF ("resize", 1, "width = %d, minWidth = %d, maxWidth = %d", + width, minWidth, maxWidth); + + if (width != -1) + extremes->minWidth = extremes->maxWidth = width; + if (minWidth != -1) + extremes->minWidth = minWidth; + if (maxWidth != -1) + extremes->maxWidth = maxWidth; + } else { + DBG_OBJ_MSG ("resize", 1, "delegated to (effective) container"); + DBG_OBJ_MSG_START (); + effContainer->correctExtremesOfChild (child, extremes); + DBG_OBJ_MSG_END (); + } + + + DBG_OBJ_MSGF ("resize", 1, "=> %d / %d", + extremes->minWidth, extremes->maxWidth); + DBG_OBJ_LEAVE (); +} + +/** + * \brief This method is called after a widget has been set as the top of a + * widget tree. + * + * A widget may override this method when it is necessary to be notified. + */ +void Widget::notifySetAsTopLevel() +{ +} + +/** + * \brief This method is called after a widget has been added to a parent. + * + * A widget may override this method when it is necessary to be notified. + */ +void Widget::notifySetParent() +{ +} + +bool Widget::isBlockLevel () +{ + // Most widgets are not block-level. + return false; +} + +bool Widget::isPossibleContainer () +{ + // In most (all?) cases identical to: + return isBlockLevel (); } bool Widget::buttonPressImpl (EventButton *event) @@ -656,7 +1720,7 @@ bool Widget::motionNotifyImpl (EventMotion *event) void Widget::enterNotifyImpl (EventCrossing *) { - core::style::Tooltip *tooltip = getStyle()->x_tooltip; + style::Tooltip *tooltip = getStyle()->x_tooltip; if (tooltip) tooltip->onEnter(); @@ -664,7 +1728,7 @@ void Widget::enterNotifyImpl (EventCrossing *) void Widget::leaveNotifyImpl (EventCrossing *) { - core::style::Tooltip *tooltip = getStyle()->x_tooltip; + style::Tooltip *tooltip = getStyle()->x_tooltip; if (tooltip) tooltip->onLeave(); @@ -676,5 +1740,25 @@ void Widget::removeChild (Widget *child) misc::assertNotReached (); } +// ---------------------------------------------------------------------- + +void splitHeightPreserveAscent (int height, int *ascent, int *descent) +{ + *descent = height - *ascent; + if (*descent < 0) { + *descent = 0; + *ascent = height; + } +} + +void splitHeightPreserveDescent (int height, int *ascent, int *descent) +{ + *ascent = height - *descent; + if (*ascent < 0) { + *ascent = 0; + *descent = height; + } +} + } // namespace core } // namespace dw diff --git a/dw/widget.hh b/dw/widget.hh index 34b35efa..0f3e2d37 100644 --- a/dw/widget.hh +++ b/dw/widget.hh @@ -27,51 +27,53 @@ class Widget: public lout::identity::IdentifiableObject protected: enum Flags { /** - * \brief Set, when dw::core::Widget::requisition is not up to date - * anymore. + * \todo Comment this. */ - NEEDS_RESIZE = 1 << 0, + RESIZE_QUEUED = 1 << 0, /** - * \brief Only used internally, set to enforce size allocation. - * - * (I've forgotten the case, for which this is necessary.) + * \todo Comment this. */ - NEEDS_ALLOCATE = 1 << 1, + EXTREMES_QUEUED = 1 << 1, /** - * \brief Set, when dw::core::Widget::extremes is not up to date + * \brief Set, when dw::core::Widget::requisition is not up to date * anymore. + * + * \todo Update, see RESIZE_QUEUED. */ - EXTREMES_CHANGED = 1 << 2, + NEEDS_RESIZE = 1 << 2, /** - * \brief Set by the widget itself (in the constructor), when set... - * methods are implemented. + * \brief Only used internally, set to enforce size allocation. * - * Will hopefully be removed, after redesigning the size model. + * In some cases, the size of a widget remains the same, but the + * children are allocated at different positions and in + * different sizes, so that a simple comparison of old and new + * allocation is insufficient. Therefore, this flag is set + * (indirectly, as ALLOCATE_QUEUED) in queueResize. */ - USES_HINTS = 1 << 3, + NEEDS_ALLOCATE = 1 << 3, /** - * \brief Set by the widget itself (in the constructor), when it contains - * some contents, e.g. an image, as opposed to a horizontal ruler. - * - * Will hopefully be removed, after redesigning the size model. + * \todo Comment this. */ - HAS_CONTENTS = 1 << 4, + ALLOCATE_QUEUED = 1 << 4, /** - * \brief Set, when a widget was already once allocated, + * \brief Set, when dw::core::Widget::extremes is not up to date + * anymore. * - * The dw::Image widget uses this flag, see dw::Image::setBuffer. + * \todo Update, see RESIZE_QUEUED. */ - WAS_ALLOCATED = 1 << 5, + EXTREMES_CHANGED = 1 << 5, /** - * \brief Set for block-level widgets (as opposed to inline widgets) + * \brief Set, when a widget was already once allocated, + * + * The dw::Image widget uses this flag, see dw::Image::setBuffer. */ - BLOCK_LEVEL = 1 << 6, + WAS_ALLOCATED = 1 << 6, }; /** @@ -97,10 +99,32 @@ protected: WidgetImgRenderer *widgetImgRenderer; private: + static bool adjustMinWidth; + /** * \brief The parent widget, NULL for top-level widgets. */ Widget *parent; + + /** + * \brief ... + */ + Widget *quasiParent; + + /** + * \brief The generating widget, NULL for top-level widgets, or if + * not set; in the latter case, the effective generator (see + * getGenerator) is the parent. + */ + Widget *generator; + + /** + * \brief The containing widget, equivalent to the "containing + * block" defined by CSS. May be NULL, in this case the viewport + * is used. + */ + Widget *container; + style::Style *style; Flags flags; @@ -133,6 +157,11 @@ private: */ bool buttonSensitiveSet; + void queueResize (int ref, bool extremesChanged, bool fast); + inline void queueResizeFast (int ref, bool extremesChanged) + { queueResize (ref, extremesChanged, true); } + void actualQueueResize (int ref, bool extremesChanged, bool fast); + public: /** * \brief This value is defined by the parent widget, and used for @@ -158,16 +187,79 @@ protected: Layout *layout; - inline void setFlags (Flags f) { flags = (Flags)(flags | f); } - inline void unsetFlags (Flags f) { flags = (Flags)(flags & ~f); } + /** + * \brief Space around the margin box. Allocation is extraSpace + + * margin + border + padding + contents; + */ + style::Box extraSpace; + + /*inline void printFlags () { + DBG_IF_RTFL { + char buf[10 * 3 - 1 + 1]; + snprintf (buf, sizeof (buf), "%s:%s:%s:%s:%s:%s:%s", + (flags & RESIZE_QUEUED) ? "Rq" : "--", + (flags & EXTREMES_QUEUED) ? "Eq" : "--", + (flags & NEEDS_RESIZE) ? "nR" : "--", + (flags & NEEDS_ALLOCATE) ? "nA" : "--", + (flags & ALLOCATE_QUEUED) ? "Aq" : "--", + (flags & EXTREMES_CHANGED) ? "Ec" : "--", + (flags & WAS_ALLOCATED) ? "wA" : "--"); + DBG_OBJ_SET_SYM ("flags", buf); + } + }*/ + + inline void printFlag (Flags f) { + DBG_IF_RTFL { + switch (f) { + case RESIZE_QUEUED: + DBG_OBJ_SET_SYM ("flags.RESIZE_QUEUED", + (flags & RESIZE_QUEUED) ? "true" : "false"); + break; + + case EXTREMES_QUEUED: + DBG_OBJ_SET_SYM ("flags.EXTREMES_QUEUED", + (flags & EXTREMES_QUEUED) ? "true" : "false"); + break; + + case NEEDS_RESIZE: + DBG_OBJ_SET_SYM ("flags.NEEDS_RESIZE", + (flags & NEEDS_RESIZE) ? "true" : "false"); + break; + + case NEEDS_ALLOCATE: + DBG_OBJ_SET_SYM ("flags.NEEDS_ALLOCATE", + (flags & NEEDS_ALLOCATE) ? "true" : "false"); + break; + + case ALLOCATE_QUEUED: + DBG_OBJ_SET_SYM ("flags.ALLOCATE_QUEUED", + (flags & ALLOCATE_QUEUED) ? "true" : "false"); + break; + + case EXTREMES_CHANGED: + DBG_OBJ_SET_SYM ("flags.EXTREMES_CHANGED", + (flags & EXTREMES_CHANGED) ? "true" : "false"); + break; + + case WAS_ALLOCATED: + DBG_OBJ_SET_SYM ("flags.WAS_ALLOCATED", + (flags & WAS_ALLOCATED) ? "true" : "false"); + break; + } + } + } + + inline void setFlags (Flags f) + { flags = (Flags)(flags | f); printFlag (f); } + inline void unsetFlags (Flags f) + { flags = (Flags)(flags & ~f); printFlag (f); } inline void queueDraw () - { - queueDrawArea (0, 0, allocation.width, getHeight()); - } + { queueDrawArea (0, 0, allocation.width, getHeight()); } void queueDrawArea (int x, int y, int width, int height); - void queueResize (int ref, bool extremesChanged); + inline void queueResize (int ref, bool extremesChanged) + { queueResize (ref, extremesChanged, false); } /** * \brief See \ref dw-widget-sizes. @@ -177,7 +269,7 @@ protected: /** * \brief See \ref dw-widget-sizes. */ - virtual void getExtremesImpl (Extremes *extremes); + virtual void getExtremesImpl (Extremes *extremes) = 0; /** * \brief See \ref dw-widget-sizes. @@ -200,6 +292,29 @@ protected: */ virtual void markExtremesChange (int ref); + int getMinWidth (Extremes *extremes, bool forceValue); + + virtual int getAvailWidthOfChild (Widget *child, bool forceValue); + virtual int getAvailHeightOfChild (Widget *child, bool forceValue); + virtual void correctRequisitionOfChild (Widget *child, + Requisition *requisition, + void (*splitHeightFun) (int, int*, + int*)); + void correctReqWidthOfChild (Widget *child, Requisition *requisition); + void correctReqHeightOfChild (Widget *child, Requisition *requisition, + void (*splitHeightFun) (int, int*, int*)); + virtual void correctExtremesOfChild (Widget *child, Extremes *extremes); + + virtual void containerSizeChangedForChildren (); + + virtual bool affectedByContainerSizeChange (); + virtual bool affectsSizeChangeContainerChild (Widget *child); + virtual bool usesAvailWidth (); + virtual bool usesAvailHeight (); + + virtual void notifySetAsTopLevel(); + virtual void notifySetParent(); + virtual bool buttonPressImpl (EventButton *event); virtual bool buttonReleaseImpl (EventButton *event); virtual bool motionNotifyImpl (EventMotion *event); @@ -216,7 +331,7 @@ protected: { layout->changeAnchor (this, name, y); } inline void removeAnchor (char* name) - { layout->removeAnchor (this, name); } + { if (layout) layout->removeAnchor (this, name); } //inline void updateBgColor () { layout->updateBgColor (); } @@ -249,32 +364,87 @@ public: inline void setDeleteCallback(DW_Callback_t func, void *data) { deleteCallbackFunc = func; deleteCallbackData = data; } +private: + bool resizeIdleEntered () { return layout && layout->resizeIdleCounter; } + + void enterQueueResize () { if (layout) layout->queueResizeCounter++; } + void leaveQueueResize () { if (layout) layout->queueResizeCounter--; } + bool queueResizeEntered () { return layout && layout->queueResizeCounter; } + + void enterSizeAllocate () { if (layout) layout->sizeAllocateCounter++; } + void leaveSizeAllocate () { if (layout) layout->sizeAllocateCounter--; } + bool sizeAllocateEntered () { return layout && layout->sizeAllocateCounter; } + + void enterSizeRequest () { if (layout) layout->sizeRequestCounter++; } + void leaveSizeRequest () { if (layout) layout->sizeRequestCounter--; } + bool sizeRequestEntered () { return layout && layout->sizeRequestCounter; } + + void enterGetExtremes () { if (layout) layout->getExtremesCounter++; } + void leaveGetExtremes () { if (layout) layout->getExtremesCounter--; } + bool getExtremesEntered () { return layout && layout->getExtremesCounter; } + + public: static int CLASS_ID; + inline static void setAdjustMinWidth (bool adjustMinWidth) + { Widget::adjustMinWidth = adjustMinWidth; } + Widget (); ~Widget (); + inline bool resizeQueued () { return flags & RESIZE_QUEUED; } + inline bool extremesQueued () { return flags & EXTREMES_QUEUED; } inline bool needsResize () { return flags & NEEDS_RESIZE; } inline bool needsAllocate () { return flags & NEEDS_ALLOCATE; } + inline bool allocateQueued () { return flags & ALLOCATE_QUEUED; } inline bool extremesChanged () { return flags & EXTREMES_CHANGED; } inline bool wasAllocated () { return flags & WAS_ALLOCATED; } - inline bool usesHints () { return flags & USES_HINTS; } - inline bool hasContents () { return flags & HAS_CONTENTS; } - inline bool blockLevel () { return flags & BLOCK_LEVEL; } void setParent (Widget *parent); + void setQuasiParent (Widget *quasiParent); + + void setGenerator (Widget *generator) { this->generator = generator; } inline style::Style *getStyle () { return style; } /** \todo I do not like this. */ inline Allocation *getAllocation () { return &allocation; } + inline int boxOffsetX () + { return extraSpace.left + getStyle()->boxOffsetX (); } + inline int boxRestWidth () + { return extraSpace.right + getStyle()->boxRestWidth (); } + inline int boxDiffWidth () { return boxOffsetX () + boxRestWidth (); } + inline int boxOffsetY () + { return extraSpace.top + getStyle()->boxOffsetY (); } + inline int boxRestHeight () + { return extraSpace.bottom + getStyle()->boxRestHeight (); } + inline int boxDiffHeight () { return boxOffsetY () + boxRestHeight (); } + void sizeRequest (Requisition *requisition); void getExtremes (Extremes *extremes); void sizeAllocate (Allocation *allocation); - virtual void setWidth (int width); - virtual void setAscent (int ascent); - virtual void setDescent (int descent); + + int getAvailWidth (bool forceValue); + int getAvailHeight (bool forceValue); + virtual bool getAdjustMinWidth () { return Widget::adjustMinWidth; } + void correctRequisition (Requisition *requisition, + void (*splitHeightFun) (int, int*, int*)); + void correctExtremes (Extremes *extremes); + int calcWidth (style::Length cssValue, int refWidth, Widget *refWidget, + int limitMinWidth, bool forceValue); + void calcFinalWidth (style::Style *style, int refWidth, Widget *refWidget, + int limitMinWidth, bool forceValue, int *finalWidth); + int calcHeight (style::Length cssValue, bool usePercentage, int refHeight, + Widget *refWidget, bool forceValue); + + virtual int applyPerWidth (int containerWidth, style::Length perWidth); + virtual int applyPerHeight (int containerHeight, style::Length perHeight); + + virtual bool isBlockLevel (); + virtual bool isPossibleContainer (); + + void containerSizeChanged (); bool intersects (Rectangle *area, Rectangle *intersection); @@ -300,10 +470,14 @@ public: inline bool isButtonSensitive () { return buttonSensitive; } inline Widget *getParent () { return parent; } + inline Widget *getContainer () { return container; } Widget *getTopLevel (); int getLevel (); + int getGeneratorLevel (); Widget *getNearestCommonAncestor (Widget *otherWidget); + inline Widget *getGenerator () { return generator ? generator : parent; } + inline Layout *getLayout () { return layout; } virtual Widget *getWidgetAtPoint (int x, int y, int level); @@ -330,6 +504,9 @@ public: virtual void removeChild (Widget *child); }; +void splitHeightPreserveAscent (int height, int *ascent, int *descent); +void splitHeightPreserveDescent (int height, int *ascent, int *descent); + } // namespace core } // namespace dw diff --git a/lout/Makefile.am b/lout/Makefile.am index bef9696e..f2219360 100644 --- a/lout/Makefile.am +++ b/lout/Makefile.am @@ -1,5 +1,6 @@ AM_CPPFLAGS = \ - -I$(top_srcdir) + -I$(top_srcdir) \ + -DCUR_WORKING_DIR='"@BASE_CUR_WORKING_DIR@/lout"' noinst_LIBRARIES = liblout.a diff --git a/lout/container.cc b/lout/container.cc index d3385137..366a58fa 100644 --- a/lout/container.cc +++ b/lout/container.cc @@ -22,6 +22,7 @@ #include "container.hh" #include "misc.hh" +#include "debug.hh" namespace lout { @@ -103,6 +104,8 @@ void Collection::intoStringBuffer(misc::StringBuffer *sb) Vector::Vector(int initSize, bool ownerOfObjects) { + DBG_OBJ_CREATE ("lout::container::untyped::Vector"); + numAlloc = initSize == 0 ? 1 : initSize; this->ownerOfObjects = ownerOfObjects; numElements = 0; @@ -113,6 +116,13 @@ Vector::~Vector() { clear(); free(array); + + DBG_OBJ_DELETE (); +} + +int Vector::size () +{ + return numElements; } void Vector::put(Object *newElement, int newPos) @@ -188,9 +198,10 @@ void Vector::remove(int pos) /** * Sort the elements in the vector. Assumes that all elements are Comparable's. */ -void Vector::sort() +void Vector::sort(Comparator *comparator) { - qsort (array, numElements, sizeof(Object*), Comparable::compareFun); + Comparator::compareFunComparator = comparator; + qsort (array, numElements, sizeof(Object*), Comparator::compareFun); } /** @@ -202,44 +213,58 @@ void Vector::sort() * size of the array. (This is the value which can be used for * insertion; see insertSortet()). */ -int Vector::bsearch(Object *key, bool mustExist) +int Vector::bsearch(Object *key, bool mustExist, int start, int end, + Comparator *comparator) { // The case !mustExist is not handled by bsearch(3), so here is a // new implementation. - if (numElements == 0) - return mustExist ? -1 : 0; - - int high = numElements - 1, low = 0; - - while (true) { - int index = (low + high) / 2; - int c = ((Comparable*) key)->compareTo ((Comparable*)array[index]); - if (c == 0) - return index; - else { - if (low >= high) { - if (mustExist) - return -1; + + DBG_OBJ_MSGF ("container", 0, + "<b>bsearch</b> (<i>key</i>, %s, %d, %d, <i>comparator</i>) " + "[size is %d]", + mustExist ? "true" : "false", start, end, size ()); + DBG_OBJ_MSG_START (); + + int result = -123; // Compiler happiness: GCC 4.7 does not handle this? + + if (start > end) { + DBG_OBJ_MSG ("container", 1, "empty"); + result = mustExist ? -1 : start; + } else { + int low = start, high = end; + bool found = false; + + while (!found) { + int index = (low + high) / 2; + int c = comparator->compare (key, array[index]); + DBG_OBJ_MSGF ("container", 1, + "searching within %d and %d; compare key with #%d => %d", + low, high, index, c); + if (c == 0) { + found = true; + result = index; + } else { + if (low >= high) { + if (mustExist) { + found = true; + result = -1; + } else { + found = true; + result = c > 0 ? index + 1 : index; + } + } + + if (c < 0) + high = index - 1; else - return c > 0 ? index + 1 : index; + low = index + 1; } - - if (c < 0) - high = index - 1; - else - low = index + 1; } } - - /* - void *result = ::bsearch (&key, array, numElements, sizeof (Object*), - Comparable::compareFun); - if (result) - return (Object**)result - array; - else - return -1; - */ + DBG_OBJ_MSGF ("container", 1, "result = %d", result); + DBG_OBJ_MSG_END (); + return result; } Object *Vector::VectorIterator::getNext() @@ -276,6 +301,32 @@ List::~List() clear(); } +int List::size () +{ + return numElements; +} + +bool List::equals(Object *other) +{ + List *otherList = (List*)other; + Node *node1 = first, *node2 = otherList->first; + while (node1 != NULL && node2 != NULL ) { + if (!node1->object->equals (node2->object)) + return false; + node1 = node1->next; + node2 = node2->next; + } + return node1 == NULL && node2 == NULL; +} + +int List::hashValue() +{ + int h = 0; + for (Node *node = first; node; node = node->next) + h = h ^ node->object->hashValue (); + return h; +} + void List::clear() { while (first) { @@ -305,6 +356,28 @@ void List::append(Object *element) numElements++; } +bool List::insertBefore(object::Object *beforeThis, object::Object *neew) +{ + Node *beforeCur, *cur; + + for (beforeCur = NULL, cur = first; cur; beforeCur = cur, cur = cur->next) { + if (cur->object == beforeThis) { + Node *newNode = new Node; + newNode->next = cur; + newNode->object = neew; + + if (beforeCur) + beforeCur->next = newNode; + else + first = newNode; + + numElements++; + return true; + } + } + + return false; +} bool List::remove0(Object *element, bool compare, bool doNotDeleteAtAll) { @@ -372,6 +445,8 @@ HashSet::HashSet(bool ownerOfObjects, int tableSize) table = new Node*[tableSize]; for (int i = 0; i < tableSize; i++) table[i] = NULL; + + numElements = 0; } HashSet::~HashSet() @@ -399,6 +474,11 @@ HashSet::~HashSet() delete[] table; } +int HashSet::size () +{ + return numElements; +} + HashSet::Node *HashSet::createNode() { return new Node; @@ -427,13 +507,15 @@ HashSet::Node *HashSet::insertNode(Object *object) { // Look whether object is already contained. Node *node = findNode(object); - if (node) + if (node) { clearNode(node); - else { + numElements--; + } else { int h = calcHashValue(object); node = createNode (); node->next = table[h]; table[h] = node; + numElements++; } node->object = object; @@ -471,6 +553,7 @@ bool HashSet::remove(Object *object) clearNode (cur); delete cur; + numElements--; return true; } @@ -642,6 +725,11 @@ Stack::~Stack() pop (); } +int Stack::size () +{ + return numElements; +} + void Stack::push (object::Object *object) { Node *newTop = new Node (); diff --git a/lout/container.hh b/lout/container.hh index 14803140..3051970e 100644 --- a/lout/container.hh +++ b/lout/container.hh @@ -93,6 +93,7 @@ class Collection: public Collection0 public: void intoStringBuffer(misc::StringBuffer *sb); inline Iterator iterator() { Iterator it(createIterator()); return it; } + virtual int size() = 0; }; @@ -128,6 +129,8 @@ public: Vector(int initSize, bool ownerOfObjects); ~Vector(); + int size(); + void put(object::Object *newElement, int newPos = -1); void insert(object::Object *newElement, int pos); @@ -137,16 +140,23 @@ public: * Notice that insertion is not very efficient, unless the position * is rather at the end. */ - inline void insertSorted(object::Object *newElement) - { insert (newElement, bsearch (newElement, false)); } + inline int insertSorted(object::Object *newElement, + object::Comparator *comparator = + &object::standardComparator) + { int pos = bsearch (newElement, false, comparator); + insert (newElement, pos); return pos; } void remove(int pos); inline object::Object *get(int pos) const { return (pos >= 0 && pos < numElements) ? array[pos] : NULL; } - inline int size() { return numElements; } void clear(); - void sort(); - int bsearch(Object *key, bool mustExist); + void sort(object::Comparator *comparator = &object::standardComparator); + int bsearch(Object *key, bool mustExist, int start, int end, + object::Comparator *comparator = &object::standardComparator); + inline int bsearch(Object *key, bool mustExist, + object::Comparator *comparator = + &object::standardComparator) + { return bsearch (key, mustExist, 0, size () - 1, comparator); } }; @@ -188,8 +198,14 @@ public: List(bool ownerOfObjects); ~List(); + bool equals(Object *other); + int hashValue(); + + int size (); + void clear(); void append(object::Object *element); + bool insertBefore(object::Object *beforeThis, object::Object *neew); inline bool removeRef(object::Object *element) { return remove0(element, false, false); } inline bool remove(object::Object *element) @@ -218,7 +234,7 @@ protected: }; Node **table; - int tableSize; + int tableSize, numElements; bool ownerOfObjects; inline int calcHashValue(object::Object *object) const @@ -254,6 +270,8 @@ public: HashSet(bool ownerOfObjects, int tableSize = 251); ~HashSet(); + int size (); + void put (object::Object *object); bool contains (object::Object *key) const; bool remove (object::Object *key); @@ -288,7 +306,8 @@ public: }; /** - * \brief A stack (LIFO). + * \brief A stack (LIFO). Can be used as Queue (FIFO) when pushUnder() + * is used instead of push(). * * Note that the iterator returns all elements in the reversed order they have * been put on the stack. @@ -326,6 +345,8 @@ public: Stack (bool ownerOfObjects); ~Stack(); + int size (); + void push (object::Object *object); void pushUnder (object::Object *object); inline object::Object *getTop () const { return top ? top->object : NULL; } @@ -385,11 +406,16 @@ public: Collection () { this->base = NULL; } ~Collection () { if (this->base) delete this->base; } + bool equals(Object *other) + { return this->base->equals (((Collection<T>*)other)->base); } + + int hashValue() { return this->base->hashValue (); } + void intoStringBuffer(misc::StringBuffer *sb) { this->base->intoStringBuffer(sb); } - inline Iterator<T> iterator() { Iterator<T> it; it.base = this->base->iterator(); return it; } + inline int size() { return this->base->size (); } }; @@ -406,16 +432,28 @@ public: { ((untyped::Vector*)this->base)->put(newElement, newPos); } inline void insert(T *newElement, int pos) { ((untyped::Vector*)this->base)->insert(newElement, pos); } - inline void insertSorted(T *newElement) - { ((untyped::Vector*)this->base)->insertSorted(newElement); } + inline int insertSorted(T *newElement, + object::Comparator *comparator = + &object::standardComparator) + { return ((untyped::Vector*)this->base)->insertSorted(newElement, + comparator); } inline void remove(int pos) { ((untyped::Vector*)this->base)->remove(pos); } inline T *get(int pos) const { return (T*)((untyped::Vector*)this->base)->get(pos); } - inline int size() const { return ((untyped::Vector*)this->base)->size(); } inline void clear() { ((untyped::Vector*)this->base)->clear(); } - inline void sort() { ((untyped::Vector*)this->base)->sort(); } - inline int bsearch(T *key, bool mustExist) - { return ((untyped::Vector*)this->base)->bsearch(key, mustExist); } + inline void sort(object::Comparator *comparator = + &object::standardComparator) + { ((untyped::Vector*)this->base)->sort(comparator); } + inline int bsearch(T *key, bool mustExist, int start, int end, + object::Comparator *comparator = + &object::standardComparator) + { return ((untyped::Vector*)this->base)->bsearch(key, mustExist, start, end, + comparator); } + inline int bsearch(T *key, bool mustExist, + object::Comparator *comparator = + &object::standardComparator) + { return ((untyped::Vector*)this->base)->bsearch(key, mustExist, + comparator); } }; @@ -431,6 +469,8 @@ public: inline void clear() { ((untyped::List*)this->base)->clear(); } inline void append(T *element) { ((untyped::List*)this->base)->append(element); } + inline bool insertBefore(object::Object *beforeThis, object::Object *neew) + { return ((untyped::List*)this->base)->insertBefore(beforeThis, neew); } inline bool removeRef(T *element) { return ((untyped::List*)this->base)->removeRef(element); } inline bool remove(T *element) { @@ -438,7 +478,6 @@ public: inline bool detachRef(T *element) { return ((untyped::List*)this->base)->detachRef(element); } - inline int size() const { return ((untyped::List*)this->base)->size(); } inline bool isEmpty() const { return ((untyped::List*)this->base)->isEmpty(); } inline T *getFirst() const @@ -501,7 +540,6 @@ public: inline T *getTop () const { return (T*)((untyped::Stack*)this->base)->getTop (); } inline void pop () { ((untyped::Stack*)this->base)->pop (); } - inline int size() const { return ((untyped::Stack*)this->base)->size(); } }; } // namespace untyped diff --git a/lout/debug.hh b/lout/debug.hh index 083234f8..e2839196 100644 --- a/lout/debug.hh +++ b/lout/debug.hh @@ -17,6 +17,8 @@ #define D_STMT_START do #define D_STMT_END while (0) +#define D_STMT_NOP D_STMT_START { } D_STMT_END + # ifdef DEBUG_LEVEL # define DEBUG_MSG(level, ...) \ D_STMT_START { \ @@ -30,7 +32,7 @@ /* - * See <http://www.dillo.org/~sgeerken/rtfl/>. + * See <http://home.gna.org/rtfl/>. */ #ifdef DBG_RTFL @@ -38,10 +40,12 @@ #include <unistd.h> #include <stdio.h> +#define DBG_IF_RTFL if(1) + // "\n" at the beginning just in case that the previous line is not finished // yet. #define RTFL_PREFIX_FMT "\n[rtfl]%s:%d:%d:" -#define RTFL_PREFIX_ARGS __FILE__, __LINE__, getpid() +#define RTFL_PREFIX_ARGS CUR_WORKING_DIR "/" __FILE__, __LINE__, getpid() #define DBG_OBJ_MSG(aspect, prio, msg) \ D_STMT_START { \ @@ -50,6 +54,15 @@ fflush (stdout); \ } D_STMT_END +// Variant which does not use "this", but an explicitly passed +// object. Should be applied to other macros. (Also, for use in C.) +#define DBG_OBJ_MSG_O(aspect, prio, obj, msg) \ + D_STMT_START { \ + printf (RTFL_PREFIX_FMT "obj-msg:%p:%s:%d:%s\n", \ + RTFL_PREFIX_ARGS, obj, aspect, prio, msg); \ + fflush (stdout); \ + } D_STMT_END + #define DBG_OBJ_MSGF(aspect, prio, fmt, ...) \ D_STMT_START { \ printf (RTFL_PREFIX_FMT "obj-msg:%p:%s:%d:" fmt "\n", \ @@ -57,6 +70,14 @@ fflush (stdout); \ } D_STMT_END +// See DBG_OBJ_MSG_O. +#define DBG_OBJ_MSGF_O(aspect, prio, obj, fmt, ...) \ + D_STMT_START { \ + printf (RTFL_PREFIX_FMT "obj-msg:%p:%s:%d:" fmt "\n", \ + RTFL_PREFIX_ARGS, obj, aspect, prio, __VA_ARGS__); \ + fflush (stdout); \ + } D_STMT_END + #define DBG_OBJ_MSG_START() \ D_STMT_START { \ printf (RTFL_PREFIX_FMT "obj-msg-start:%p\n", \ @@ -64,6 +85,14 @@ fflush (stdout); \ } D_STMT_END +// See DBG_OBJ_MSG_O. +#define DBG_OBJ_MSG_START_O(obj) \ + D_STMT_START { \ + printf (RTFL_PREFIX_FMT "obj-msg-start:%p\n", \ + RTFL_PREFIX_ARGS, obj); \ + fflush (stdout); \ + } D_STMT_END + #define DBG_OBJ_MSG_END() \ D_STMT_START { \ printf (RTFL_PREFIX_FMT "obj-msg-end:%p\n", \ @@ -71,6 +100,56 @@ fflush (stdout); \ } D_STMT_END +// See DBG_OBJ_MSG_O. +#define DBG_OBJ_MSG_END_O(obj) \ + D_STMT_START { \ + printf (RTFL_PREFIX_FMT "obj-msg-end:%p\n", \ + RTFL_PREFIX_ARGS, obj); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_ENTER0(aspect, prio, funname) \ + D_STMT_START { \ + printf (RTFL_PREFIX_FMT "obj-enter:%p:%s:%d:%s:\n", \ + RTFL_PREFIX_ARGS, this, aspect, prio, funname); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_ENTER0_O(aspect, prio, obj, funname) \ + D_STMT_START { \ + printf (RTFL_PREFIX_FMT "obj-enter:%p:%s:%d:%s:\n", \ + RTFL_PREFIX_ARGS, obj, aspect, prio, funname); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_ENTER(aspect, prio, funname, fmt, ...) \ + D_STMT_START { \ + fflush (stdout); \ + printf (RTFL_PREFIX_FMT "obj-enter:%p:%s:%d:%s:" fmt "\n", \ + RTFL_PREFIX_ARGS, this, aspect, prio, funname, __VA_ARGS__); \ + } D_STMT_END + +#define DBG_OBJ_ENTER_O(aspect, prio, obj, funname, fmt, ...) \ + D_STMT_START { \ + fflush (stdout); \ + printf (RTFL_PREFIX_FMT "obj-enter:%p:%s:%d:%s:" fmt "\n", \ + RTFL_PREFIX_ARGS, obj, aspect, prio, funname, __VA_ARGS__); \ + } D_STMT_END + +#define DBG_OBJ_LEAVE() \ + D_STMT_START { \ + printf (RTFL_PREFIX_FMT "obj-leave:%p\n", \ + RTFL_PREFIX_ARGS, this); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_LEAVE_O(obj) \ + D_STMT_START { \ + printf (RTFL_PREFIX_FMT "obj-leave:%p\n", \ + RTFL_PREFIX_ARGS, obj); \ + fflush (stdout); \ + } D_STMT_END + #define DBG_OBJ_CREATE(klass) \ D_STMT_START { \ printf (RTFL_PREFIX_FMT "obj-create:%p:%s\n", \ @@ -124,13 +203,27 @@ fflush (stdout); \ } D_STMT_END -#define DBG_OBJ_SET_STR(var, val) \ +#define DBG_OBJ_SET_NUM_O(obj, var, val) \ + D_STMT_START { \ + printf (RTFL_PREFIX_FMT "obj-set:%p:%s:%d\n", \ + RTFL_PREFIX_ARGS, obj, var, val); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_SET_SYM(var, val) \ D_STMT_START { \ printf (RTFL_PREFIX_FMT "obj-set:%p:%s:%s\n", \ RTFL_PREFIX_ARGS, this, var, val); \ fflush (stdout); \ } D_STMT_END +#define DBG_OBJ_SET_STR(var, val) \ + D_STMT_START { \ + printf (RTFL_PREFIX_FMT "obj-set:%p:%s:\"%s\"\n", \ + RTFL_PREFIX_ARGS, this, var, val); \ + fflush (stdout); \ + } D_STMT_END + #define DBG_OBJ_SET_PTR(var, val) \ D_STMT_START { \ printf (RTFL_PREFIX_FMT "obj-set:%p:%s:%p\n", \ @@ -138,28 +231,105 @@ fflush (stdout); \ } D_STMT_END +#define DBG_OBJ_SET_PTR_O(obj, var, val) \ + D_STMT_START { \ + printf (RTFL_PREFIX_FMT "obj-set:%p:%s:%p\n", \ + RTFL_PREFIX_ARGS, obj, var, val); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_SET_BOOL(var, val) \ + D_STMT_START { \ + printf (RTFL_PREFIX_FMT "obj-set:%p:%s:%s\n", \ + RTFL_PREFIX_ARGS, this, var, val ? "true" : "false"); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_SET_BOOL_O(obj, var, val) \ + D_STMT_START { \ + printf (RTFL_PREFIX_FMT "obj-set:%p:%s:%s\n", \ + RTFL_PREFIX_ARGS, obj, var, val ? "true" : "false"); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_SET_COL(var, val) \ + D_STMT_START { \ + printf (RTFL_PREFIX_FMT "obj-set:%p:%s:#%06x\n", \ + RTFL_PREFIX_ARGS, this, var, val); \ + fflush (stdout); \ + } D_STMT_END + #define DBG_OBJ_ARRSET_NUM(var, ind, val) \ D_STMT_START { \ - printf (RTFL_PREFIX_FMT "obj-set:%p:" var ".%d:%d\n", \ - RTFL_PREFIX_ARGS, this, ind, val); \ + printf (RTFL_PREFIX_FMT "obj-set:%p:%s.%d:%d\n", \ + RTFL_PREFIX_ARGS, this, var, ind, val); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_ARRSET_SYM(var, ind, val) \ + D_STMT_START { \ + printf (RTFL_PREFIX_FMT "obj-set:%p:%s.%d:%s\n", \ + RTFL_PREFIX_ARGS, this, var, ind, val); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_ARRSET_BOOL(var, ind, val) \ + D_STMT_START { \ + printf (RTFL_PREFIX_FMT "obj-set:%p:%s.%d:%s\n", \ + RTFL_PREFIX_ARGS, this, var, ind, val ? "true" : "false"); \ fflush (stdout); \ } D_STMT_END #define DBG_OBJ_ARRSET_STR(var, ind, val) \ D_STMT_START { \ - printf (RTFL_PREFIX_FMT "obj-set:%p:" var ".%d:%s\n", \ - RTFL_PREFIX_ARGS, this, ind, val); \ + printf (RTFL_PREFIX_FMT "obj-set:%p:%s.%d:\"%s\"\n", \ + RTFL_PREFIX_ARGS, this, var, ind, val); \ fflush (stdout); \ } D_STMT_END #define DBG_OBJ_ARRSET_PTR(var, ind, val) \ D_STMT_START { \ - printf (RTFL_PREFIX_FMT "obj-set:%p:" var ".%d:%p\n", \ - RTFL_PREFIX_ARGS, this, ind, val); \ + printf (RTFL_PREFIX_FMT "obj-set:%p:%s.%d:%p\n", \ + RTFL_PREFIX_ARGS, this, var, ind, val); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_ARRATTRSET_NUM(var, ind, attr, val) \ + D_STMT_START { \ + printf (RTFL_PREFIX_FMT "obj-set:%p:%s.%d.%s:%d\n", \ + RTFL_PREFIX_ARGS, this, var, ind, attr, val); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_ARRATTRSET_SYM(var, ind, attr, val) \ + D_STMT_START { \ + printf (RTFL_PREFIX_FMT "obj-set:%p:%s.%d.%s:%s\n", \ + RTFL_PREFIX_ARGS, this, var, ind, attr, val); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_ARRATTRSET_BOOL(var, ind, attr, val) \ + D_STMT_START { \ + printf (RTFL_PREFIX_FMT "obj-set:%p:%s.%d.%s:%s\n", \ + RTFL_PREFIX_ARGS, this, var, ind, attr, val ? "true" : "false"); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_ARRATTRSET_STR(var, ind, attr, val) \ + D_STMT_START { \ + printf (RTFL_PREFIX_FMT "obj-set:%p:%s.%d.%s:\"%s\"\n", \ + RTFL_PREFIX_ARGS, this, var, ind, attr, val); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_ARRATTRSET_PTR(var, ind, attr, val) \ + D_STMT_START { \ + printf (RTFL_PREFIX_FMT "obj-set:%p:%s.%d.%s:%p\n", \ + RTFL_PREFIX_ARGS, this, var, ind, attr, val); \ fflush (stdout); \ } D_STMT_END -#define DBG_OBJ_COLOR(color, klass) \ +#define DBG_OBJ_COLOR(klass, color) \ D_STMT_START { \ printf (RTFL_PREFIX_FMT "obj-color:%s:%s\n", \ RTFL_PREFIX_ARGS, color, klass); \ @@ -168,23 +338,48 @@ #else /* DBG_RTFL */ -#define DBG_OBJ_MSG(aspect, prio, msg) -#define DBG_OBJ_MSGF(aspect, prio, fmt, ...) -#define DBG_OBJ_MSG_START() -#define DBG_OBJ_MSG_END() -#define DBG_OBJ_CREATE(klass) -#define DBG_OBJ_DELETE() -#define DBG_OBJ_BASECLASS(klass) -#define DBG_OBJ_ASSOC_PARENT(parent) -#define DBG_OBJ_ASSOC_CHILD(child) -#define DBG_OBJ_ASSOC(parent, child) -#define DBG_OBJ_SET_NUM(var, val) -#define DBG_OBJ_SET_STR(var, val) -#define DBG_OBJ_SET_PTR(var, val) -#define DBG_OBJ_ARRSET_NUM(var, ind, val) -#define DBG_OBJ_ARRSET_STR(var, ind, val) -#define DBG_OBJ_ARRSET_PTR(var, ind, val) -#define DBG_OBJ_COLOR(klass, color) +#define DBG_IF_RTFL if(0) + +#define DBG_OBJ_MSG(aspect, prio, msg) D_STMT_NOP +#define DBG_OBJ_MSG_O(aspect, prio, obj, msg) D_STMT_NOP +#define DBG_OBJ_MSGF(aspect, prio, fmt, ...) D_STMT_NOP +#define DBG_OBJ_MSGF_O(aspect, prio, obj, fmt, ...) D_STMT_NOP +#define DBG_OBJ_MSG_START() D_STMT_NOP +#define DBG_OBJ_MSG_START_O(obj) D_STMT_NOP +#define DBG_OBJ_MSG_END() D_STMT_NOP +#define DBG_OBJ_MSG_END_O(obj) D_STMT_NOP +#define DBG_OBJ_ENTER0(aspect, prio, funname) D_STMT_NOP +#define DBG_OBJ_ENTER0_O(aspect, prio, obj, funname) D_STMT_NOP +#define DBG_OBJ_ENTER(aspect, prio, funname, fmt, ...) D_STMT_NOP +#define DBG_OBJ_ENTER_O(aspect, prio, obj, funname, fmt, ...) D_STMT_NOP +#define DBG_OBJ_LEAVE() D_STMT_NOP +#define DBG_OBJ_LEAVE_O(obj) D_STMT_NOP +#define DBG_OBJ_CREATE(klass) D_STMT_NOP +#define DBG_OBJ_DELETE() D_STMT_NOP +#define DBG_OBJ_BASECLASS(klass) D_STMT_NOP +#define DBG_OBJ_ASSOC_PARENT(parent) D_STMT_NOP +#define DBG_OBJ_ASSOC_CHILD(child) D_STMT_NOP +#define DBG_OBJ_ASSOC(parent, child) D_STMT_NOP +#define DBG_OBJ_SET_NUM(var, val) D_STMT_NOP +#define DBG_OBJ_SET_NUM_O(obj, var, val) D_STMT_NOP +#define DBG_OBJ_SET_SYM(var, val) D_STMT_NOP +#define DBG_OBJ_SET_STR(var, val) D_STMT_NOP +#define DBG_OBJ_SET_PTR(var, val) D_STMT_NOP +#define DBG_OBJ_SET_PTR_O(obj, var, val) D_STMT_NOP +#define DBG_OBJ_SET_BOOL(var, val) D_STMT_NOP +#define DBG_OBJ_SET_BOOL_O(obj, var, val) D_STMT_NOP +#define DBG_OBJ_SET_COL(var, val) D_STMT_NOP +#define DBG_OBJ_ARRSET_NUM(var, ind, val) D_STMT_NOP +#define DBG_OBJ_ARRSET_SYM(var, ind, val) D_STMT_NOP +#define DBG_OBJ_ARRSET_STR(var, ind, val) D_STMT_NOP +#define DBG_OBJ_ARRSET_PTR(var, ind, val) D_STMT_NOP +#define DBG_OBJ_ARRSET_BOOL(var, ind, val) D_STMT_NOP +#define DBG_OBJ_ARRATTRSET_NUM(var, ind, attr, val) D_STMT_NOP +#define DBG_OBJ_ARRATTRSET_SYM(var, ind, attr, val) D_STMT_NOP +#define DBG_OBJ_ARRATTRSET_STR(var, ind, attr, val) D_STMT_NOP +#define DBG_OBJ_ARRATTRSET_PTR(var, ind, attr, val) D_STMT_NOP +#define DBG_OBJ_ARRATTRSET_BOOL(var, ind, attr, val) D_STMT_NOP +#define DBG_OBJ_COLOR(klass, color) D_STMT_NOP #endif /* DBG_RTFL */ diff --git a/lout/identity.cc b/lout/identity.cc index 6fe679b4..61f59ace 100644 --- a/lout/identity.cc +++ b/lout/identity.cc @@ -17,8 +17,6 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - - #include "identity.hh" #include <stdio.h> @@ -41,6 +39,22 @@ IdentifiableObject::Class::Class (IdentifiableObject::Class *parent, int id, this->className = className; } +void IdentifiableObject::Class::intoStringBuffer(misc::StringBuffer *sb) +{ + sb->append ("<class "); + sb->append (className); + sb->append (" ("); + sb->appendInt (id); + sb->append (")"); + + if (parent) { + sb->append (", parent: "); + parent->intoStringBuffer (sb); + } + + sb->append (">"); +} + HashTable <ConstString, IdentifiableObject::Class> *IdentifiableObject::classesByName = new HashTable<ConstString, IdentifiableObject::Class> (true, true); @@ -55,7 +69,9 @@ IdentifiableObject::IdentifiableObject () void IdentifiableObject::intoStringBuffer(misc::StringBuffer *sb) { - sb->append("<instance of "); + sb->append("<instance "); + sb->appendPointer(this); + sb->append(" of "); sb->append(getClassName()); sb->append(">"); } @@ -78,6 +94,7 @@ void IdentifiableObject::registerName (const char *className, int *classId) } this->classId = klass->id; + *classId = klass->id; currentlyConstructedClass = klass; } diff --git a/lout/identity.hh b/lout/identity.hh index 1f0b4bdf..df42b204 100644 --- a/lout/identity.hh +++ b/lout/identity.hh @@ -106,6 +106,8 @@ private: const char *className; Class (Class *parent, int id, const char *className); + + void intoStringBuffer(misc::StringBuffer *sb); }; static container::typed::HashTable <object::ConstString, @@ -121,7 +123,7 @@ protected: public: IdentifiableObject (); - virtual void intoStringBuffer(misc::StringBuffer *sb); + void intoStringBuffer(misc::StringBuffer *sb); /** * \brief Returns the class identifier. diff --git a/lout/misc.hh b/lout/misc.hh index 6a04c89a..b362fc2f 100644 --- a/lout/misc.hh +++ b/lout/misc.hh @@ -223,6 +223,14 @@ public: assert (i >= 0 && this->num - i > 0); this->array[i] = t; } + + /** + * \brief Store an object at the end of the vector. + */ + inline void setLast (T t) { + assert (this->num > 0); + this->array[this->num - 1] = t; + } }; /** @@ -379,7 +387,7 @@ public: this->startExtra = index; resizeExtra (); } else { - if (index < startExtra) { + if (index < startExtra) { consolidate (); insert (index, numInsert); } else if (index < startExtra + numExtra) { @@ -421,14 +429,29 @@ public: */ inline T* getRef (int i) const { - if (this->startExtra == -1) + if (this->startExtra == -1) { + assert (i >= 0 && i < this->numMain); return this->arrayMain + i; - else { - if (i < this->startExtra) + } else { + if (i < this->startExtra) { + assert (i >= 0); return this->arrayMain + i; - else if (i >= this->startExtra + this->numExtra) + } else if (i >= this->startExtra + this->numExtra) { + // The original assertion + /// + // "assert (i < this->numMain + this->numExtra)" + // + // causes this warnung in dw::Textblock::breakAdded: + // + // "assuming signed overflow does not occur when assuming that + // (X - c) > X is always false [-Wstrict-overflow]" + // + // Subtracting numExtra from both sides solves this, + // interrestingly. + + assert (i - this->numExtra < this->numMain); return this->arrayMain + i - this->numExtra; - else + } else return this->arrayExtra1 + i - this->startExtra; } } @@ -485,6 +508,13 @@ public: inline void set (int i, T t) { *(this->getRef(i)) = t; } + + /** + * \brief Store an object at the end of the vector. + */ + inline void setLast (T t) { + *(this->getLastRef()) = t; + } }; /** @@ -515,6 +545,11 @@ public: * about memory management. */ inline void append(const char *str) { appendNoCopy(strdup(str)); } + inline void appendInt(int n) + { char buf[32]; sprintf (buf, "%d", n); append (buf); } + inline void appendPointer(void *p) + { char buf[32]; sprintf (buf, "%p", p); append (buf); } + inline void appendBool(bool b) { append (b ? "true" : "false"); } void appendNoCopy(char *str); const char *getChars(); void clear (); diff --git a/lout/object.cc b/lout/object.cc index 99b5902d..e4e0152a 100644 --- a/lout/object.cc +++ b/lout/object.cc @@ -94,7 +94,9 @@ const char *Object::toString() */ void Object::intoStringBuffer(misc::StringBuffer *sb) { - sb->append("<not further specified object>"); + sb->append("<not further specified object "); + sb->appendPointer(this); + sb->append(">"); } /** @@ -107,29 +109,44 @@ size_t Object::sizeOf() } // ---------------- -// Comparable +// Comparator // ---------------- +Comparator *Comparator::compareFunComparator = NULL; + /** * \brief This static method may be used as compare function for * qsort(3) and bsearch(3), for an array of Object* (Object*[] or * Object**). + * + * "compareFunComparator" should be set before. + * + * \todo Not reentrant. Consider switching to reentrant variants + * (qsort_r), and compare function with an additional argument. */ -int Comparable::compareFun(const void *p1, const void *p2) +int Comparator::compareFun(const void *p1, const void *p2) { - Comparable *c1 = *(Comparable**)p1; - Comparable *c2 = *(Comparable**)p2; + return compareFunComparator->compare (*(Object**)p1, *(Object**)p2); +} + +// ------------------------ +// StandardComparator +// ------------------------ - if (c1 && c2) - return ((c1)->compareTo(c2)); - else if (c1) +int StandardComparator::compare(Object *o1, Object *o2) +{ + if (o1 && o2) + return ((Comparable*)o1)->compareTo ((Comparable*)o2); + else if (o1) return 1; - else if (c2) + else if (o2) return -1; else return 0; } +StandardComparator standardComparator; + // ------------- // Pointer // ------------- @@ -194,6 +211,32 @@ int Integer::compareTo(Comparable *other) return value - ((Integer*)other)->value; } +// ------------- +// Boolean +// ------------- + +bool Boolean::equals(Object *other) +{ + bool value2 = ((Boolean*)other)->value; + // TODO Does "==" work? + return (value && value2) || (!value && value2); +} + +int Boolean::hashValue() +{ + return value ? 1 : 0; +} + +void Boolean::intoStringBuffer(misc::StringBuffer *sb) +{ + sb->append(value ? "true" : "false"); +} + +int Boolean::compareTo(Comparable *other) +{ + return (value ? 1 : 0) - (((Boolean*)other)->value ? 1 : 0); +} + // ----------------- // ConstString // ----------------- diff --git a/lout/object.hh b/lout/object.hh index fd612863..3ba7b590 100644 --- a/lout/object.hh +++ b/lout/object.hh @@ -42,10 +42,11 @@ class Comparable: public Object { public: /** - * \brief Compare two objects c1 and c2. + * \brief Compare two objects, this and other. * - * Return a value < 0, when c1 is less than c2, a value > 0, when c1 - * is greater than c2, or 0, when c1 and c2 are equal. + * Return a value < 0, when this is less than other, a value > 0, + * when this is greater than other, or 0, when this and other are + * equal. * * If c1.equals(c2) (as defined in Object), c1.compareTo(c2) must * be 0, but, unlike you may expect, the reversed is not @@ -55,10 +56,43 @@ public: * care about. */ virtual int compareTo(Comparable *other) = 0; +}; + +/** + * \brief Used for other orders as the one defined by Comparable. + * + * Compared objects must not neccessary be instances of Comparable. + */ +class Comparator: public Object +{ +public: + /** + * \brief Compare two objects o1 and o2. + * + * Return a value < 0, when o1 is less than o2, a value > 0, when o1 + * is greater than o2, or 0, when o1 and o2 are equal. + * + * If o1.equals(o2) (as defined in Object), compare(o1, o2) must be + * 0, but, unlike you may expect, the reversed is not necessarily + * true. This method returns 0, if, according to the rules for + * sorting, there is no difference, but there may still be + * differences (not relevant for sorting), which "equals" will care + * about. + */ + virtual int compare(Object *o1, Object *o2) = 0; + static Comparator *compareFunComparator; static int compareFun(const void *p1, const void *p2); }; +class StandardComparator: public Comparator +{ +public: + int compare(Object *o1, Object *o2); +}; + +extern StandardComparator standardComparator; + /** * \brief An object::Object wrapper for void pointers. */ @@ -104,6 +138,23 @@ public: /** + * \brief An object::Object wrapper for bool's. + */ +class Boolean: public Comparable +{ + bool value; + +public: + Boolean(bool value) { this->value = value; } + bool equals(Object *other); + int hashValue(); + void intoStringBuffer(misc::StringBuffer *sb); + int compareTo(Comparable *other); + inline bool getValue() { return value; } +}; + + +/** * \brief An object::Object wrapper for constant strings (char*). * * As opposed to object::String, the char array is not copied. diff --git a/lout/unicode.cc b/lout/unicode.cc index 4f0f0b3b..9fc2f3d3 100644 --- a/lout/unicode.cc +++ b/lout/unicode.cc @@ -1,3 +1,23 @@ +/* + * Dillo Widget + * + * Copyright 2012, 2013 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + #include "unicode.hh" #include "misc.hh" diff --git a/src/IO/IO.c b/src/IO/IO.c index a0a8bba5..0addf486 100644 --- a/src/IO/IO.c +++ b/src/IO/IO.c @@ -298,6 +298,8 @@ static void IO_fd_write_cb(int fd, void *data) } else { if (IO_callback(io) == 0) a_IOwatch_remove_fd(fd, DIO_WRITE); + if (io->Status) + a_IO_ccc(OpAbort, 1, FWD, io->Info, NULL, NULL); } } @@ -350,6 +352,7 @@ void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info, switch (Op) { case OpStart: io = IO_new(IOWrite); + io->Info = Info; Info->LocalKey = io; break; case OpSend: @@ -384,6 +387,13 @@ void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info, } else { /* 1 FWD */ /* Write-data status */ switch (Op) { + case OpAbort: + io = Info->LocalKey; + IO_close_fd(io, IO_StopRdWr); + IO_free(io); + a_Chain_fcb(OpAbort, Info, NULL, NULL); + dFree(Info); + break; default: MSG_WARN("Unused CCC\n"); break; @@ -406,9 +416,10 @@ void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info, IO_submit(io); } break; + case OpEnd: case OpAbort: io = Info->LocalKey; - IO_close_fd(io, IO_StopRdWr); + IO_close_fd(io, Op == OpEnd ? IO_StopRd : IO_StopRdWr); IO_free(io); dFree(Info); break; diff --git a/src/IO/about.c b/src/IO/about.c index 5ffe7dff..0cc3b427 100644 --- a/src/IO/about.c +++ b/src/IO/about.c @@ -229,29 +229,32 @@ const char *const AboutSplash= "<table border='0' cellpadding='5' cellspacing='1' width='100%'>\n" "<tr>\n" " <td bgcolor='#CCCCCC'>\n" -" <h4>Release overview</h4>\n" -" December 24, 2014\n" +" <h4>Notes</h4>\n" "<tr>\n" " <td bgcolor='#FFFFFF'>\n" " <table border='0' cellspacing='0' cellpadding='5'>\n" " <tr>\n" " <td>\n" -"<p>\n" -"The dillo-3.0.4.1 release brings you fixes:\n" "<ul>\n" -"<li> for linking with the recently-released fltk-1.3.3\n" -" (we don't use <tt>fl_oldfocus</tt> anymore).\n" -"<li> to make sure that windows are resizable with fltk-1.3.3.\n" -"<li> not to load background images, or follow redirections or meta refresh,\n" -" in <tt>--local</tt> mode (security).\n" -"<li> to permit linking on OS X (remove our <tt>Fl_Printer</tt> stub).\n" -"<li> for a crash when searching from the address bar and no search urls are\n" -" found in dillorc.\n" +" <li> There's a\n" +" <a href='http://www.dillo.org/dillorc'>dillorc</a>\n" +" (readable config) file inside the tarball. It is well-commented\n" +" and has plenty of options to customize dillo, so <STRONG>copy\n" +" it</STRONG> to your <STRONG>~/.dillo/</STRONG> directory, and\n" +" modify it to your taste.\n" +" <li> The right mouse button brings up a context-sensitive menu\n" +" (available on pages, links, images, forms, the Back and Forward buttons,\n" +" and the bug meter).\n" +" <li> Cookies are disabled by default for privacy. To log into certain\n" +" sites, you may need to <a href='http://www.dillo.org/Cookies.txt'>enable\n" +" cookies selectively</a>.\n" +" <li> Frames, Java and Javascript are not supported.\n" +" <li> This release is mainly intended for <strong>developers</strong>\n" +" and <strong>advanced users</strong>.\n" +" <li> Documentation for developers is in the <CODE>/doc</CODE>\n" +" dir inside the tarball; you can find directions on everything\n" +" else at the home page.\n" "</ul>\n" -"<p>\n" -"...that shouldn't have to wait until dillo-3.1 is ready with its floating\n" -"elements and assorted good things.\n" -"<p>\n" " </table>\n" "</table>\n" "</table>\n" @@ -262,32 +265,29 @@ const char *const AboutSplash= "<table border='0' cellpadding='5' cellspacing='1' width='100%'>\n" "<tr>\n" " <td bgcolor='#CCCCCC'>\n" -" <h4>Notes</h4>\n" +" <h4>Release overview</h4>\n" +" December 24, 2014\n" "<tr>\n" " <td bgcolor='#FFFFFF'>\n" " <table border='0' cellspacing='0' cellpadding='5'>\n" " <tr>\n" " <td>\n" +"<p>\n" +"The dillo-3.0.4.1 release brings you fixes:\n" "<ul>\n" -" <li> There's a\n" -" <a href='http://www.dillo.org/dillorc'>dillorc</a>\n" -" (readable config) file inside the tarball. It is well-commented\n" -" and has plenty of options to customize dillo, so <STRONG>copy\n" -" it</STRONG> to your <STRONG>~/.dillo/</STRONG> directory, and\n" -" modify it to your taste.\n" -" <li> Documentation for developers is in the <CODE>/doc</CODE>\n" -" dir inside the tarball; you can find directions on everything\n" -" else at the home page.\n" -" <li> The right mouse button brings up a context-sensitive menu\n" -" (available on pages, links, images, forms, the Back and Forward buttons,\n" -" and the bug meter).\n" -" <li> Dillo behaves very nicely when browsing local files, images, and HTML.\n" -" It's also very good for Internet searching.\n" -" <li> This release is mainly intended for <strong>developers</strong>\n" -" and <strong>advanced users</strong>.\n" -" <li> Frames, Java and Javascript are not supported.\n" +"<li> for linking with the recently-released fltk-1.3.3\n" +" (we don't use <tt>fl_oldfocus</tt> anymore).\n" +"<li> to make sure that windows are resizable with fltk-1.3.3.\n" +"<li> not to load background images, or follow redirections or meta refresh,\n" +" in <tt>--local</tt> mode (security).\n" +"<li> to permit linking on OS X (remove our <tt>Fl_Printer</tt> stub).\n" +"<li> for a crash when searching from the address bar and no search urls are\n" +" found in dillorc.\n" "</ul>\n" -"<br>\n" +"<p>\n" +"...that shouldn't have to wait until dillo-3.1 is ready with its floating\n" +"elements and assorted good things.\n" +"<p>\n" " </table>\n" "</table>\n" "</table>\n" diff --git a/src/IO/http.c b/src/IO/http.c index a0021a9e..49b3a3ac 100644 --- a/src/IO/http.c +++ b/src/IO/http.c @@ -48,6 +48,8 @@ D_STMT_START { \ #define _MSG_BW(web, root, ...) +static const int HTTP_PORT = 80; + static const int HTTP_SOCKET_USE_PROXY = 0x1; static const int HTTP_SOCKET_QUEUED = 0x4; static const int HTTP_SOCKET_TO_BE_FREED = 0x8; @@ -67,28 +69,23 @@ typedef struct { /* Data structures and functions to queue sockets that need to be * delayed due to the per host connection limit. */ -typedef struct SocketQueueEntry { - SocketData_t* sock; - struct SocketQueueEntry *next ; -} SocketQueueEntry_t; - -typedef struct { - SocketQueueEntry_t *head; - SocketQueueEntry_t *tail; -} SocketQueue_t; - typedef struct { char *host; - int active_connections; - SocketQueue_t queue; + int active_conns; + Dlist *queue; } HostConnection_t; -static void Http_socket_queue_init(SocketQueue_t *sq); -static void Http_socket_enqueue(SocketQueue_t *sq, SocketData_t* sock); -static SocketData_t* Http_socket_dequeue(SocketQueue_t *sq); +typedef struct { + int fd; + int skey; +} FdMapEntry_t; + +static void Http_socket_enqueue(HostConnection_t *hc, SocketData_t* sock); +static SocketData_t* Http_socket_dequeue(HostConnection_t *hc); static HostConnection_t *Http_host_connection_get(const char *host); static void Http_host_connection_remove(HostConnection_t *hc); static int Http_connect_socket(ChainLink *Info); +static void Http_send_query(ChainLink *Info, SocketData_t *S); static void Http_socket_free(int SKey); /* @@ -101,6 +98,11 @@ static char *HTTP_Proxy_Auth_base64 = NULL; static char *HTTP_Language_hdr = NULL; static Dlist *host_connections; +/* TODO: If fd_map will stick around in its present form (FDs and SocketData_t) + * then consider whether having both this and ValidSocks is necessary. + */ +static Dlist *fd_map; + /* * Initialize proxy vars and Accept-Language header */ @@ -125,6 +127,7 @@ int a_Http_init(void) */ host_connections = dList_new(5); + fd_map = dList_new(20); return 0; } @@ -155,14 +158,39 @@ void a_Http_set_proxy_passwd(const char *str) static int Http_sock_new(void) { SocketData_t *S = dNew0(SocketData_t, 1); + S->SockFD = -1; return a_Klist_insert(&ValidSocks, S); } +/* + * Compare by FD. + */ +static int Http_fd_map_cmp(const void *v1, const void *v2) +{ + int fd = VOIDP2INT(v2); + const FdMapEntry_t *e = v1; + + return (fd == e->fd) ? 0 : 1; +} + +/* + * Remove and free entry from fd_map. + */ +static void Http_fd_map_remove_entry(int fd) +{ + void *data = dList_find_custom(fd_map, INT2VOIDP(fd), Http_fd_map_cmp); + + if (data) { + dList_remove_fast(fd_map, data); + dFree(data); + } +} + static void Http_connect_queued_sockets(HostConnection_t *hc) { SocketData_t *sd; - while (hc->active_connections < prefs.http_max_conns && - (sd = Http_socket_dequeue(&hc->queue))) { + while (hc->active_conns < prefs.http_max_conns && + (sd = Http_socket_dequeue(hc))) { sd->flags &= ~HTTP_SOCKET_QUEUED; @@ -177,8 +205,17 @@ static void Http_connect_queued_sockets(HostConnection_t *hc) Http_socket_free(VOIDP2INT(Info->LocalKey)); /* free sd */ dFree(Info); } else { + FdMapEntry_t *e = dNew0(FdMapEntry_t, 1); + + e->fd = sd->SockFD; + e->skey = VOIDP2INT(sd->Info->LocalKey); + dList_append(fd_map, e); + + hc->active_conns++; + a_Chain_bcb(OpSend, sd->Info, &sd->SockFD, "FD"); + a_Chain_fcb(OpSend, sd->Info, &sd->SockFD, "FD"); + Http_send_query(sd->Info, sd); sd->connected_to = hc->host; - hc->active_connections++; } } } @@ -197,11 +234,13 @@ static void Http_socket_free(int SKey) if (S->flags & HTTP_SOCKET_QUEUED) { S->flags |= HTTP_SOCKET_TO_BE_FREED; } else { + if (S->SockFD != -1) + Http_fd_map_remove_entry(S->SockFD); if (S->connected_to) { HostConnection_t *hc = Http_host_connection_get(S->connected_to); - hc->active_connections--; + hc->active_conns--; Http_connect_queued_sockets(hc); - if (hc->active_connections == 0) + if (hc->active_conns == 0) Http_host_connection_remove(hc); } dFree(S); @@ -275,6 +314,10 @@ Dstr *a_Http_make_query_str(const DilloUrl *url, const DilloUrl *requester, web_flags & WEB_Stylesheet ? "text/css,*/*;q=0.1" : "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"; + const char *connection_hdr_val = + (prefs.http_persistent_conns == TRUE && + !dStrAsciiCasecmp(URL_SCHEME(url), "http")) ? "keep-alive" : "close"; + if (use_proxy) { dStr_sprintfa(request_uri, "%s%s", URL_STR(url), @@ -309,15 +352,15 @@ Dstr *a_Http_make_query_str(const DilloUrl *url, const DilloUrl *requester, "DNT: 1\r\n" "%s" /* proxy auth */ "%s" /* referer */ - "Connection: close\r\n" + "Connection: %s\r\n" "Content-Type: %s\r\n" "Content-Length: %ld\r\n" "%s" /* cookies */ "\r\n", request_uri->str, URL_AUTHORITY(url), prefs.http_user_agent, accept_hdr_value, HTTP_Language_hdr, auth ? auth : "", - proxy_auth->str, referer, content_type->str, (long)URL_DATA(url)->len, - cookies); + proxy_auth->str, referer, connection_hdr_val, content_type->str, + (long)URL_DATA(url)->len, cookies); dStr_append_l(query, URL_DATA(url)->str, URL_DATA(url)->len); dStr_free(content_type, TRUE); } else { @@ -333,13 +376,13 @@ Dstr *a_Http_make_query_str(const DilloUrl *url, const DilloUrl *requester, "DNT: 1\r\n" "%s" /* proxy auth */ "%s" /* referer */ - "Connection: close\r\n" + "Connection: %s\r\n" "%s" /* cache control */ "%s" /* cookies */ "\r\n", request_uri->str, URL_AUTHORITY(url), prefs.http_user_agent, accept_hdr_value, HTTP_Language_hdr, auth ? auth : "", - proxy_auth->str, referer, + proxy_auth->str, referer, connection_hdr_val, (URL_FLAGS(url) & URL_E2EQuery) ? "Pragma: no-cache\r\nCache-Control: no-cache\r\n" : "", cookies); @@ -416,7 +459,7 @@ static int Http_connect_socket(ChainLink *Info) struct sockaddr_in *sin = (struct sockaddr_in *)&name; socket_len = sizeof(struct sockaddr_in); sin->sin_family = dh->af; - sin->sin_port = S->port ? htons(S->port) : htons(DILLO_URL_HTTP_PORT); + sin->sin_port = S->port ? htons(S->port) : htons(HTTP_PORT); memcpy(&sin->sin_addr, dh->data, (size_t)dh->alen); if (a_Web_valid(S->web) && (S->web->flags & WEB_RootUrl)) MSG("Connecting to %s\n", inet_ntoa(sin->sin_addr)); @@ -430,7 +473,7 @@ static int Http_connect_socket(ChainLink *Info) socket_len = sizeof(struct sockaddr_in6); sin6->sin6_family = dh->af; sin6->sin6_port = - S->port ? htons(S->port) : htons(DILLO_URL_HTTP_PORT); + S->port ? htons(S->port) : htons(HTTP_PORT); memcpy(&sin6->sin6_addr, dh->data, dh->alen); inet_ntop(dh->af, dh->data, buf, sizeof(buf)); if (a_Web_valid(S->web) && (S->web->flags & WEB_RootUrl)) @@ -447,9 +490,6 @@ static int Http_connect_socket(ChainLink *Info) dClose(S->SockFD); MSG("Http_connect_socket ERROR: %s\n", dStrerror(S->Err)); } else { - a_Chain_bcb(OpSend, Info, &S->SockFD, "FD"); - a_Chain_fcb(OpSend, Info, &S->SockFD, "FD"); - Http_send_query(S->Info, S); return 0; /* Success */ } } @@ -491,7 +531,6 @@ static int Http_must_use_proxy(const DilloUrl *url) /* * Return a new string for the request used to tunnel HTTPS through a proxy. - * As of 2009, the best reference appears to be section 5 of RFC 2817. */ char *a_Http_make_connect_str(const DilloUrl *url) { @@ -565,11 +604,11 @@ static void Http_dns_cb(int Status, Dlist *addr_list, void *data) hc = Http_host_connection_get(URL_HOST(HTTP_Proxy)); else hc = Http_host_connection_get(URL_HOST(S->web->url)); - Http_socket_enqueue(&hc->queue, S); + Http_socket_enqueue(hc, S); Http_connect_queued_sockets(hc); } else { /* DNS wasn't able to resolve the hostname */ - MSG_BW(S->web, 0, "ERROR: Dns can't resolve %s", + MSG_BW(S->web, 0, "ERROR: DNS can't resolve %s", (S->flags & HTTP_SOCKET_USE_PROXY) ? URL_HOST_(HTTP_Proxy) : URL_HOST_(S->web->url)); a_Chain_bfcb(OpAbort, S->Info, NULL, "Both"); @@ -620,6 +659,46 @@ static int Http_get(ChainLink *Info, void *Data1) } /* + * If any entry in the socket data queue can reuse our connection, set it up + * and send off a new query. + */ +static void Http_socket_reuse(int SKey) +{ + SocketData_t *new_sd, *old_sd = a_Klist_get_data(ValidSocks, SKey); + HostConnection_t *hc = Http_host_connection_get(old_sd->connected_to); + int i, n = dList_length(hc->queue); + + for (i = 0; i < n; i++) { + new_sd = dList_nth_data(hc->queue, i); + + if (a_Web_valid(new_sd->web) && old_sd->port == new_sd->port) { + new_sd->SockFD = old_sd->SockFD; + Http_fd_map_remove_entry(old_sd->SockFD); + a_Klist_remove(ValidSocks, SKey); + dFree(old_sd); + + dList_remove(hc->queue, new_sd); + new_sd->flags &= ~HTTP_SOCKET_QUEUED; + FdMapEntry_t *e = dNew0(FdMapEntry_t, 1); + e->fd = new_sd->SockFD; + e->skey = VOIDP2INT(new_sd->Info->LocalKey); + dList_append(fd_map, e); + + a_Chain_bcb(OpSend, new_sd->Info, &new_sd->SockFD, "FD"); + a_Chain_fcb(OpSend, new_sd->Info, &new_sd->SockFD, "FD"); + Http_send_query(new_sd->Info, new_sd); + new_sd->connected_to = hc->host; + return; + } + } + dClose(old_sd->SockFD); + Http_fd_map_remove_entry(old_sd->SockFD); + a_Klist_remove(ValidSocks, SKey); + hc->active_conns--; + dFree(old_sd); +} + +/* * CCC function for the HTTP module */ void a_Http_ccc(int Op, int Branch, int Dir, ChainLink *Info, @@ -648,7 +727,6 @@ void a_Http_ccc(int Op, int Branch, int Dir, ChainLink *Info, case OpEnd: /* finished the HTTP query branch */ a_Chain_bcb(OpEnd, Info, NULL, NULL); - Http_socket_free(SKey); dFree(Info); break; case OpAbort: @@ -659,51 +737,97 @@ void a_Http_ccc(int Op, int Branch, int Dir, ChainLink *Info, break; } } else { /* 1 FWD */ + SocketData_t *sd; /* HTTP send-query status branch */ switch (Op) { + case OpAbort: + if ((sd = a_Klist_get_data(ValidSocks, SKey))) + MSG_BW(sd->web, 1, "Can't get %s", URL_STR(sd->web->url)); + a_Chain_fcb(OpAbort, Info, NULL, "Both"); + Http_socket_free(SKey); + dFree(Info); + break; default: MSG_WARN("Unused CCC\n"); break; } } + } else if (Branch == 2) { + if (Dir == FWD) { + /* Receiving from server */ + switch (Op) { + case OpSend: + /* Data1 = dbuf */ + a_Chain_fcb(OpSend, Info, Data1, "send_page_2eof"); + break; + case OpEnd: + a_Chain_fcb(OpEnd, Info, NULL, NULL); + Http_socket_free(SKey); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } else { /* 2 BCK */ + switch (Op) { + case OpStart: + a_Chain_link_new(Info, a_Http_ccc, BCK, a_IO_ccc, 2, 2); + a_Chain_bcb(OpStart, Info, NULL, NULL); /* IORead */ + break; + case OpSend: + if (Data2) { + if (!strcmp(Data2, "FD")) { + int fd = *(int*)Data1; + FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd), + Http_fd_map_cmp); + Info->LocalKey = INT2VOIDP(fme->skey); + a_Chain_bcb(OpSend, Info, Data1, Data2); + } else if (!strcmp(Data2, "reply_complete")) { + a_Chain_bfcb(OpEnd, Info, NULL, NULL); + Http_socket_reuse(SKey); + dFree(Info); + } + } + break; + case OpAbort: + a_Chain_bcb(OpAbort, Info, NULL, NULL); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } } } - -static void Http_socket_queue_init(SocketQueue_t *sq) -{ - sq->head = NULL; - sq->tail = NULL; -} - -static void Http_socket_enqueue(SocketQueue_t *sq, SocketData_t* sock) +/* + * Add socket data to the queue. Pages/stylesheets/etc. have higher priority + * than images. + */ +static void Http_socket_enqueue(HostConnection_t *hc, SocketData_t* sock) { - SocketQueueEntry_t *se = dNew(SocketQueueEntry_t, 1); + if ((sock->web->flags & WEB_Image) == 0) { + int i, n = dList_length(hc->queue); - se->sock = sock; - se->next = NULL; + for (i = 0; i < n; i++) { + SocketData_t *curr = dList_nth_data(hc->queue, i); - if (sq->tail) - sq->tail->next = se; - sq->tail = se; - - if (! sq->head) - sq->head = se; + if (a_Web_valid(curr->web) && (curr->web->flags & WEB_Image)) { + dList_insert_pos(hc->queue, sock, i); + return; + } + } + } + dList_append(hc->queue, sock); } -static SocketData_t* Http_socket_dequeue(SocketQueue_t *sq) +static SocketData_t* Http_socket_dequeue(HostConnection_t *hc) { - SocketQueueEntry_t *se = sq->head; - SocketData_t *sd = NULL; - - if (se) { - sq->head = se->next; - if (sq->tail == se) - sq->tail = NULL; - sd = se->sock; - dFree(se); - } + SocketData_t *sd = dList_nth_data(hc->queue, 0); + dList_remove(hc->queue, sd); return sd; } @@ -720,7 +844,7 @@ static HostConnection_t *Http_host_connection_get(const char *host) } hc = dNew0(HostConnection_t, 1); - Http_socket_queue_init(&hc->queue); + hc->queue = dList_new(10); hc->host = dStrdup(host); dList_append(host_connections, hc); @@ -729,7 +853,8 @@ static HostConnection_t *Http_host_connection_get(const char *host) static void Http_host_connection_remove(HostConnection_t *hc) { - assert(hc->queue.head == NULL); + assert(dList_length(hc->queue) == 0); + dList_free(hc->queue); dList_remove_fast(host_connections, hc); dFree(hc->host); dFree(hc); @@ -738,15 +863,29 @@ static void Http_host_connection_remove(HostConnection_t *hc) static void Http_host_connection_remove_all() { HostConnection_t *hc; + SocketData_t *sd; while (dList_length(host_connections) > 0) { hc = (HostConnection_t*) dList_nth_data(host_connections, 0); - while (Http_socket_dequeue(&hc->queue)); + while ((sd = Http_socket_dequeue(hc))) + dFree(sd); Http_host_connection_remove(hc); } dList_free(host_connections); } +static void Http_fd_map_remove_all() +{ + FdMapEntry_t *fme; + int i, n = dList_length(fd_map); + + for (i = 0; i < n; i++) { + fme = (FdMapEntry_t *) dList_nth_data(fd_map, i); + dFree(fme); + } + dList_free(fd_map); +} + /* * Deallocate memory used by http module * (Call this one at exit time) @@ -754,6 +893,7 @@ static void Http_host_connection_remove_all() void a_Http_freeall(void) { Http_host_connection_remove_all(); + Http_fd_map_remove_all(); a_Klist_free(&ValidSocks); a_Url_free(HTTP_Proxy); dFree(HTTP_Proxy_Auth_base64); diff --git a/src/Makefile.am b/src/Makefile.am index 65a42cad..597a743b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2,7 +2,9 @@ AM_CPPFLAGS= \ -I$(top_srcdir) \ -DDILLO_SYSCONF='"$(sysconfdir)/"' \ -DDILLO_DOCDIR='"$(docdir)/"' \ + -DCUR_WORKING_DIR='"@BASE_CUR_WORKING_DIR@/src"' \ @LIBJPEG_CPPFLAGS@ + AM_CFLAGS = @LIBPNG_CFLAGS@ AM_CXXFLAGS = @LIBPNG_CFLAGS@ @LIBFLTK_CXXFLAGS@ @@ -96,6 +98,7 @@ dillo_SOURCES = \ plain.cc \ html.cc \ html.hh \ + html_charrefs.h \ html_common.hh \ form.cc \ form.hh \ diff --git a/src/cache.c b/src/cache.c index 14e862b5..189e18d5 100644 --- a/src/cache.c +++ b/src/cache.c @@ -55,7 +55,7 @@ typedef struct { Dstr *Data; /* Pointer to raw data */ Dstr *UTF8Data; /* Data after charset translation */ int DataRefcount; /* Reference count */ - Decode *TransferDecoder; /* Transfer decoder (e.g., chunked) */ + DecodeTransfer *TransferDecoder; /* Transfer decoder (e.g., chunked) */ Decode *ContentDecoder; /* Data decoder (e.g., gzip) */ Decode *CharsetDecoder; /* Translates text to UTF-8 encoding */ int ExpectedSize; /* Goal size of the HTTP transfer (0 if unknown)*/ @@ -110,7 +110,7 @@ static int Cache_entry_by_url_cmp(const void *v1, const void *v2) } /* - * Initialize dicache data + * Initialize cache data */ void a_Cache_init(void) { @@ -205,7 +205,7 @@ static void Cache_entry_init(CacheEntry_t *NewEntry, const DilloUrl *Url) NewEntry->CharsetDecoder = NULL; NewEntry->ExpectedSize = 0; NewEntry->TransferSize = 0; - NewEntry->Flags = CA_IsEmpty; + NewEntry->Flags = CA_IsEmpty | CA_KeepAlive; } /* @@ -308,7 +308,7 @@ static void Cache_entry_free(CacheEntry_t *entry) if (entry->CharsetDecoder) a_Decode_free(entry->CharsetDecoder); if (entry->TransferDecoder) - a_Decode_free(entry->TransferDecoder); + a_Decode_transfer_free(entry->TransferDecoder); if (entry->ContentDecoder) a_Decode_free(entry->ContentDecoder); dFree(entry); @@ -498,7 +498,7 @@ const char *a_Cache_set_content_type(const DilloUrl *url, const char *ctype, _MSG("a_Cache_set_content_type {%s} {%s}\n", ctype, URL_STR(url)); curr = Cache_current_content_type(entry); - if (entry->TypeMeta || (*from == 'h' && entry->TypeHdr) ) { + if (entry->TypeMeta || (*from == 'h' && entry->TypeHdr) ) { /* Type is already been set. Do nothing. * BTW, META overrides TypeHdr */ } else { @@ -652,7 +652,8 @@ static Dlist *Cache_parse_multiple_fields(const char *header, static void Cache_parse_header(CacheEntry_t *entry) { char *header = entry->Header->str; - char *Length, *Type, *location_str, *encoding; + bool_t server1point0 = !strncmp(entry->Header->str, "HTTP/1.0", 8); + char *Length, *Type, *location_str, *encoding, *connection; #ifndef DISABLE_COOKIES Dlist *Cookies; #endif @@ -716,6 +717,17 @@ static void Cache_parse_header(CacheEntry_t *entry) dList_free(warnings); } + if (server1point0) + entry->Flags &= ~CA_KeepAlive; + + if ((connection = Cache_parse_field(header, "Connection"))) { + if (!dStrAsciiCasecmp(connection, "close")) + entry->Flags &= ~CA_KeepAlive; + else if (server1point0 && !dStrAsciiCasecmp(connection, "keep-alive")) + entry->Flags |= CA_KeepAlive; + dFree(connection); + } + /* * Get Transfer-Encoding and initialize decoder */ @@ -834,6 +846,54 @@ static int Cache_get_header(CacheEntry_t *entry, return 0; } +static void Cache_finish_msg(CacheEntry_t *entry) +{ + if (entry->Flags & CA_GotData) { + /* already finished */ + return; + } + + if ((entry->ExpectedSize || entry->TransferSize) && + entry->TypeHdr == NULL) { + MSG_HTTP("Message with a body lacked Content-Type header.\n"); + } + if ((entry->Flags & CA_GotLength) && + (entry->ExpectedSize != entry->TransferSize)) { + MSG_HTTP("Content-Length does NOT match message body at\n" + "%s\n", URL_STR_(entry->Url)); + MSG("Expected size: %d, Transfer size: %d\n", + entry->ExpectedSize, entry->TransferSize); + } + if (!entry->TransferSize && !(entry->Flags & CA_Redirect) && + (entry->Flags & WEB_RootUrl)) { + char *eol = strchr(entry->Header->str, '\n'); + if (eol) { + char *status_line = dStrndup(entry->Header->str, + eol - entry->Header->str); + MSG_HTTP("Body of %s was empty. Server sent status: %s\n", + URL_STR_(entry->Url), status_line); + dFree(status_line); + } + } + entry->Flags |= CA_GotData; + entry->Flags &= ~CA_Stopped; /* it may catch up! */ + if (entry->TransferDecoder) { + a_Decode_transfer_free(entry->TransferDecoder); + entry->TransferDecoder = NULL; + } + if (entry->ContentDecoder) { + a_Decode_free(entry->ContentDecoder); + entry->ContentDecoder = NULL; + } + dStr_fit(entry->Data); /* fit buffer size! */ + + if ((entry = Cache_process_queue(entry))) { + if (entry->Flags & CA_GotHeader) { + Cache_unref_data(entry); + } + } +} + /* * Receive new data, update the reception buffer (for next read), update the * cache, and service the client queue. @@ -842,16 +902,17 @@ static int Cache_get_header(CacheEntry_t *entry, * 'Op' is the operation to perform * 'VPtr' is a (void) pointer to the IO control structure */ -void a_Cache_process_dbuf(int Op, const char *buf, size_t buf_size, - const DilloUrl *Url) +bool_t a_Cache_process_dbuf(int Op, const char *buf, size_t buf_size, + const DilloUrl *Url) { int offset, len; const char *str; Dstr *dstr1, *dstr2, *dstr3; + bool_t done = FALSE; CacheEntry_t *entry = Cache_entry_search(Url); /* Assert a valid entry (not aborted) */ - dReturn_if_fail (entry != NULL); + dReturn_val_if_fail (entry != NULL, FALSE); _MSG("__a_Cache_process_dbuf__\n"); @@ -875,7 +936,8 @@ void a_Cache_process_dbuf(int Op, const char *buf, size_t buf_size, /* Decode arrived data (<= 3 stages) */ if (entry->TransferDecoder) { - dstr1 = a_Decode_process(entry->TransferDecoder, str, len); + dstr1 = a_Decode_transfer_process(entry->TransferDecoder, str,len); + done = a_Decode_transfer_finished(entry->TransferDecoder); str = dstr1->str; len = dstr1->len; } @@ -896,51 +958,37 @@ void a_Cache_process_dbuf(int Op, const char *buf, size_t buf_size, if (entry->Data->len) entry->Flags &= ~CA_IsEmpty; + if ((entry->Flags & CA_GotLength) && + (entry->TransferSize >= entry->ExpectedSize)) { + done = TRUE; + } + if (!(entry->Flags & CA_KeepAlive)) { + /* Let IOClose finish it later */ + done = FALSE; + } + entry = Cache_process_queue(entry); + + if (entry && done) + Cache_finish_msg(entry); } } else if (Op == IOClose) { - if ((entry->ExpectedSize || entry->TransferSize) && - entry->TypeHdr == NULL) { - MSG_HTTP("Message with a body lacked Content-Type header.\n"); - } - if ((entry->Flags & CA_GotLength) && - (entry->ExpectedSize != entry->TransferSize)) { - MSG_HTTP("Content-Length does NOT match message body at\n" - "%s\n", URL_STR_(entry->Url)); - MSG("Expected size: %d, Transfer size: %d\n", - entry->ExpectedSize, entry->TransferSize); - } - if (!entry->TransferSize && !(entry->Flags & CA_Redirect) && - (entry->Flags & WEB_RootUrl)) { - char *eol = strchr(entry->Header->str, '\n'); - if (eol) { - char *status_line = dStrndup(entry->Header->str, - eol - entry->Header->str); - MSG_HTTP("Body was empty. Server sent status: %s\n", status_line); - dFree(status_line); - } - } - entry->Flags |= CA_GotData; - entry->Flags &= ~CA_Stopped; /* it may catch up! */ - if (entry->TransferDecoder) { - a_Decode_free(entry->TransferDecoder); - entry->TransferDecoder = NULL; - } - if (entry->ContentDecoder) { - a_Decode_free(entry->ContentDecoder); - entry->ContentDecoder = NULL; - } - dStr_fit(entry->Data); /* fit buffer size! */ + Cache_finish_msg(entry); + } else if (Op == IOAbort) { + int i; + CacheClient_t *Client; - if ((entry = Cache_process_queue(entry))) { - if (entry->Flags & CA_GotHeader) { - Cache_unref_data(entry); + for (i = 0; (Client = dList_nth_data(ClientQueue, i)); ++i) { + if (Client->Url == entry->Url) { + DilloWeb *web = (DilloWeb *)Client->Web; + + a_Bw_remove_client(web->bw, Client->Key); + Cache_client_dequeue(Client); + --i; /* Keep the index value in the next iteration */ } } - } else if (Op == IOAbort) { - /* unused */ - MSG("a_Cache_process_dbuf Op = IOAbort; not implemented!\n"); } + return done; } /* @@ -1264,7 +1312,6 @@ static CacheEntry_t *Cache_process_queue(CacheEntry_t *entry) /* Trigger cleanup when there are no cache clients */ if (dList_length(ClientQueue) == 0) { - _MSG(" a_Dicache_cleanup()\n"); a_Dicache_cleanup(); } diff --git a/src/cache.h b/src/cache.h index c39e4600..f3b064f2 100644 --- a/src/cache.h +++ b/src/cache.h @@ -33,6 +33,7 @@ extern "C" { #define CA_InternalUrl 0x800 /* URL content is generated by dillo */ #define CA_HugeFile 0x1000 /* URL content is too big */ #define CA_IsEmpty 0x2000 /* True until a byte of content arrives */ +#define CA_KeepAlive 0x4000 typedef struct CacheClient CacheClient_t; @@ -67,7 +68,7 @@ const char *a_Cache_set_content_type(const DilloUrl *url, const char *ctype, const char *from); uint_t a_Cache_get_flags(const DilloUrl *url); uint_t a_Cache_get_flags_with_redirection(const DilloUrl *url); -void a_Cache_process_dbuf(int Op, const char *buf, size_t buf_size, +bool_t a_Cache_process_dbuf(int Op, const char *buf, size_t buf_size, const DilloUrl *Url); int a_Cache_download_enabled(const DilloUrl *url); void a_Cache_entry_remove_by_url(DilloUrl *url); @@ -345,7 +345,7 @@ static char *Capi_dpi_build_cmd(DilloWeb *web, char *server) /* * Send the requested URL's source to the "view source" dpi */ -static void Capi_dpi_send_source(BrowserWindow *bw, DilloUrl *url) +static void Capi_dpi_send_source(BrowserWindow *bw, DilloUrl *url) { char *p, *buf, *cmd, size_str[32], *server="vsource"; int buf_size; @@ -385,81 +385,80 @@ int a_Capi_open_url(DilloWeb *web, CA_Callback_t Call, void *CbData) int safe = 0, ret = 0, use_cache = 0; /* web->requester is NULL if the action is initiated by user */ - if (!(a_Capi_get_flags(web->url) & CAPI_IsCached || - web->requester == NULL || - a_Domain_permit(web->requester, web->url))) { - return 0; - } - - /* reload test */ - reload = (!(a_Capi_get_flags(web->url) & CAPI_IsCached) || - (URL_FLAGS(web->url) & URL_E2EQuery)); - - if (web->flags & WEB_Download) { - /* download request: if cached save from cache, else - * for http, ftp or https, use the downloads dpi */ - if (a_Capi_get_flags_with_redirection(web->url) & CAPI_IsCached) { - if (web->filename) { - if ((web->stream = fopen(web->filename, "w"))) { - use_cache = 1; - } else { - MSG_WARN("Cannot open \"%s\" for writing.\n", web->filename); + if (a_Capi_get_flags(web->url) & CAPI_IsCached || + web->requester == NULL || + a_Domain_permit(web->requester, web->url)) { + + /* reload test */ + reload = (!(a_Capi_get_flags(web->url) & CAPI_IsCached) || + (URL_FLAGS(web->url) & URL_E2EQuery)); + + if (web->flags & WEB_Download) { + /* download request: if cached save from cache, else + * for http, ftp or https, use the downloads dpi */ + if (a_Capi_get_flags_with_redirection(web->url) & CAPI_IsCached) { + if (web->filename) { + if ((web->stream = fopen(web->filename, "w"))) { + use_cache = 1; + } else { + MSG_WARN("Cannot open \"%s\" for writing.\n", web->filename); + } } + } else if (a_Cache_download_enabled(web->url)) { + server = "downloads"; + cmd = Capi_dpi_build_cmd(web, server); + a_Capi_dpi_send_cmd(web->url, web->bw, cmd, server, 1); + dFree(cmd); + } else { + MSG_WARN("Ignoring download request for '%s': " + "not in cache and not downloadable.\n", + URL_STR(web->url)); } - } else if (a_Cache_download_enabled(web->url)) { - server = "downloads"; - cmd = Capi_dpi_build_cmd(web, server); - a_Capi_dpi_send_cmd(web->url, web->bw, cmd, server, 1); - dFree(cmd); - } else { - MSG_WARN("Ignoring download request for '%s': " - "not in cache and not downloadable.\n", - URL_STR(web->url)); - } - - } else if (Capi_url_uses_dpi(web->url, &server)) { - /* dpi request */ - if ((safe = a_Capi_dpi_verify_request(web->bw, web->url))) { - if (dStrAsciiCasecmp(scheme, "dpi") == 0) { - if (strcmp(server, "vsource") == 0) { - /* allow "view source" reload upon user request */ - } else { - /* make the other "dpi:/" prefixed urls always reload. */ - a_Url_set_flags(web->url, URL_FLAGS(web->url) | URL_E2EQuery); - reload = 1; + + } else if (Capi_url_uses_dpi(web->url, &server)) { + /* dpi request */ + if ((safe = a_Capi_dpi_verify_request(web->bw, web->url))) { + if (dStrAsciiCasecmp(scheme, "dpi") == 0) { + if (strcmp(server, "vsource") == 0) { + /* allow "view source" reload upon user request */ + } else { + /* make the other "dpi:/" prefixed urls always reload. */ + a_Url_set_flags(web->url, URL_FLAGS(web->url) |URL_E2EQuery); + reload = 1; + } + } + if (reload) { + a_Capi_conn_abort_by_url(web->url); + /* Send dpip command */ + _MSG("a_Capi_open_url, reload url='%s'\n", URL_STR(web->url)); + cmd = Capi_dpi_build_cmd(web, server); + a_Capi_dpi_send_cmd(web->url, web->bw, cmd, server, 1); + dFree(cmd); + if (strcmp(server, "vsource") == 0) { + Capi_dpi_send_source(web->bw, web->url); + } } + use_cache = 1; } + dFree(server); + + } else if (!dStrAsciiCasecmp(scheme, "http")) { + /* http request */ if (reload) { a_Capi_conn_abort_by_url(web->url); - /* Send dpip command */ - _MSG("a_Capi_open_url, reload url='%s'\n", URL_STR(web->url)); - cmd = Capi_dpi_build_cmd(web, server); - a_Capi_dpi_send_cmd(web->url, web->bw, cmd, server, 1); - dFree(cmd); - if (strcmp(server, "vsource") == 0) { - Capi_dpi_send_source(web->bw, web->url); - } + /* create a new connection and start the CCC operations */ + conn = Capi_conn_new(web->url, web->bw, "http", "none"); + /* start the reception branch before the query one because the DNS + * may callback immediately. This may avoid a race condition. */ + a_Capi_ccc(OpStart, 2, BCK, a_Chain_new(), conn, "http"); + a_Capi_ccc(OpStart, 1, BCK, a_Chain_new(), conn, web); } use_cache = 1; - } - dFree(server); - - } else if (!dStrAsciiCasecmp(scheme, "http")) { - /* http request */ - if (reload) { - a_Capi_conn_abort_by_url(web->url); - /* create a new connection and start the CCC operations */ - conn = Capi_conn_new(web->url, web->bw, "http", "none"); - /* start the reception branch before the query one because the DNS - * may callback immediately. This may avoid a race condition. */ - a_Capi_ccc(OpStart, 2, BCK, a_Chain_new(), conn, "http"); - a_Capi_ccc(OpStart, 1, BCK, a_Chain_new(), conn, web); - } - use_cache = 1; - } else if (!dStrAsciiCasecmp(scheme, "about")) { - /* internal request */ - use_cache = 1; + } else if (!dStrAsciiCasecmp(scheme, "about")) { + /* internal request */ + use_cache = 1; + } } if (use_cache) { @@ -681,6 +680,7 @@ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info, case OpAbort: conn = Info->LocalKey; conn->InfoSend = NULL; + a_Cache_process_dbuf(IOAbort, NULL, 0, conn->url); if (Data2) { if (!strcmp(Data2, "DpidERROR")) { a_UIcmd_set_msg(conn->bw, @@ -714,7 +714,10 @@ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info, Capi_conn_ref(conn); Info->LocalKey = conn; conn->InfoRecv = Info; - a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Dpi_ccc, 2, 2); + if (strcmp(conn->server, "http") == 0) + a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Http_ccc, 2, 2); + else + a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Dpi_ccc, 2, 2); a_Chain_bcb(OpStart, Info, NULL, Data2); break; case OpSend: @@ -744,7 +747,15 @@ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info, if (strcmp(Data2, "send_page_2eof") == 0) { /* Data1 = dbuf */ DataBuf *dbuf = Data1; - a_Cache_process_dbuf(IORead, dbuf->Buf, dbuf->Size, conn->url); + bool_t finished = a_Cache_process_dbuf(IORead, dbuf->Buf, + dbuf->Size, conn->url); + if (finished && Capi_conn_valid(conn) && conn->InfoRecv) { + /* If we have a persistent connection where cache tells us + * that we've received the full response, and cache didn't + * trigger an abort and tear everything down, tell upstream. + */ + a_Chain_bcb(OpSend, conn->InfoRecv, NULL, "reply_complete"); + } } else if (strcmp(Data2, "send_status_message") == 0) { a_UIcmd_set_msg(conn->bw, "%s", Data1); } else if (strcmp(Data2, "chat") == 0) { diff --git a/src/colors.c b/src/colors.c index fe3598eb..237e63a1 100644 --- a/src/colors.c +++ b/src/colors.c @@ -303,7 +303,7 @@ int32_t a_Color_parse (const char *str, int32_t default_color, int *err) static int Color_distance(long c1, long c2) { return (labs((c1 & 0x0000ff) - (c2 & 0x0000ff)) + - labs(((c1 & 0x00ff00) - (c2 & 0x00ff00)) >> 8) + + labs(((c1 & 0x00ff00) - (c2 & 0x00ff00)) >> 8) + labs(((c1 & 0xff0000) - (c2 & 0xff0000)) >> 16)) / 75; } #endif diff --git a/src/cookies.h b/src/cookies.h index 1cdb82ac..5e4d8c59 100644 --- a/src/cookies.h +++ b/src/cookies.h @@ -5,18 +5,17 @@ extern "C" { #endif /* __cplusplus */ +void a_Cookies_init( void ); #ifdef DISABLE_COOKIES # define a_Cookies_get_query(url, requester) dStrdup("") # define a_Cookies_set() ; -# define a_Cookies_init() ; # define a_Cookies_freeall() ; #else char *a_Cookies_get_query(const DilloUrl *query_url, const DilloUrl *requester); void a_Cookies_set(Dlist *cookie_string, const DilloUrl *set_url, const char *server_date); - void a_Cookies_init( void ); void a_Cookies_freeall( void ); #endif @@ -544,7 +544,7 @@ void CssContext::addRule (CssSelector *sel, CssPropertyList *props, if (order == CSS_PRIMARY_USER_AGENT) { userAgentSheet.addRule (rule); - } else { + } else { sheet[order].addRule (rule); } } @@ -133,7 +133,7 @@ inline float CSS_LENGTH_VALUE (CssLength l) { case CSS_LENGTH_TYPE_EX: case CSS_LENGTH_TYPE_PERCENTAGE: case CSS_LENGTH_TYPE_RELATIVE: - return ((float)(l & ~7)) / (1 << 15); + return ((float)(l & ~7)) / (1 << 15); case CSS_LENGTH_TYPE_AUTO: return 0.0; default: diff --git a/src/cssparser.cc b/src/cssparser.cc index 369dd67f..1487a605 100644 --- a/src/cssparser.cc +++ b/src/cssparser.cc @@ -72,6 +72,10 @@ static const char *const Css_border_width_enum_vals[] = { "thin", "medium", "thick", NULL }; +static const char *const Css_clear_enum_vals[] = { + "left", "right", "both", "none", NULL +}; + static const char *const Css_cursor_enum_vals[] = { "crosshair", "default", "pointer", "move", "e-resize", "ne-resize", "nw-resize", "n-resize", "se-resize", "sw-resize", "s-resize", @@ -84,6 +88,10 @@ static const char *const Css_display_enum_vals[] = { "table-cell", NULL }; +static const char *const Css_float_enum_vals[] = { + "none", "left", "right", NULL +}; + static const char *const Css_font_size_enum_vals[] = { "large", "larger", "medium", "small", "smaller", "xx-large", "xx-small", "x-large", "x-small", NULL @@ -121,6 +129,14 @@ static const char *const Css_list_style_type_enum_vals[] = { "katakana-iroha", "none", NULL }; +static const char *const Css_overflow_enum_vals[] = { + "visible", "hidden", "scroll", "auto", NULL +}; + +static const char *const Css_position_enum_vals[] = { + "static", "relative", "absolute", "fixed", NULL +}; + static const char *const Css_text_align_enum_vals[] = { "left", "right", "center", "justify", "string", NULL }; @@ -182,9 +198,9 @@ const CssPropertyInfo Css_property_info[CSS_PROPERTY_LAST] = { Css_border_style_enum_vals}, {"border-top-width", {CSS_TYPE_ENUM, CSS_TYPE_LENGTH, CSS_TYPE_UNUSED}, Css_border_width_enum_vals}, - {"bottom", {CSS_TYPE_UNUSED}, NULL}, + {"bottom", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_UNUSED}, NULL}, {"caption-side", {CSS_TYPE_UNUSED}, NULL}, - {"clear", {CSS_TYPE_UNUSED}, NULL}, + {"clear", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_clear_enum_vals}, {"clip", {CSS_TYPE_UNUSED}, NULL}, {"color", {CSS_TYPE_COLOR, CSS_TYPE_UNUSED}, NULL}, {"content", {CSS_TYPE_STRING, CSS_TYPE_UNUSED}, NULL}, @@ -194,7 +210,7 @@ const CssPropertyInfo Css_property_info[CSS_PROPERTY_LAST] = { {"direction", {CSS_TYPE_UNUSED}, NULL}, {"display", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_display_enum_vals}, {"empty-cells", {CSS_TYPE_UNUSED}, NULL}, - {"float", {CSS_TYPE_UNUSED}, NULL}, + {"float", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_float_enum_vals}, {"font-family", {CSS_TYPE_SYMBOL, CSS_TYPE_UNUSED}, NULL}, {"font-size", {CSS_TYPE_ENUM, CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_UNUSED}, Css_font_size_enum_vals}, @@ -227,21 +243,25 @@ const CssPropertyInfo Css_property_info[CSS_PROPERTY_LAST] = { {CSS_TYPE_SIGNED_LENGTH, CSS_TYPE_AUTO, CSS_TYPE_UNUSED}, NULL}, {"marker-offset", {CSS_TYPE_UNUSED}, NULL}, {"marks", {CSS_TYPE_UNUSED}, NULL}, - {"max-height", {CSS_TYPE_UNUSED}, NULL}, - {"max-width", {CSS_TYPE_UNUSED}, NULL}, - {"min-height", {CSS_TYPE_UNUSED}, NULL}, - {"min-width", {CSS_TYPE_UNUSED}, NULL}, + {"max-height", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_AUTO, CSS_TYPE_UNUSED}, + NULL}, + {"max-width", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_AUTO, CSS_TYPE_UNUSED}, + NULL}, + {"min-height", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_AUTO, CSS_TYPE_UNUSED}, + NULL}, + {"min-width", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_AUTO, CSS_TYPE_UNUSED}, + NULL}, {"outline-color", {CSS_TYPE_UNUSED}, NULL}, {"outline-style", {CSS_TYPE_UNUSED}, NULL}, {"outline-width", {CSS_TYPE_UNUSED}, NULL}, - {"overflow", {CSS_TYPE_UNUSED}, NULL}, + {"overflow", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_overflow_enum_vals}, {"padding-bottom", {CSS_TYPE_LENGTH, CSS_TYPE_UNUSED}, NULL}, {"padding-left", {CSS_TYPE_LENGTH, CSS_TYPE_UNUSED}, NULL}, {"padding-right", {CSS_TYPE_LENGTH, CSS_TYPE_UNUSED}, NULL}, {"padding-top", {CSS_TYPE_LENGTH, CSS_TYPE_UNUSED}, NULL}, - {"position", {CSS_TYPE_UNUSED}, NULL}, + {"position", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_position_enum_vals}, {"quotes", {CSS_TYPE_UNUSED}, NULL}, - {"right", {CSS_TYPE_UNUSED}, NULL}, + {"right", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_UNUSED}, NULL}, {"text-align", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_text_align_enum_vals}, {"text-decoration", {CSS_TYPE_MULTI_ENUM, CSS_TYPE_UNUSED}, Css_text_decoration_enum_vals}, @@ -249,7 +269,7 @@ const CssPropertyInfo Css_property_info[CSS_PROPERTY_LAST] = { {"text-shadow", {CSS_TYPE_UNUSED}, NULL}, {"text-transform", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_text_transform_enum_vals}, - {"top", {CSS_TYPE_UNUSED}, NULL}, + {"top", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_UNUSED}, NULL}, {"unicode-bidi", {CSS_TYPE_UNUSED}, NULL}, {"vertical-align",{CSS_TYPE_ENUM, CSS_TYPE_UNUSED},Css_vertical_align_vals}, {"visibility", {CSS_TYPE_UNUSED}, NULL}, @@ -277,7 +297,7 @@ typedef struct { } type; const CssPropertyName *properties; /* CSS_SHORTHAND_MULTIPLE: * must be terminated by - * CSS_PROPERTY_END + * CSS_PROPERTY_END * CSS_SHORTHAND_DIRECTIONS: * must have length 4 * CSS_SHORTHAND_BORDERS: @@ -720,7 +740,9 @@ bool CssParser::tokenMatchesProperty(CssPropertyName prop, CssValueType *type) dStrAsciiCasecmp(tval, "top") == 0 || dStrAsciiCasecmp(tval, "bottom") == 0)) return true; - // Fall Through (lenght and percentage) + if (ttype == CSS_TK_DECINT || ttype == CSS_TK_FLOAT) + return true; + break; case CSS_TYPE_LENGTH_PERCENTAGE: case CSS_TYPE_LENGTH_PERCENTAGE_NUMBER: case CSS_TYPE_LENGTH: @@ -766,7 +788,8 @@ bool CssParser::tokenMatchesProperty(CssPropertyName prop, CssValueType *type) case CSS_TYPE_URI: if (ttype == CSS_TK_SYMBOL && - dStrAsciiCasecmp(tval, "url") == 0) + (dStrAsciiCasecmp(tval, "url") == 0 || + dStrAsciiCasecmp(tval, "none") == 0)) return true; break; @@ -1044,12 +1067,16 @@ bool CssParser::parseValue(CssPropertyName prop, break; case CSS_TYPE_URI: - if (ttype == CSS_TK_SYMBOL && - dStrAsciiCasecmp(tval, "url") == 0) { - val->strVal = parseUrl(); - nextToken(); - if (val->strVal) + if (ttype == CSS_TK_SYMBOL) { + if (dStrAsciiCasecmp(tval, "url") == 0) { + val->strVal = parseUrl(); + if (val->strVal) + ret = true; + } else if (dStrAsciiCasecmp(tval, "none") == 0) { + val->strVal = NULL; ret = true; + } + nextToken(); } break; @@ -1106,6 +1133,9 @@ bool CssParser::parseValue(CssPropertyName prop, if (parseValue(prop, CSS_TYPE_LENGTH_PERCENTAGE, &valTmp)) { pos[i] = valTmp.intVal; ret = true; + } else if (parseValue(prop, CSS_TYPE_SIGNED_LENGTH, &valTmp)) { + pos[i] = valTmp.intVal; + ret = true; } else // ... but something may still fail. h[i] = v[i] = false; diff --git a/src/decode.c b/src/decode.c index 53a0d621..6d838d41 100644 --- a/src/decode.c +++ b/src/decode.c @@ -21,9 +21,10 @@ static const int bufsize = 8*1024; /* - * Decode chunked data + * Decode 'Transfer-Encoding: chunked' data */ -static Dstr *Decode_chunked(Decode *dc, const char *instr, int inlen) +Dstr *a_Decode_transfer_process(DecodeTransfer *dc, const char *instr, + int inlen) { char *inputPtr, *eol; int inputRemaining; @@ -66,6 +67,7 @@ static Dstr *Decode_chunked(Decode *dc, const char *instr, int inlen) } if (!(chunkRemaining = strtol(inputPtr, NULL, 0x10))) { + dc->finished = TRUE; break; /* A chunk length of 0 means we're done! */ } inputRemaining -= (eol - inputPtr) + 1; @@ -80,10 +82,16 @@ static Dstr *Decode_chunked(Decode *dc, const char *instr, int inlen) return output; } -static void Decode_chunked_free(Decode *dc) +bool_t a_Decode_transfer_finished(DecodeTransfer *dc) +{ + return dc->finished; +} + +void a_Decode_transfer_free(DecodeTransfer *dc) { dFree(dc->state); dStr_free(dc->leftover, 1); + dFree(dc); } static void Decode_compression_free(Decode *dc) @@ -208,7 +216,7 @@ static Dstr *Decode_deflate(Decode *dc, const char *instr, int inlen) dStr_free(output, 1); (void)inflateEnd(zs); dFree(dc->state); - dc->state = zs = dNew(z_stream, 1);; + dc->state = zs = dNew(z_stream, 1); zs->zalloc = NULL; zs->zfree = NULL; zs->next_in = NULL; @@ -280,19 +288,17 @@ static void Decode_charset_free(Decode *dc) /* * Initialize transfer decoder. Currently handles "chunked". */ -Decode *a_Decode_transfer_init(const char *format) +DecodeTransfer *a_Decode_transfer_init(const char *format) { - Decode *dc = NULL; + DecodeTransfer *dc = NULL; if (format && !dStrAsciiCasecmp(format, "chunked")) { int *chunk_remaining = dNew(int, 1); *chunk_remaining = 0; - dc = dNew(Decode, 1); + dc = dNew(DecodeTransfer, 1); dc->leftover = dStr_new(""); dc->state = chunk_remaining; - dc->decode = Decode_chunked; - dc->free = Decode_chunked_free; - dc->buffer = NULL; /* not used */ + dc->finished = FALSE; _MSG("chunked!\n"); } return dc; diff --git a/src/decode.h b/src/decode.h index 279807a6..06c987f6 100644 --- a/src/decode.h +++ b/src/decode.h @@ -15,7 +15,21 @@ typedef struct Decode { void (*free) (struct Decode *dc); } Decode; -Decode *a_Decode_transfer_init(const char *format); +/* I'm not going to shoehorn the decoders into the same form anymore. They + * can evolve independently. + */ +typedef struct DecodeTransfer { + Dstr *leftover; + void *state; + bool_t finished; /* has the terminating chunk been seen? */ +} DecodeTransfer; + +DecodeTransfer *a_Decode_transfer_init(const char *format); +Dstr *a_Decode_transfer_process(DecodeTransfer *dc, const char *instr, + int inlen); +bool_t a_Decode_transfer_finished(DecodeTransfer *dc); +void a_Decode_transfer_free(DecodeTransfer *dc); + Decode *a_Decode_content_init(const char *format); Decode *a_Decode_charset_init(const char *format); Dstr *a_Decode_process(Decode *dc, const char *instr, int inlen); diff --git a/src/dicache.c b/src/dicache.c index db3b86b2..a2904c32 100644 --- a/src/dicache.c +++ b/src/dicache.c @@ -28,15 +28,10 @@ enum { DIC_Jpeg }; -typedef struct { - int valid; /* flag */ - DilloUrl *url; /* primary "Key" for this dicache entry */ - DICacheEntry *first; /* pointer to the first dicache entry in this list */ -} DICacheNode; /* - * List of DICacheNode. One node per URL. Each node may have several - * versions of the same image in a linked list. + * List of DICacheEntry. May hold several versions of the same image, + * although most of the time it holds just one. */ static Dlist *CachedIMGs = NULL; @@ -45,24 +40,20 @@ static uint_t dicache_size_total; /* invariant: dicache_size_total is * of all the images in the dicache. */ /* - * Compare two dicache nodes + * Compare function for image entries */ -static int Dicache_node_cmp(const void *v1, const void *v2) +static int Dicache_entry_cmp(const void *v1, const void *v2) { - const DICacheNode *n1 = v1, *n2 = v2; - - return a_Url_cmp(n1->url, n2->url); -} - -/* - * Compare function for searching a node by Url - */ -static int Dicache_node_by_url_cmp(const void *v1, const void *v2) -{ - const DICacheNode *node = v1; - const DilloUrl *url = v2; - - return a_Url_cmp(node->url, url); + const DICacheEntry *e1 = v1, *e2 = v2; + + int st = a_Url_cmp(e1->url, e2->url); + if (st == 0) { + if (e2->version == DIC_Last) + st = (e1->Flags & DIF_Last ? 0 : -1); + else + st = (e1->version - e2->version); + } + return st; } /* @@ -83,6 +74,8 @@ static DICacheEntry *Dicache_entry_new(void) entry->width = 0; entry->height = 0; + entry->Flags = DIF_Valid; + entry->SurvCleanup = 0; entry->type = DILLO_IMG_TYPE_NOTSET; entry->cmap = NULL; entry->v_imgbuf = NULL; @@ -97,41 +90,29 @@ static DICacheEntry *Dicache_entry_new(void) entry->DecoderData = NULL; entry->DecodedSize = 0; - entry->next = NULL; - return entry; } /* * Add a new entry in the dicache - * (a single node (URL) may have several entries) + * (a single URL may have several entries) */ static DICacheEntry *Dicache_add_entry(const DilloUrl *Url) { - DICacheEntry *entry; - DICacheNode *node; + DICacheEntry e, *entry, *last; entry = Dicache_entry_new(); - - if ((node = dList_find_sorted(CachedIMGs, Url, Dicache_node_by_url_cmp))) { - /* this URL is already in CachedIMGs, add entry at the END of the list */ - DICacheEntry *ptr = node->first; - - node->valid = 1; - for ( ; ptr->next; ptr = ptr->next); - ptr->next = entry; - entry->version = ptr->version+1; - entry->url = node->url; - - } else { /* no node yet, so create one */ - DICacheNode *node = dNew(DICacheNode, 1); - - node->url = a_Url_dup(Url); - entry->url = node->url; - node->first = entry; - node->valid = 1; - dList_insert_sorted(CachedIMGs, node, Dicache_node_cmp); + e.url = (DilloUrl*)Url; + e.version = DIC_Last; + last = dList_find_sorted(CachedIMGs, &e, Dicache_entry_cmp); + if (last) { + /* URL is already in CachedIMGs, make a new version */ + last->Flags &= ~DIF_Last; + entry->version = last->version + 1; } + entry->url = a_Url_dup(Url); + entry->Flags |= DIF_Last; + dList_insert_sorted(CachedIMGs, entry, Dicache_entry_cmp); return entry; } @@ -145,23 +126,15 @@ static DICacheEntry *Dicache_add_entry(const DilloUrl *Url) */ DICacheEntry *a_Dicache_get_entry(const DilloUrl *Url, int version) { - DICacheNode *node; + DICacheEntry e; DICacheEntry *entry = NULL; dReturn_val_if_fail(version != 0, NULL); - - node = dList_find_sorted(CachedIMGs, Url, Dicache_node_by_url_cmp); - if (node) { - if (version == DIC_Last) { - if (node->valid) { - entry = node->first; - for ( ; (entry && entry->next); entry = entry->next); - } - } else { - entry = node->first; - for ( ; entry && entry->version != version; entry = entry->next) ; - } - } + e.url = (DilloUrl*)Url; + e.version = version; + entry = dList_find_sorted(CachedIMGs, &e, Dicache_entry_cmp); + if (entry && !(entry->Flags & DIF_Valid) && version == DIC_Last) + entry = NULL; return entry; } @@ -170,57 +143,48 @@ DICacheEntry *a_Dicache_get_entry(const DilloUrl *Url, int version) */ static void Dicache_remove(const DilloUrl *Url, int version) { - DICacheNode *node; - DICacheEntry *entry, *prev; - _MSG("Dicache_remove url=%s\n", URL_STR(Url)); - node = dList_find_sorted(CachedIMGs, Url, Dicache_node_by_url_cmp); - prev = entry = (node) ? node->first : NULL; + DICacheEntry e, *entry; - while (entry && (entry->version != version) ) { - prev = entry; - entry = entry->next; - } - - if (entry) { - _MSG("Dicache_remove Decoder=%p DecoderData=%p\n", - entry->Decoder, entry->DecoderData); - /* Eliminate this dicache entry */ - dFree(entry->cmap); - a_Bitvec_free(entry->BitVec); - a_Imgbuf_unref(entry->v_imgbuf); - if (entry->Decoder) { - entry->Decoder(CA_Abort, entry->DecoderData); - } - dicache_size_total -= entry->TotalSize; - - if (node->first == entry) { - if (!entry->next) { - /* last entry with this URL. Remove the node as well */ - dList_remove(CachedIMGs, node); - a_Url_free(node->url); - dFree(node); - } else - node->first = entry->next; - } else { - prev->next = entry->next; - } - dFree(entry); + _MSG("Dicache_remove url=%s\n", URL_STR(Url)); + e.url = (DilloUrl*)Url; + e.version = version; + entry = dList_find_sorted(CachedIMGs, &e, Dicache_entry_cmp); + dReturn_if (entry == NULL); + + _MSG("Dicache_remove Imgbuf=%p Decoder=%p DecoderData=%p\n", + entry->v_imgbuf, entry->Decoder, entry->DecoderData); + /* Eliminate this dicache entry */ + dList_remove(CachedIMGs, entry); + dicache_size_total -= entry->TotalSize; + + /* entry cleanup */ + a_Url_free(entry->url); + dFree(entry->cmap); + a_Bitvec_free(entry->BitVec); + a_Imgbuf_unref(entry->v_imgbuf); + if (entry->Decoder) { + entry->Decoder(CA_Abort, entry->DecoderData); } + dFree(entry); } /* - * Unrefs the counter of a dicache entry, and _if_ no DwImage is acessing - * this buffer, then we call Dicache_remove() to do the job. + * Unrefs the counter of a dicache entry (it counts cache clients). + * If there're no clients and no imgbuf, remove the entry. + * Otherwise, let a_Dicache_cleanup() do the job later + * (keeping it cached meanwhile for e.g. reload, repush, back/fwd). */ void a_Dicache_unref(const DilloUrl *Url, int version) { DICacheEntry *entry; - _MSG("a_Dicache_unref\n"); if ((entry = a_Dicache_get_entry(Url, version))) { - if (--entry->RefCount == 0) { + _MSG("a_Dicache_unref: RefCount=%d State=%d ImgbufLastRef=%d\n", + entry->RefCount, entry->State, + entry->v_imgbuf ? a_Imgbuf_last_reference(entry->v_imgbuf) : -1); + if (entry->RefCount > 0) --entry->RefCount; + if (entry->RefCount == 0 && entry->v_imgbuf == NULL) Dicache_remove(Url, version); - } } } @@ -244,11 +208,9 @@ DICacheEntry* a_Dicache_ref(const DilloUrl *Url, int version) */ void a_Dicache_invalidate_entry(const DilloUrl *Url) { - DICacheNode *node; - - node = dList_find_sorted(CachedIMGs, Url, Dicache_node_by_url_cmp); - if (node) - node->valid = 0; + DICacheEntry *entry = a_Dicache_get_entry(Url, DIC_Last); + if (entry) + entry->Flags &= ~DIF_Valid; } @@ -256,7 +218,9 @@ void a_Dicache_invalidate_entry(const DilloUrl *Url) /* * Set image's width, height & type - * (By now, we'll use the image information despite the html tags --Jcid) + * - 'width' and 'height' come from the image data. + * - HTML width and height attrs are handled with setNonCssHint. + * - CSS sizing is handled by the CSS engine. */ void a_Dicache_set_parms(DilloUrl *url, int version, DilloImage *Image, uint_t width, uint_t height, DilloImgType type, @@ -269,7 +233,7 @@ void a_Dicache_set_parms(DilloUrl *url, int version, DilloImage *Image, /* Find the DicEntry for this Image */ DicEntry = a_Dicache_get_entry(url, version); dReturn_if_fail ( DicEntry != NULL ); - /* Parameters already set? */ + /* Parameters already set? Don't do it twice. */ dReturn_if_fail ( DicEntry->State < DIC_SetParms ); _MSG(" RefCount=%d version=%d\n", DicEntry->RefCount, DicEntry->version); @@ -292,7 +256,7 @@ void a_Dicache_set_parms(DilloUrl *url, int version, DilloImage *Image, /* * Implement the set_cmap method for the Image */ -void a_Dicache_set_cmap(DilloUrl *url, int version, DilloImage *Image, +void a_Dicache_set_cmap(DilloUrl *url, int version, int bg_color, const uchar_t *cmap, uint_t num_colors, int num_colors_max, int bg_index) { @@ -305,9 +269,9 @@ void a_Dicache_set_cmap(DilloUrl *url, int version, DilloImage *Image, DicEntry->cmap = dNew0(uchar_t, 3 * num_colors_max); memcpy(DicEntry->cmap, cmap, 3 * num_colors); if (bg_index >= 0 && (uint_t)bg_index < num_colors) { - DicEntry->cmap[bg_index * 3] = (Image->bg_color >> 16) & 0xff; - DicEntry->cmap[bg_index * 3 + 1] = (Image->bg_color >> 8) & 0xff; - DicEntry->cmap[bg_index * 3 + 2] = (Image->bg_color) & 0xff; + DicEntry->cmap[bg_index * 3] = (bg_color >> 16) & 0xff; + DicEntry->cmap[bg_index * 3 + 1] = (bg_color >> 8) & 0xff; + DicEntry->cmap[bg_index * 3 + 2] = (bg_color) & 0xff; } DicEntry->State = DIC_SetCmap; @@ -368,6 +332,9 @@ void a_Dicache_close(DilloUrl *url, int version, CacheClient_t *Client) /* a_Dicache_unref() may free DicEntry */ _MSG("a_Dicache_close RefCount=%d\n", DicEntry->RefCount - 1); + _MSG("a_Dicache_close DIC_Close=%d State=%d\n", DIC_Close, DicEntry->State); + _MSG(" a_Dicache_close imgbuf=%p Decoder=%p DecoderData=%p\n", + DicEntry->v_imgbuf, DicEntry->Decoder, DicEntry->DecoderData); if (DicEntry->State < DIC_Close) { DicEntry->State = DIC_Close; @@ -410,24 +377,29 @@ static void *Dicache_image(int ImgType, const char *MimeType, void *Ptr, DicEntry = a_Dicache_get_entry(web->url, DIC_Last); if (!DicEntry) { - /* Let's create an entry for this image... */ + /* Create an entry for this image... */ DicEntry = Dicache_add_entry(web->url); - DicEntry->DecoderData = - (ImgType == DIC_Png) ? - a_Png_new(web->Image, DicEntry->url, DicEntry->version) : - (ImgType == DIC_Gif) ? - a_Gif_new(web->Image, DicEntry->url, DicEntry->version) : - (ImgType == DIC_Jpeg) ? - a_Jpeg_new(web->Image, DicEntry->url, DicEntry->version) : - NULL; + /* Attach a decoder */ + if (ImgType == DIC_Jpeg) { + DicEntry->Decoder = (CA_Callback_t)a_Jpeg_callback; + DicEntry->DecoderData = + a_Jpeg_new(web->Image, DicEntry->url, DicEntry->version); + } else if (ImgType == DIC_Gif) { + DicEntry->Decoder = (CA_Callback_t)a_Gif_callback; + DicEntry->DecoderData = + a_Gif_new(web->Image, DicEntry->url, DicEntry->version); + } else if (ImgType == DIC_Png) { + DicEntry->Decoder = (CA_Callback_t)a_Png_callback; + DicEntry->DecoderData = + a_Png_new(web->Image, DicEntry->url, DicEntry->version); + } } else { /* Repeated image */ a_Dicache_ref(DicEntry->url, DicEntry->version); } - DicEntry->Decoder = (ImgType == DIC_Png) ? (CA_Callback_t)a_Png_callback : - (ImgType == DIC_Gif) ? (CA_Callback_t)a_Gif_callback : - (ImgType == DIC_Jpeg) ? (CA_Callback_t)a_Jpeg_callback: - NULL; + /* Survive three cleanup passes (set to zero = old behaviour). */ + DicEntry->SurvCleanup = 3; + *Data = DicEntry->DecoderData; *Call = (CA_Callback_t) a_Dicache_callback; @@ -535,52 +507,42 @@ void a_Dicache_callback(int Op, CacheClient_t *Client) void a_Dicache_cleanup(void) { int i; - DICacheNode *node; DICacheEntry *entry; - _MSG("a_Dicache_cleanup\n"); - for (i = 0; i < dList_length(CachedIMGs); ++i) { - node = dList_nth_data(CachedIMGs, i); - /* iterate each entry of this node */ - for (entry = node->first; entry; entry = entry->next) { - if (entry->v_imgbuf && - a_Imgbuf_last_reference(entry->v_imgbuf)) { - /* free this unused entry */ - if (entry->next) { - Dicache_remove(node->url, entry->version); - } else { - Dicache_remove(node->url, entry->version); - --i; - break; - } - } + for (i = 0; (entry = dList_nth_data(CachedIMGs, i)); ++i) { + _MSG(" SurvCleanup = %d\n", entry->SurvCleanup); + if (entry->RefCount == 0 && + (!entry->v_imgbuf || a_Imgbuf_last_reference(entry->v_imgbuf))) { + if (--entry->SurvCleanup >= 0) + continue; /* keep the entry one more pass */ + + /* free this unused entry */ + Dicache_remove(entry->url, entry->version); + --i; /* adjust counter */ } } + MSG("a_Dicache_cleanup: length = %d\n", dList_length(CachedIMGs)); } /* ------------------------------------------------------------------------- */ /* * Deallocate memory used by dicache module - * (Call this one at exit time) + * (Call this one at exit time, with no cache clients queued) */ void a_Dicache_freeall(void) { - DICacheNode *node; DICacheEntry *entry; - /* Remove every dicache node and its entries */ - while ((node = dList_nth_data(CachedIMGs, 0))) { - while ((entry = node->first)) { - node->first = entry->next; - dFree(entry->cmap); - a_Bitvec_free(entry->BitVec); - a_Imgbuf_unref(entry->v_imgbuf); - dicache_size_total -= entry->TotalSize; - } - dList_remove_fast(CachedIMGs, node); - a_Url_free(node->url); - dFree(node); + /* Remove all the dicache entries */ + while ((entry = dList_nth_data(CachedIMGs, dList_length(CachedIMGs)-1))) { + dList_remove_fast(CachedIMGs, entry); + a_Url_free(entry->url); + dFree(entry->cmap); + a_Bitvec_free(entry->BitVec); + a_Imgbuf_unref(entry->v_imgbuf); + dicache_size_total -= entry->TotalSize; + dFree(entry); } dList_free(CachedIMGs); } diff --git a/src/dicache.h b/src/dicache.h index df8a8b89..7d5ef6ee 100644 --- a/src/dicache.h +++ b/src/dicache.h @@ -12,6 +12,9 @@ extern "C" { /* Symbolic name to request the last version of an image */ #define DIC_Last -1 +/* Flags: Last version, Valid entry */ +#define DIF_Last 1 +#define DIF_Valid 2 /* These will reflect the entry's "state" */ @@ -26,8 +29,10 @@ typedef enum { typedef struct DICacheEntry { DilloUrl *url; /* Image URL for this entry */ - uint_t width, height; /* As taken from image data */ DilloImgType type; /* Image type */ + uint_t width, height; /* As taken from image data */ + short Flags; /* See Flags */ + short SurvCleanup; /* Cleanup-pass survival for unused images */ uchar_t *cmap; /* Color map */ void *v_imgbuf; /* Void pointer to an Imgbuf object */ uint_t TotalSize; /* Amount of memory the image takes up */ @@ -38,11 +43,9 @@ typedef struct DICacheEntry { int version; /* Version number, used for different versions of the same URL image */ + uint_t DecodedSize; /* Size of already decoded data */ CA_Callback_t Decoder; /* Client function */ void *DecoderData; /* Client function data */ - uint_t DecodedSize; /* Size of already decoded data */ - - struct DICacheEntry *next; /* Link to the next "newer" version */ } DICacheEntry; @@ -61,7 +64,7 @@ void a_Dicache_callback(int Op, CacheClient_t *Client); void a_Dicache_set_parms(DilloUrl *url, int version, DilloImage *Image, uint_t width, uint_t height, DilloImgType type, double gamma); -void a_Dicache_set_cmap(DilloUrl *url, int version, DilloImage *Image, +void a_Dicache_set_cmap(DilloUrl *url, int version, int bg_color, const uchar_t *cmap, uint_t num_colors, int num_colors_max, int bg_index); void a_Dicache_new_scan(const DilloUrl *url, int version); diff --git a/src/dillo.cc b/src/dillo.cc index ee2f4da4..0a68cb59 100644 --- a/src/dillo.cc +++ b/src/dillo.cc @@ -56,7 +56,9 @@ #include "lout/debug.hh" #include "dw/fltkcore.hh" +#include "dw/widget.hh" #include "dw/textblock.hh" +#include "dw/table.hh" /* * Command line options structure @@ -377,10 +379,18 @@ static DilloUrl *makeStartUrl(char *str, bool local) */ int main(int argc, char **argv) { - DBG_OBJ_COLOR("#c0ff80", "dw::*"); - DBG_OBJ_COLOR("#c0c0ff", "dw::fltk::*"); - DBG_OBJ_COLOR("#ffa0a0", "dw::core::*"); - DBG_OBJ_COLOR("#ffe0a0", "dw::core::style::*"); + DBG_OBJ_COLOR ("dw::*", "#c0ff80"); + DBG_OBJ_COLOR ("dw::fltk::*", "#c0c0ff"); + DBG_OBJ_COLOR ("dw::core::*", "#ffa0a0"); + DBG_OBJ_COLOR ("dw::core::style::*", "#ffe0a0"); + + DBG_OBJ_COLOR ("dw::Image", "#80ffa0"); + DBG_OBJ_COLOR ("dw::Textblock", "#f0ff80"); + DBG_OBJ_COLOR ("dw::OutOfFlowMgr", "#d0ff80"); + DBG_OBJ_COLOR ("dw::AlignedTextblock", "#e0ff80"); + DBG_OBJ_COLOR ("dw::ListItem", "#b0ff80"); + DBG_OBJ_COLOR ("dw::TableCell", "#80ff80"); + DBG_OBJ_COLOR ("dw::Table", "#80ffc0"); uint_t opt_id; uint_t options_got = 0; @@ -475,6 +485,8 @@ int main(int argc, char **argv) a_UIcmd_init(); StyleEngine::init(); + dw::core::Widget::setAdjustMinWidth (prefs.adjust_min_width); + dw::Table::setAdjustTableMinWidth (prefs.adjust_table_min_width); dw::Textblock::setPenaltyHyphen (prefs.penalty_hyphen); dw::Textblock::setPenaltyHyphen2 (prefs.penalty_hyphen_2); dw::Textblock::setPenaltyEmDashLeft (prefs.penalty_em_dash_left); diff --git a/src/domain.c b/src/domain.c index ea5c4948..8bff39de 100644 --- a/src/domain.c +++ b/src/domain.c @@ -129,7 +129,7 @@ bool_t a_Domain_permit(const DilloUrl *source, const DilloUrl *dest) ret = source_host[0] == '\0' || !dStrAsciiCasecmp(URL_SCHEME(dest), "data"); if (ret == FALSE) - MSG("Domain: DENIED from %s to %s.\n", source_host, URL_STR(dest)); + MSG("Domain: DENIED %s -> %s.\n", source_host, URL_STR(dest)); return ret; } @@ -151,7 +151,7 @@ bool_t a_Domain_permit(const DilloUrl *source, const DilloUrl *dest) if (ret == FALSE) { const char *src = source_host[0] ? source_host : URL_STR(source); - MSG("Domain: DENIED from %s to %s.\n", src, dest_host); + MSG("Domain: DENIED %s -> %s.\n", src, dest_host); } return ret; } diff --git a/src/form.cc b/src/form.cc index 07c12815..9e7dc4f6 100644 --- a/src/form.cc +++ b/src/form.cc @@ -343,7 +343,7 @@ void Html_tag_open_form(DilloHtml *html, const char *tag, int tagsize) HT2TB(html)->addParbreak (9, html->wordStyle ()); if (html->InFlags & IN_FORM) { - BUG_MSG("nested forms\n"); + BUG_MSG("Nested <form>."); return; } html->InFlags |= IN_FORM; @@ -356,14 +356,14 @@ void Html_tag_open_form(DilloHtml *html, const char *tag, int tagsize) if (!dStrAsciiCasecmp(attrbuf, "post")) { method = DILLO_HTML_METHOD_POST; } else if (dStrAsciiCasecmp(attrbuf, "get")) { - BUG_MSG("Unknown form submission method \"%s\"\n", attrbuf); + BUG_MSG("<form> submission method unknown: '%s'.", attrbuf); } } if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "action"))) action = a_Html_url_new(html, attrbuf, NULL, 0); else { if (html->DocType != DT_HTML || html->DocTypeVersion <= 4.01f) - BUG_MSG("action attribute is required for <form>\n"); + BUG_MSG("<form> requires action attribute."); action = a_Url_dup(html->base_url); } content_type = DILLO_HTML_ENC_URLENCODED; @@ -417,7 +417,7 @@ static int Html_input_get_size(DilloHtml *html, const char *attrbuf) if (size < 1 || size > MAX_SIZE) { int badSize = size; size = (size < 1 ? 20 : MAX_SIZE); - BUG_MSG("input size=%d, using size=%d instead\n", badSize, size); + BUG_MSG("<input> size=%d, using size=%d instead.", badSize, size); } } return size; @@ -437,11 +437,11 @@ void Html_tag_open_input(DilloHtml *html, const char *tag, int tagsize) ResourceFactory *factory; if (html->InFlags & IN_SELECT) { - BUG_MSG("<input> element inside <select>\n"); + BUG_MSG("<input> inside <select>."); return; } if (html->InFlags & IN_BUTTON) { - BUG_MSG("<input> element inside <button>\n"); + BUG_MSG("<input> inside <button>."); return; } @@ -507,12 +507,12 @@ void Html_tag_open_input(DilloHtml *html, const char *tag, int tagsize) DilloHtmlForm *form = html->getCurrentForm(); if (form->method != DILLO_HTML_METHOD_POST) { valid = false; - BUG_MSG("Forms with file input MUST use HTTP POST method\n"); + BUG_MSG("<form> with file input MUST use HTTP POST method."); MSG("File input ignored in form not using HTTP POST method\n"); } else if (form->content_type != DILLO_HTML_ENC_MULTIPART) { valid = false; - BUG_MSG("Forms with file input MUST use multipart/form-data" - " encoding\n"); + BUG_MSG("<form> with file input MUST use multipart/form-data" + " encoding."); MSG("File input ignored in form not using multipart/form-data" " encoding\n"); } @@ -641,25 +641,25 @@ void Html_tag_content_textarea(DilloHtml *html, const char *tag, int tagsize) cols = strtol(attrbuf, NULL, 10); } else { if (html->DocType != DT_HTML || html->DocTypeVersion <= 4.01f) - BUG_MSG("cols attribute is required for <textarea>\n"); + BUG_MSG("<textarea> requires cols attribute."); cols = 20; } if (cols < 1 || cols > MAX_COLS) { int badCols = cols; cols = (cols < 1 ? 20 : MAX_COLS); - BUG_MSG("textarea cols=%d, using cols=%d instead\n", badCols, cols); + BUG_MSG("<textarea> cols=%d, using cols=%d instead.", badCols, cols); } if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "rows"))) { rows = strtol(attrbuf, NULL, 10); } else { if (html->DocType != DT_HTML || html->DocTypeVersion <= 4.01f) - BUG_MSG("rows attribute is required for <textarea>\n"); + BUG_MSG("<textarea> requires rows attribute."); rows = 10; } if (rows < 1 || rows > MAX_ROWS) { int badRows = rows; rows = (rows < 1 ? 2 : MAX_ROWS); - BUG_MSG("textarea rows=%d, using rows=%d instead\n", badRows, rows); + BUG_MSG("<textarea> rows=%d, using rows=%d instead.", badRows, rows); } name = NULL; if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "name"))) @@ -799,11 +799,11 @@ void Html_tag_close_select(DilloHtml *html) void Html_tag_open_optgroup(DilloHtml *html, const char *tag, int tagsize) { if (!(html->InFlags & IN_SELECT)) { - BUG_MSG("<optgroup> element outside <select>\n"); + BUG_MSG("<optgroup> outside <select>."); return; } if (html->InFlags & IN_OPTGROUP) { - BUG_MSG("nested <optgroup>\n"); + BUG_MSG("Nested <optgroup>."); return; } if (html->InFlags & IN_OPTION) { @@ -821,7 +821,7 @@ void Html_tag_open_optgroup(DilloHtml *html, const char *tag, int tagsize) bool enabled = (a_Html_get_attr(html, tag, tagsize, "disabled") == NULL); if (!label) { - BUG_MSG("label attribute is required for <optgroup>\n"); + BUG_MSG("<optgroup> requires label attribute."); label = strdup(""); } @@ -859,7 +859,7 @@ void Html_tag_close_optgroup(DilloHtml *html) void Html_tag_open_option(DilloHtml *html, const char *tag, int tagsize) { if (!(html->InFlags & IN_SELECT)) { - BUG_MSG("<option> element outside <select>\n"); + BUG_MSG("<option> outside <select>."); return; } if (html->InFlags & IN_OPTION) @@ -918,7 +918,7 @@ void Html_tag_open_button(DilloHtml *html, const char *tag, int tagsize) inp_type = DILLO_HTML_INPUT_BUTTON_SUBMIT; } else { inp_type = DILLO_HTML_INPUT_UNKNOWN; - BUG_MSG("Unknown button type: \"%s\"\n", type); + BUG_MSG("<button> type unknown: '%s'.", type); } if (inp_type != DILLO_HTML_INPUT_UNKNOWN) { @@ -945,9 +945,7 @@ void Html_tag_open_button(DilloHtml *html, const char *tag, int tagsize) embed = new Embed(resource); // a_Dw_button_set_sensitive (DW_BUTTON (button), FALSE); - HT2TB(html)->addParbreak (5, html->wordStyle ()); HT2TB(html)->addWidget (embed, html->backgroundStyle ()); - HT2TB(html)->addParbreak (5, html->wordStyle ()); S_TOP(html)->textblock = html->dw = page; @@ -103,9 +103,7 @@ typedef struct { size_t ColorMap_ofs; uint_t ColorResolution; uint_t NumColors; -#if 0 - int Background; -#endif + int Background; uint_t spill_line_index; #if 0 uint_t AspectRatio; /* AspectRatio (not used) */ @@ -165,9 +163,7 @@ void *a_Gif_new(DilloImage *Image, DilloUrl *url, int version) gif->state = 0; gif->Start_Ofs = 0; gif->linebuf = NULL; -#if 0 - gif->Background = -1; -#endif + gif->Background = Image->bg_color; gif->transparent = -1; gif->num_spill_lines_max = 0; gif->spill_lines = NULL; @@ -222,7 +218,7 @@ static void Gif_write(DilloGif *gif, void *Buf, uint_t BufSize) int bufsize, bytes_consumed; /* Sanity checks */ - if (!Buf || !gif->Image || BufSize == 0) + if (!Buf || BufSize == 0) return; buf = ((uchar_t *) Buf) + gif->Start_Ofs; @@ -804,8 +800,8 @@ static size_t Gif_do_img_desc(DilloGif *gif, void *Buf, if (bsize < 10) return 0; - gif->Width = LM_to_uint(buf[4], buf[5]); - gif->Height = LM_to_uint(buf[6], buf[7]); + gif->Width = LM_to_uint(buf[4], buf[5]); + gif->Height = LM_to_uint(buf[6], buf[7]); /* check max image size */ if (gif->Width <= 0 || gif->Height <= 0 || @@ -820,6 +816,7 @@ static size_t Gif_do_img_desc(DilloGif *gif, void *Buf, a_Dicache_set_parms(gif->url, gif->version, gif->Image, gif->Width, gif->Height, DILLO_IMG_TYPE_INDEXED, 1 / 2.2); + gif->Image = NULL; /* safeguard: hereafter it may be freed by its owner */ Flags = buf[8]; @@ -850,8 +847,8 @@ static size_t Gif_do_img_desc(DilloGif *gif, void *Buf, gif->spill_line_index = 0; gif->linebuf = dMalloc(gif->Width); gif->state = 3; /*Process the lzw data next */ - if (gif->Image && gif->ColorMap_ofs) { - a_Dicache_set_cmap(gif->url, gif->version, gif->Image, + if (gif->ColorMap_ofs) { + a_Dicache_set_cmap(gif->url, gif->version, gif->Background, (uchar_t *) Buf + gif->ColorMap_ofs, gif->NumColors, 256, gif->transparent); } diff --git a/src/html.cc b/src/html.cc index a8c70879..a1452858 100644 --- a/src/html.cc +++ b/src/html.cc @@ -26,6 +26,7 @@ #include "msg.h" #include "binaryconst.h" #include "colors.h" +#include "html_charrefs.h" #include "utf8.hh" #include "misc.h" @@ -132,9 +133,11 @@ void DilloHtml::bugMessage(const char *format, ... ) { va_list argp; + if (bw->num_page_bugs) + dStr_append_c(bw->page_bugs, '\n'); dStr_sprintfa(bw->page_bugs, "HTML warning: line %d, ", - getCurTagLineNumber()); + getCurrLineNumber()); va_start(argp, format); dStr_vsprintfa(bw->page_bugs, format, argp); va_end(argp); @@ -158,15 +161,15 @@ DilloUrl *a_Html_url_new(DilloHtml *html, const char *suffix = (n_ic) > 1 ? "s" : ""; n_ic_spc = URL_ILLEGAL_CHARS_SPC(url); if (n_ic == n_ic_spc) { - BUG_MSG("URL has %d illegal space%s\n", n_ic, suffix); + BUG_MSG("URL has %d illegal space%s ('%s').", n_ic, suffix, url_str); } else if (n_ic_spc == 0) { - BUG_MSG("URL has %d illegal character%s in {00-1F, 7F} range\n", - n_ic, suffix); + BUG_MSG("URL has %d illegal byte%s in {00-1F, 7F-FF} range ('%s').", + n_ic, suffix, url_str); } else { - BUG_MSG("URL has %d illegal character%s: " - "%d space%s, and %d in {00-1F, 7F} range\n", + BUG_MSG("URL has %d illegal byte%s: " + "%d space%s and %d in {00-1F, 7F-FF} range ('%s').", n_ic, suffix, - n_ic_spc, n_ic_spc > 1 ? "s" : "", n_ic-n_ic_spc); + n_ic_spc, n_ic_spc > 1 ? "s" : "", n_ic-n_ic_spc, url_str); } } return url; @@ -290,7 +293,7 @@ void a_Html_tag_set_align_attr(DilloHtml *html, const char *tag, int tagsize) TextAlignType textAlignType = TEXT_ALIGN_LEFT; if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) - BUG_MSG("The align attribute is obsolete in HTML5.\n"); + BUG_MSG("The align attribute is obsolete in HTML5."); if (dStrAsciiCasecmp (align, "left") == 0) textAlignType = TEXT_ALIGN_LEFT; @@ -334,7 +337,7 @@ bool a_Html_tag_set_valign_attr(DilloHtml *html, const char *tag, int tagsize) if ((attr = a_Html_get_attr(html, tag, tagsize, "valign"))) { if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) - BUG_MSG("The valign attribute is obsolete in HTML5.\n"); + BUG_MSG("The valign attribute is obsolete in HTML5."); if (dStrAsciiCasecmp (attr, "top") == 0) valign = VALIGN_TOP; @@ -356,15 +359,24 @@ bool a_Html_tag_set_valign_attr(DilloHtml *html, const char *tag, int tagsize) /* * Create and add a new Textblock to the current Textblock */ -static void Html_add_textblock(DilloHtml *html, int space) +static void Html_add_textblock(DilloHtml *html, bool addBreaks, int breakSpace) { Textblock *textblock = new Textblock (prefs.limit_text_width); - HT2TB(html)->addParbreak (space, html->wordStyle ()); - HT2TB(html)->addWidget (textblock, html->style ()); - HT2TB(html)->addParbreak (space, html->wordStyle ()); + if (addBreaks) + HT2TB(html)->addParbreak (breakSpace, html->wordStyle ()); + HT2TB(html)->addWidget (textblock, html->style ()); /* Works also for floats + etc. */ + if (addBreaks) + HT2TB(html)->addParbreak (breakSpace, html->wordStyle ()); S_TOP(html)->textblock = html->dw = textblock; - S_TOP(html)->hand_over_break = true; + if (addBreaks) + S_TOP(html)->hand_over_break = true; +} + +static bool Html_will_textblock_be_out_of_flow(DilloHtml *html) +{ + return HT2TB(html)->isStyleOutOfFlow (html->style ()); } /* @@ -397,9 +409,8 @@ DilloHtml::DilloHtml(BrowserWindow *p_bw, const DilloUrl *url, stop_parser = false; - CurrTagOfs = 0; - OldTagOfs = 0; - OldTagLine = 1; + CurrOfs = OldOfs = 0; + OldLine = 1; DocType = DT_NONE; /* assume Tag Soup 0.0! :-) */ DocTypeVersion = 0.0f; @@ -539,10 +550,10 @@ void DilloHtml::write(char *Buf, int BufSize, int Eof) } /* - * Return the line number of the tag being processed by the parser. + * Return the line number of the tag/word being processed by the parser. * Also update the offsets. */ -int DilloHtml::getCurTagLineNumber() +int DilloHtml::getCurrLineNumber() { int i, ofs, line; const char *p = Start_Buf; @@ -551,13 +562,13 @@ int DilloHtml::getCurTagLineNumber() /* Disable line counting for META hack. Buffers differ. */ dReturn_val_if((InFlags & IN_META_HACK), -1); - ofs = CurrTagOfs; - line = OldTagLine; - for (i = OldTagOfs; i < ofs; ++i) + ofs = CurrOfs; + line = OldLine; + for (i = OldOfs; i < ofs; ++i) if (p[i] == '\n' || (p[i] == '\r' && p[i+1] != '\n')) ++line; - OldTagOfs = CurrTagOfs; - OldTagLine = line; + OldOfs = CurrOfs; + OldLine = line; return line; } @@ -787,113 +798,16 @@ void a_Html_stash_init(DilloHtml *html) dStr_truncate(html->Stash, 0); } -/* Entities list from the HTML 4.01 DTD */ -typedef struct { - const char *entity; - int isocode; -} Ent_t; - -#define NumEnt 252 -static const Ent_t Entities[NumEnt] = { - {"AElig",0306}, {"Aacute",0301}, {"Acirc",0302}, {"Agrave",0300}, - {"Alpha",01621},{"Aring",0305}, {"Atilde",0303}, {"Auml",0304}, - {"Beta",01622}, {"Ccedil",0307}, {"Chi",01647}, {"Dagger",020041}, - {"Delta",01624},{"ETH",0320}, {"Eacute",0311}, {"Ecirc",0312}, - {"Egrave",0310},{"Epsilon",01625},{"Eta",01627}, {"Euml",0313}, - {"Gamma",01623},{"Iacute",0315}, {"Icirc",0316}, {"Igrave",0314}, - {"Iota",01631}, {"Iuml",0317}, {"Kappa",01632}, {"Lambda",01633}, - {"Mu",01634}, {"Ntilde",0321}, {"Nu",01635}, {"OElig",0522}, - {"Oacute",0323},{"Ocirc",0324}, {"Ograve",0322}, {"Omega",01651}, - {"Omicron",01637},{"Oslash",0330},{"Otilde",0325},{"Ouml",0326}, - {"Phi",01646}, {"Pi",01640}, {"Prime",020063},{"Psi",01650}, - {"Rho",01641}, {"Scaron",0540}, {"Sigma",01643}, {"THORN",0336}, - {"Tau",01644}, {"Theta",01630}, {"Uacute",0332}, {"Ucirc",0333}, - {"Ugrave",0331},{"Upsilon",01645},{"Uuml",0334}, {"Xi",01636}, - {"Yacute",0335},{"Yuml",0570}, {"Zeta",01626}, {"aacute",0341}, - {"acirc",0342}, {"acute",0264}, {"aelig",0346}, {"agrave",0340}, - {"alefsym",020465},{"alpha",01661},{"amp",38}, {"and",021047}, - {"ang",021040}, {"aring",0345}, {"asymp",021110},{"atilde",0343}, - {"auml",0344}, {"bdquo",020036},{"beta",01662}, {"brvbar",0246}, - {"bull",020042},{"cap",021051}, {"ccedil",0347}, {"cedil",0270}, - {"cent",0242}, {"chi",01707}, {"circ",01306}, {"clubs",023143}, - {"cong",021105},{"copy",0251}, {"crarr",020665},{"cup",021052}, - {"curren",0244},{"dArr",020723}, {"dagger",020040},{"darr",020623}, - {"deg",0260}, {"delta",01664}, {"diams",023146},{"divide",0367}, - {"eacute",0351},{"ecirc",0352}, {"egrave",0350}, {"empty",021005}, - {"emsp",020003},{"ensp",020002}, {"epsilon",01665},{"equiv",021141}, - {"eta",01667}, {"eth",0360}, {"euml",0353}, {"euro",020254}, - {"exist",021003},{"fnof",0622}, {"forall",021000},{"frac12",0275}, - {"frac14",0274},{"frac34",0276}, {"frasl",020104},{"gamma",01663}, - {"ge",021145}, {"gt",62}, {"hArr",020724}, {"harr",020624}, - {"hearts",023145},{"hellip",020046},{"iacute",0355},{"icirc",0356}, - {"iexcl",0241}, {"igrave",0354}, {"image",020421},{"infin",021036}, - {"int",021053}, {"iota",01671}, {"iquest",0277}, {"isin",021010}, - {"iuml",0357}, {"kappa",01672}, {"lArr",020720}, {"lambda",01673}, - {"lang",021451},{"laquo",0253}, {"larr",020620}, {"lceil",021410}, - {"ldquo",020034},{"le",021144}, {"lfloor",021412},{"lowast",021027}, - {"loz",022712}, {"lrm",020016}, {"lsaquo",020071},{"lsquo",020030}, - {"lt",60}, {"macr",0257}, {"mdash",020024},{"micro",0265}, - {"middot",0267},{"minus",021022},{"mu",01674}, {"nabla",021007}, - {"nbsp",0240}, {"ndash",020023},{"ne",021140}, {"ni",021013}, - {"not",0254}, {"notin",021011},{"nsub",021204}, {"ntilde",0361}, - {"nu",01675}, {"oacute",0363}, {"ocirc",0364}, {"oelig",0523}, - {"ograve",0362},{"oline",020076},{"omega",01711}, {"omicron",01677}, - {"oplus",021225},{"or",021050}, {"ordf",0252}, {"ordm",0272}, - {"oslash",0370},{"otilde",0365}, {"otimes",021227},{"ouml",0366}, - {"para",0266}, {"part",021002}, {"permil",020060},{"perp",021245}, - {"phi",01706}, {"pi",01700}, {"piv",01726}, {"plusmn",0261}, - {"pound",0243}, {"prime",020062},{"prod",021017}, {"prop",021035}, - {"psi",01710}, {"quot",34}, {"rArr",020722}, {"radic",021032}, - {"rang",021452},{"raquo",0273}, {"rarr",020622}, {"rceil",021411}, - {"rdquo",020035},{"real",020434},{"reg",0256}, {"rfloor",021413}, - {"rho",01701}, {"rlm",020017}, {"rsaquo",020072},{"rsquo",020031}, - {"sbquo",020032},{"scaron",0541},{"sdot",021305}, {"sect",0247}, - {"shy",0255}, {"sigma",01703}, {"sigmaf",01702},{"sim",021074}, - {"spades",023140},{"sub",021202},{"sube",021206}, {"sum",021021}, - {"sup",021203}, {"sup1",0271}, {"sup2",0262}, {"sup3",0263}, - {"supe",021207},{"szlig",0337}, {"tau",01704}, {"there4",021064}, - {"theta",01670},{"thetasym",01721},{"thinsp",020011},{"thorn",0376}, - {"tilde",01334},{"times",0327}, {"trade",020442},{"uArr",020721}, - {"uacute",0372},{"uarr",020621}, {"ucirc",0373}, {"ugrave",0371}, - {"uml",0250}, {"upsih",01722}, {"upsilon",01705},{"uuml",0374}, - {"weierp",020430},{"xi",01676}, {"yacute",0375}, {"yen",0245}, - {"yuml",0377}, {"zeta",01666}, {"zwj",020015}, {"zwnj",020014} -}; - - -/* - * Comparison function for binary search - */ -static int Html_entity_comp(const void *a, const void *b) -{ - return strcmp(((Ent_t *)a)->entity, ((Ent_t *)b)->entity); -} - -/* - * Binary search of 'key' in entity list - */ -static int Html_entity_search(char *key) -{ - Ent_t *res, EntKey; - - EntKey.entity = key; - res = (Ent_t*) bsearch(&EntKey, Entities, NumEnt, - sizeof(Ent_t), Html_entity_comp); - if (res) - return (res - Entities); - return -1; -} - /* * This is M$ non-standard "smart quotes" (w1252). Now even deprecated by them! * * SGML for HTML4.01 defines c >= 128 and c <= 159 as UNUSED. - * TODO: Probably I should remove this hack, and add a HTML warning. --Jcid + * TODO: Probably I should remove this hack. --Jcid */ -static int Html_ms_stupid_quotes_2ucs(int isocode) +static int Html_ms_stupid_quotes_2ucs(int codepoint) { int ret; - switch (isocode) { + switch (codepoint) { case 145: case 146: ret = '\''; break; case 147: @@ -901,130 +815,233 @@ static int Html_ms_stupid_quotes_2ucs(int isocode) case 149: ret = 176; break; case 150: case 151: ret = '-'; break; - default: ret = isocode; break; + default: ret = codepoint; break; } return ret; } /* - * Given an entity, return the UCS character code. - * Returns a negative value (error code) if not a valid entity. - * - * The first character *token is assumed to be == '&' - * - * For valid entities, *entsize is set to the length of the parsed entity. + * Parse a numeric character reference (e.g., "/" or "/"). + * The "&#" has already been consumed. */ -static int Html_parse_entity(DilloHtml *html, const char *token, - int toksize, int *entsize) +static const char *Html_parse_numeric_charref(DilloHtml *html, char *tok, + bool_t is_attr, int *entsize) { - int isocode, i; - char *tok, *s, c; + static char buf[5]; + char *s = tok; + int n, codepoint = -1; - token++; - tok = s = toksize ? dStrndup(token, (uint_t)toksize) : dStrdup(token); - - isocode = -1; - - if (*s == '#') { - /* numeric character reference */ - errno = 0; - if (*++s == 'x' || *s == 'X') { - if (isxdigit(*++s)) { - /* strtol with base 16 accepts leading "0x" - we don't */ - if (*s == '0' && s[1] == 'x') { - s++; - isocode = 0; - } else { - isocode = strtol(s, &s, 16); - } + errno = 0; + + if (*s == 'x' || *s == 'X') { + if (isxdigit(*++s)) { + /* strtol with base 16 accepts leading "0x" - we don't */ + if (*s == '0' && s[1] == 'x') { + s++; + codepoint = 0; + } else { + codepoint = strtol(s, &s, 16); } - } else if (isdigit(*s)) { - isocode = strtol(s, &s, 10); } + } else if (isdigit(*s)) { + codepoint = strtol(s, &s, 10); + } + if (errno) + codepoint = -1; - if (!isocode || errno || isocode > 0xffff) { - /* this catches null bytes, errors and codes >= 0xFFFF */ - BUG_MSG("numeric character reference \"%s\" out of range\n", tok); - isocode = -2; + if (*s == ';') + s++; + else { + if (prefs.show_extra_warnings && (html->DocType == DT_XHTML || + (html->DocType == DT_HTML && html->DocTypeVersion <= 4.01f))) { + char c = *s; + *s = '\0'; + BUG_MSG("Character reference '&#%s' lacks ';'.", tok); + *s = c; } - - if (isocode != -1) { - if (*s == ';') - s++; - else if (prefs.show_extra_warnings) - BUG_MSG("numeric character reference without trailing ';'\n"); + /* Don't require ';' for old HTML, except that our current heuristic + * is to require it in attributes to avoid cases like "©=1" found + * in URLs. + */ + if (is_attr || html->DocType == DT_XHTML || + (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)) { + return NULL; } - } else if (isalpha(*s)) { - /* character entity reference */ - while (*++s && (isalnum(*s) || strchr(":_.-", *s))) ; - c = *s; - *s = 0; + } + if ((codepoint < 0x20 && codepoint != '\t' && codepoint != '\n' && + codepoint != '\f') || + (codepoint >= 0x7f && codepoint <= 0x9f) || + (codepoint >= 0xd800 && codepoint <= 0xdfff) || codepoint > 0x10ffff || + ((codepoint & 0xfffe) == 0xfffe) || + (!(html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) && + codepoint > 0xffff)) { + /* this catches null bytes, errors, codes out of range, disallowed + * control chars, permanently undefined chars, and surrogates. + */ + char c = *s; + *s = '\0'; + BUG_MSG("Numeric character reference '&#%s' is not valid.", tok); + *s = c; - if ((i = Html_entity_search(tok)) >= 0) { - isocode = Entities[i].isocode; + codepoint = (codepoint >= 145 && codepoint <= 151) ? + Html_ms_stupid_quotes_2ucs(codepoint) : -1; + } + if (codepoint != -1) { + if (codepoint >= 128) { + n = a_Utf8_encode(codepoint, buf); } else { - if (html->DocType == DT_XHTML && !strcmp(tok, "apos")) { - isocode = 0x27; - } else { - if ((html->DocType == DT_HTML && html->DocTypeVersion == 4.01f) || - html->DocType == DT_XHTML) - BUG_MSG("undefined character entity '%s'\n", tok); - isocode = -3; - } + n = 1; + buf[0] = (char) codepoint; + } + assert(n < 5); + buf[n] = '\0'; + *entsize = s-tok+2; + return buf; + } else { + return NULL; + } +} + +/* + * Comparison function for binary search + */ +static int Html_charref_comp(const void *a, const void *b) +{ + return strcmp(((Charref_t *)a)->ref, ((Charref_t *)b)->ref); +} + +/* + * Binary search of 'key' in charref list + */ +static Charref_t *Html_charref_search(char *key) +{ + Charref_t RefKey; + + RefKey.ref = key; + return (Charref_t*) bsearch(&RefKey, Charrefs, NumRef, + sizeof(Charref_t), Html_charref_comp); +} + +/* + * Parse a named character reference (e.g., "&" or "…"). + * The "&" has already been consumed. + */ +static const char *Html_parse_named_charref(DilloHtml *html, char *tok, + bool_t is_attr, int *entsize) +{ + Charref_t *p; + char c; + char *s = tok; + const char *ret = NULL; + + while (*++s && (isalnum(*s) || strchr(":_.-", *s))) ; + c = *s; + *s = '\0'; + if (c != ';') { + if (prefs.show_extra_warnings && (html->DocType == DT_XHTML || + (html->DocType == DT_HTML && html->DocTypeVersion <= 4.01f))) + BUG_MSG("Character reference '&%s' lacks ';'.", tok); + + /* Don't require ';' for old HTML, except that our current heuristic + * is to require it in attributes to avoid cases like "©=1" found + * in URLs. + */ + if (is_attr || html->DocType == DT_XHTML || + (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)) { + return ret; } - if (c == ';') - s++; - else if (prefs.show_extra_warnings) - BUG_MSG("character entity reference without trailing ';'\n"); } + if ((p = Html_charref_search(tok))) { + ret = (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) ? + p->html5_str : p->html4_str; + } + + if (!ret && html->DocType == DT_XHTML && !strcmp(tok, "apos")) + ret = "'"; + + *s = c; + if (c == ';') + s++; + + if (!ret) { + c = *s; + *s = '\0'; + BUG_MSG("Undefined character reference '&%s'.", tok); + *s = c; + } *entsize = s-tok+1; - dFree(tok); + return ret; +} + +/* + * Given an entity, return the corresponding string. + * Returns NULL if not a valid entity. + * + * The first character *token is assumed to be == '&' + * + * For valid entities, *entsize is set to the length of the parsed entity. + */ +static const char *Html_parse_entity(DilloHtml *html, const char *token, + int toksize, int *entsize, bool_t is_attr) +{ + const char *ret = NULL; + char *tok; - if (isocode >= 145 && isocode <= 151) { - /* TODO: remove this hack. */ - isocode = Html_ms_stupid_quotes_2ucs(isocode); - } else if (isocode == -1 && prefs.show_extra_warnings) - BUG_MSG("literal '&'\n"); + token++; + tok = dStrndup(token, (uint_t)toksize); + + if (*tok == '#') { + ret = Html_parse_numeric_charref(html, tok+1, is_attr, entsize); + } else if (isalpha(*tok)) { + ret = Html_parse_named_charref(html, tok, is_attr, entsize); + } else if (prefs.show_extra_warnings && + (!(html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f))) { + // HTML5 doesn't mind literal '&'s. + BUG_MSG("Literal '&'."); + } + dFree(tok); - return isocode; + return ret; } /* - * Convert all the entities in a token to utf8 encoding. Takes - * a token and its length, and returns a newly allocated string. + * Parse all the entities in a token. Takes the token and its length, and + * returns a newly allocated string. */ char *a_Html_parse_entities(DilloHtml *html, const char *token, int toksize) { const char *esc_set = "&"; - char *new_str, buf[4]; - int i, j, k, n, s, isocode, entsize; - - new_str = dStrndup(token, toksize); - s = strcspn(new_str, esc_set); - if (new_str[s] == 0) - return new_str; - - for (i = j = s; i < toksize; i++) { - if (token[i] == '&' && - (isocode = Html_parse_entity(html, token+i, - toksize-i, &entsize)) >= 0) { - if (isocode >= 128) { - /* multibyte encoding */ - n = a_Utf8_encode(isocode, buf); - for (k = 0; k < n; ++k) - new_str[j++] = buf[k]; + int i, s, entsize; + char *str; + + s = strcspn(token, esc_set); + if (s >= toksize) { + /* no ampersands */ + str = dStrndup(token, toksize); + } else { + Dstr *ds = dStr_sized_new(toksize); + + dStr_append_l(ds, token, s); + + for (i = s; i < toksize; i++) { + const char *entstr; + const bool_t is_attr = FALSE; + + if (token[i] == '&' && + (entstr = Html_parse_entity(html, token+i, toksize-i, &entsize, + is_attr))) { + dStr_append(ds, entstr); + i += entsize-1; } else { - new_str[j++] = (char) isocode; + dStr_append_c(ds, token[i]); } - i += entsize-1; - } else { - new_str[j++] = token[i]; } + str = ds->str; + dStr_free(ds, 0); } - new_str[j] = '\0'; - return new_str; + return str; } /* @@ -1095,7 +1112,7 @@ static void Html_process_space(DilloHtml *html, const char *space, break; case '\t': if (prefs.show_extra_warnings) - BUG_MSG("TAB character inside <PRE>\n"); + BUG_MSG("TAB character inside <pre>."); offset = TAB_SIZE - html->pre_column % TAB_SIZE; spaceCnt += offset; html->pre_column += offset; @@ -1314,7 +1331,7 @@ static void Html_tag_cleanup_to_idx(DilloHtml *html, int idx) int toptag_idx = S_TOP(html)->tag_idx; TagInfo toptag = Tags[toptag_idx]; if (s_sz > idx + 1 && toptag.EndTag != 'O') - BUG_MSG(" - forcing close of open tag: <%s>\n", toptag.name); + BUG_MSG(" - forcing close of open tag: <%s>.", toptag.name); _MSG("Close: %*s%s\n", size," ", toptag.name); if (toptag.close) toptag.close(html); @@ -1372,10 +1389,10 @@ static void Html_tag_cleanup_at_close(DilloHtml *html, int new_idx) if (matched) { Html_tag_cleanup_to_idx(html, stack_idx); } else if (expected) { - BUG_MSG("unexpected closing tag: </%s> -- expected </%s>.\n", + BUG_MSG("Unexpected closing tag: </%s> -- expected </%s>.", new_tag.name, Tags[tag_idx].name); } else { - BUG_MSG("unexpected closing tag: </%s>.\n", new_tag.name); + BUG_MSG("Unexpected closing tag: </%s>.", new_tag.name); } } @@ -1411,7 +1428,7 @@ static void Html_tag_cleanup_nested_inputs(DilloHtml *html, int new_idx) } if (matched) { - BUG_MSG("attempt to nest <%s> element inside <%s> -- closing <%s>\n", + BUG_MSG("Attempt to nest <%s> element inside <%s> -- closing <%s>.", Tags[new_idx].name, Tags[u_idx].name, Tags[u_idx].name); Html_tag_cleanup_to_idx(html, stack_idx); } else { @@ -1481,7 +1498,7 @@ CssLength a_Html_parse_length (DilloHtml *html, const char *attr) else { /* allow only whitespaces */ if (*end && !isspace (*end)) { - BUG_MSG("Garbage after length: %s\n", attr); + BUG_MSG("Garbage after length: '%s'.", attr); l = CSS_CREATE_LENGTH(0.0, CSS_LENGTH_TYPE_AUTO); } } @@ -1501,7 +1518,7 @@ int32_t a_Html_color_parse(DilloHtml *html, const char *str, int32_t color = a_Color_parse(str, default_color, &err); if (err) { - BUG_MSG("color \"%s\" is not in \"#RRGGBB\" format\n", str); + BUG_MSG("Color '%s' is not in \"#RRGGBB\" format.", str); } return color; } @@ -1518,8 +1535,8 @@ static int bool valid = *val && !strchr(val, ' '); if (!valid) { - BUG_MSG("'%s' value must not be empty and must not contain spaces.\n", - attrname); + BUG_MSG("'%s' value \"%s\" must not be empty and must not contain " + "spaces.", attrname, val); } return valid ? 1 : 0; } else { @@ -1530,8 +1547,8 @@ static int break; if (val[i] || !(isascii(val[0]) && isalpha(val[0]))) - BUG_MSG("'%s' value \"%s\" is not of the form " - "[A-Za-z][A-Za-z0-9:_.-]*\n", attrname, val); + BUG_MSG("%s attribute value \"%s\" is not of the form " + "'[A-Za-z][A-Za-z0-9:_.-]*'.", attrname, val); return !(val[i]); } @@ -1559,7 +1576,6 @@ static int static void Html_parse_doctype(DilloHtml *html, const char *tag, int tagsize) { static const char HTML_SGML_sig [] = "<!DOCTYPE HTML PUBLIC "; - static const char HTML5_sig [] = "<!DOCTYPE html>"; static const char HTML20 [] = "-//IETF//DTD HTML"; static const char HTML32 [] = "-//W3C//DTD HTML 3.2"; static const char HTML40 [] = "-//W3C//DTD HTML 4.0"; @@ -1596,7 +1612,7 @@ static void Html_parse_doctype(DilloHtml *html, const char *tag, int tagsize) _MSG("New: {%s}\n", ntag); if (html->DocType != DT_NONE) - BUG_MSG("Multiple DOCTYPE declarations.\n"); + BUG_MSG("Multiple DOCTYPE declarations."); /* The default DT_NONE type is TagSoup */ if (i > strlen(HTML_SGML_sig) && // avoid out of bounds reads! @@ -1624,13 +1640,14 @@ static void Html_parse_doctype(DilloHtml *html, const char *tag, int tagsize) html->DocType = DT_HTML; html->DocTypeVersion = 2.0f; } - } else if (!dStrAsciiCasecmp(ntag, HTML5_sig)) { + } else if (!dStrAsciiCasecmp(ntag, "<!DOCTYPE html>") || + !dStrAsciiCasecmp(ntag, "<!DOCTYPE html >")) { html->DocType = DT_HTML; html->DocTypeVersion = 5.0f; } if (html->DocType == DT_NONE) { html->DocType = DT_UNRECOGNIZED; - BUG_MSG("DOCTYPE not recognized:\n%s.\n", ntag); + BUG_MSG("DOCTYPE not recognized: ('%s').", ntag); } dFree(ntag); } @@ -1649,7 +1666,7 @@ static void Html_tag_open_html(DilloHtml *html, const char *tag, int tagsize) ++html->Num_HTML; if (html->Num_HTML > 1) { - BUG_MSG("HTML element was already open\n"); + BUG_MSG("<html> was already open."); html->ReqTagClose = true; } } @@ -1668,7 +1685,7 @@ static void Html_tag_close_html(DilloHtml *html) static void Html_tag_open_head(DilloHtml *html, const char *tag, int tagsize) { if (html->InFlags & IN_BODY) { - BUG_MSG("HEAD element must go before the BODY section\n"); + BUG_MSG("<head> must go before the BODY section."); html->ReqTagClose = true; return; } @@ -1676,10 +1693,10 @@ static void Html_tag_open_head(DilloHtml *html, const char *tag, int tagsize) if (html->Num_HEAD < UCHAR_MAX) ++html->Num_HEAD; if (html->InFlags & IN_HEAD) { - BUG_MSG("HEAD element was already open\n"); + BUG_MSG("<head> was already open."); html->ReqTagClose = true; } else if (html->Num_HEAD > 1) { - BUG_MSG("HEAD section already finished -- ignoring\n"); + BUG_MSG("<head> already finished -- ignoring."); html->ReqTagClose = true; } else { html->InFlags |= IN_HEAD; @@ -1696,7 +1713,7 @@ static void Html_tag_close_head(DilloHtml *html) if (html->Num_HEAD == 1) { /* match for the well formed start of HEAD section */ if (html->Num_TITLE == 0) - BUG_MSG("HEAD section lacks the TITLE element\n"); + BUG_MSG("<head> lacks <title>."); html->InFlags &= ~IN_HEAD; @@ -1726,9 +1743,9 @@ static void Html_tag_open_title(DilloHtml *html, const char *tag, int tagsize) if (html->Num_TITLE < UCHAR_MAX) ++html->Num_TITLE; if (html->Num_TITLE > 1) - BUG_MSG("A redundant TITLE element was found\n"); + BUG_MSG("Redundant <title>."); } else { - BUG_MSG("TITLE element must be inside the HEAD section -- ignoring\n"); + BUG_MSG("<title> must be inside <head> -- ignoring."); } } @@ -1776,7 +1793,7 @@ static void Html_tag_open_style(DilloHtml *html, const char *tag, int tagsize) if (!(attrbuf = a_Html_get_attr(html, tag, tagsize, "type"))) { if (html->DocType != DT_HTML || html->DocTypeVersion <= 4.01f) - BUG_MSG("type attribute is required for <style>\n"); + BUG_MSG("<style> requires type attribute."); } else if (dStrAsciiCasecmp(attrbuf, "text/css")) { html->loadCssFromStash = false; } @@ -1800,8 +1817,8 @@ static void Html_tag_open_style(DilloHtml *html, const char *tag, int tagsize) static void Html_tag_close_style(DilloHtml *html) { if (prefs.parse_embedded_css && html->loadCssFromStash) - html->styleEngine->parse(html, html->base_url, html->Stash->str, html->Stash->len, - CSS_ORIGIN_AUTHOR); + html->styleEngine->parse(html, html->base_url, html->Stash->str, + html->Stash->len, CSS_ORIGIN_AUTHOR); } /* @@ -1825,21 +1842,21 @@ static void Html_tag_open_body(DilloHtml *html, const char *tag, int tagsize) ++html->Num_BODY; if (html->Num_BODY > 1) { - BUG_MSG("BODY element was already open\n"); + BUG_MSG("<body> was already open."); html->ReqTagClose = true; return; } if (html->InFlags & IN_HEAD) { /* if we're here, it's bad XHTML, no need to recover */ - BUG_MSG("unclosed HEAD element\n"); + BUG_MSG("Unclosed <head>."); } if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "bgcolor"))) { color = a_Html_color_parse(html, attrbuf, -1); if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) - BUG_MSG("<body> bgcolor attribute is obsolete.\n"); + BUG_MSG("<body> bgcolor attribute is obsolete."); if (color != -1) html->styleEngine->setNonCssHint (CSS_PROPERTY_BACKGROUND_COLOR, @@ -1850,7 +1867,7 @@ static void Html_tag_open_body(DilloHtml *html, const char *tag, int tagsize) color = a_Html_color_parse(html, attrbuf, -1); if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) - BUG_MSG("<body> text attribute is obsolete.\n"); + BUG_MSG("<body> text attribute is obsolete."); if (color != -1) html->styleEngine->setNonCssHint (CSS_PROPERTY_COLOR, @@ -1862,13 +1879,13 @@ static void Html_tag_open_body(DilloHtml *html, const char *tag, int tagsize) if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "link"))) { html->non_css_link_color = a_Html_color_parse(html, attrbuf, -1); if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) - BUG_MSG("<body> link attribute is obsolete.\n"); + BUG_MSG("<body> link attribute is obsolete."); } if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "vlink"))) { html->non_css_visited_color = a_Html_color_parse(html, attrbuf, -1); if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) - BUG_MSG("<body> vlink attribute is obsolete.\n"); + BUG_MSG("<body> vlink attribute is obsolete."); } html->dw->setStyle (html->style ()); @@ -2012,7 +2029,7 @@ static void Html_tag_content_frameset (DilloHtml *html, { HT2TB(html)->addParbreak (9, html->wordStyle ()); HT2TB(html)->addText("--FRAME--", html->wordStyle ()); - Html_add_textblock(html, 5); + Html_add_textblock(html, true, 5); } /* @@ -2089,8 +2106,8 @@ void a_Html_common_image_attrs(DilloHtml *html, const char *tag, int tagsize) { char *width_ptr, *height_ptr; const char *attrbuf; - CssLength l_w = CSS_CREATE_LENGTH(0.0, CSS_LENGTH_TYPE_AUTO); - CssLength l_h = CSS_CREATE_LENGTH(0.0, CSS_LENGTH_TYPE_AUTO); + CssLength l_w = CSS_CREATE_LENGTH(0.0, CSS_LENGTH_TYPE_AUTO); + CssLength l_h = CSS_CREATE_LENGTH(0.0, CSS_LENGTH_TYPE_AUTO); int w = 0, h = 0; if (prefs.show_tooltip && @@ -2123,7 +2140,7 @@ void a_Html_common_image_attrs(DilloHtml *html, const char *tag, int tagsize) */ if (w < 0 || h < 0 || w > IMAGE_MAX_AREA || h > IMAGE_MAX_AREA || - (h > 0 && w > IMAGE_MAX_AREA / h)) { + (h > 0 && w > IMAGE_MAX_AREA / h)) { dFree(width_ptr); dFree(height_ptr); width_ptr = height_ptr = NULL; @@ -2176,6 +2193,8 @@ DilloImage *a_Html_image_new(DilloHtml *html, const char *tag, int tagsize) dw::Image *dw = new dw::Image(alt_ptr); image = a_Image_new(html->dw->getLayout(), (void*)(dw::core::ImgRenderer*)dw, 0); + + a_Image_ref(image); if (HT2TB(html)->getBgColor()) image->bg_color = HT2TB(html)->getBgColor()->getColor(); @@ -2192,10 +2211,10 @@ DilloImage *a_Html_image_new(DilloHtml *html, const char *tag, int tagsize) if (load_now && Html_load_image(html->bw, url, html->page_url, image)) { // hi->image is NULL if dillo tries to load the image immediately hi->image = NULL; + a_Image_unref(image); } else { // otherwise a reference is kept in html->images hi->image = image; - a_Image_ref(image); } dFree(alt_ptr); @@ -2338,7 +2357,7 @@ static void Html_tag_content_map(DilloHtml *html, const char *tag, int tagsize) DilloUrl *url; if (html->InFlags & IN_MAP) { - BUG_MSG("nested <map>\n"); + BUG_MSG("Nested <map>."); } else { if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "name"))) { html->InFlags |= IN_MAP; @@ -2348,7 +2367,7 @@ static void Html_tag_content_map(DilloHtml *html, const char *tag, int tagsize) a_Url_free (url); dFree(hash_name); } else { - BUG_MSG("name attribute is required for <map>\n"); + BUG_MSG("<map> requires name attribute."); } } } @@ -2400,7 +2419,7 @@ misc::SimpleVector<int> *Html_read_coords(DilloHtml *html, const char *str) if (!*newtail) break; if (*newtail != ',') { - BUG_MSG("area coords must be integers separated by commas.\n"); + BUG_MSG("<area> coords must be integers separated by commas."); } tail = newtail + 1; } @@ -2423,7 +2442,7 @@ static void Shape *shape = NULL; if (!(html->InFlags & IN_MAP)) { - BUG_MSG("<area> element not inside <map>\n"); + BUG_MSG("<area> not inside <map>."); return; } attrbuf = a_Html_get_attr(html, tag, tagsize, "shape"); @@ -2439,7 +2458,7 @@ static void } else if (dStrnAsciiCasecmp(attrbuf, "poly", 4) == 0) { type = POLYGON; } else { - BUG_MSG("<area> unknown shape: \"%s\"\n", attrbuf); + BUG_MSG("<area> unknown shape: '%s'.", attrbuf); type = UNKNOWN; } if (type == RECTANGLE || type == CIRCLE || type == POLYGON) { @@ -2449,7 +2468,7 @@ static void if (type == RECTANGLE) { if (coords->size() != 4) - BUG_MSG("<area> rectangle must have four coordinate values\n"); + BUG_MSG("<area> rectangle must have four coordinate values."); if (coords->size() >= 4) shape = new Rectangle(coords->get(0), coords->get(1), @@ -2457,7 +2476,7 @@ static void coords->get(3) - coords->get(1)); } else if (type == CIRCLE) { if (coords->size() != 3) - BUG_MSG("<area> circle must have three coordinate values\n"); + BUG_MSG("<area> circle must have three coordinate values."); if (coords->size() >= 3) shape = new Circle(coords->get(0), coords->get(1), coords->get(2)); @@ -2465,7 +2484,7 @@ static void Polygon *poly; int i; if (coords->size() % 2) - BUG_MSG("<area> polygon with odd number of coordinates\n"); + BUG_MSG("<area> polygon with odd number of coordinates."); shape = poly = new Polygon(); for (i = 0; i < (coords->size() / 2); i++) poly->addPoint(coords->get(2*i), coords->get(2*i + 1)); @@ -2601,11 +2620,11 @@ static void Html_tag_open_source(DilloHtml *html, const char *tag, const char *attrbuf; if (!(html->InFlags & IN_MEDIA)) { - BUG_MSG("<source> element not inside a media element.\n"); + BUG_MSG("<source> not inside a media element."); return; } if (!(attrbuf = a_Html_get_attr(html, tag, tagsize, "src"))) { - BUG_MSG("src attribute is required in <source> element.\n"); + BUG_MSG("<source> requires src attribute."); return; } else { DilloUrl *url = a_Html_url_new(html, attrbuf, NULL, 0); @@ -2682,7 +2701,7 @@ static const char* Html_get_javascript_link(DilloHtml *html) if ((ch == '"' || ch == '\'') && (p2 = strchr(Buf->str + i + 1 , ch))) { p1 = Buf->str + i; - BUG_MSG("link depends on javascript()\n"); + BUG_MSG("Link depends on javascript()."); dStr_truncate(Buf, p2 - Buf->str); dStr_erase(Buf, 0, p1 - Buf->str + 1); } @@ -2697,7 +2716,8 @@ static void Html_add_anchor(DilloHtml *html, const char *name) { _MSG("Registering ANCHOR: %s\n", name); if (!HT2TB(html)->addAnchor (name, html->style ())) - BUG_MSG("Anchor names must be unique within the document ('%s')\n",name); + BUG_MSG("Anchor names must be unique within the document (\"%s\").", + name); /* * According to Sec. 12.2.1 of the HTML 4.01 spec, "anchor names that * differ only in case may not appear in the same document", but @@ -2767,7 +2787,8 @@ static void Html_tag_open_a(DilloHtml *html, const char *tag, int tagsize) /* We compare the "id" value with the url-decoded "name" value */ if (!id || strcmp(nameVal, id)) { if (id) - BUG_MSG("'id' and 'name' attribute of <a> tag differ\n"); + BUG_MSG("In <a>, id ('%s') and name ('%s') attributes differ.", + id, nameVal); Html_add_anchor(html, nameVal); } @@ -2790,7 +2811,7 @@ static void Html_tag_close_a(DilloHtml *html) static void Html_tag_open_blockquote(DilloHtml *html, const char *tag, int tagsize) { - Html_add_textblock(html, 9); + Html_add_textblock(html, true, 9); } /* @@ -2843,7 +2864,7 @@ static void Html_tag_open_ul(DilloHtml *html, const char *tag, int tagsize) html->styleEngine->setNonCssHint (CSS_PROPERTY_LIST_STYLE_TYPE, CSS_TYPE_ENUM, list_style_type); if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) - BUG_MSG("<ul> type attribute is obsolete.\n"); + BUG_MSG("<ul> type attribute is obsolete."); } S_TOP(html)->list_type = HTML_LIST_UNORDERED; @@ -2865,7 +2886,7 @@ static void Html_tag_open_dir(DilloHtml *html, const char *tag, int tagsize) S_TOP(html)->ref_list_item = NULL; if (prefs.show_extra_warnings) - BUG_MSG("Obsolete list type; use <UL> instead\n"); + BUG_MSG("Obsolete list type; use <ul> instead."); } /* @@ -2873,7 +2894,16 @@ static void Html_tag_open_dir(DilloHtml *html, const char *tag, int tagsize) */ static void Html_tag_open_menu(DilloHtml *html, const char *tag, int tagsize) { - Html_tag_open_dir(html, tag, tagsize); + /* In another bit of ridiculous mess from the HTML5 world, the menu + * element, which was deprecated in HTML4: + * - does not appear at all in W3C's HTML5 spec + * - appears in WHATWG's HTML5 doc and the W3C's 5.1 draft, where it + * means something totally different than it did in the old days + * (now it's for popup menus and toolbar menus rather than being a + * sort of list). + */ + if (!(html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)) + Html_tag_open_dir(html, tag, tagsize); } /* @@ -2906,7 +2936,7 @@ static void Html_tag_open_ol(DilloHtml *html, const char *tag, int tagsize) if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "start")) && (n = (int) strtol(attrbuf, NULL, 10)) < 0) { - BUG_MSG( "illegal '-' character in START attribute; Starting from 0\n"); + BUG_MSG("Illegal '-' character in START attribute; Starting from 0."); n = 0; } S_TOP(html)->list_number = n; @@ -2923,7 +2953,7 @@ static void Html_tag_open_li(DilloHtml *html, const char *tag, int tagsize) const char *attrbuf; if (S_TOP(html)->list_type == HTML_LIST_NONE) - BUG_MSG("<li> outside <ul> or <ol>\n"); + BUG_MSG("<li> outside <ul> or <ol>."); html->InFlags |= IN_LI; @@ -2934,7 +2964,7 @@ static void Html_tag_open_li(DilloHtml *html, const char *tag, int tagsize) // ordered if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "value")) && (*list_number = strtol(attrbuf, NULL, 10)) < 0) { - BUG_MSG("illegal negative LIST VALUE attribute; Starting from 0\n"); + BUG_MSG("Illegal negative list value attribute; Starting from 0."); *list_number = 0; } } @@ -2961,7 +2991,7 @@ static void Html_tag_open_hr(DilloHtml *html, const char *tag, int tagsize) width_ptr = a_Html_get_attr_wdef(html, tag, tagsize, "width", NULL); if (width_ptr) { if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) - BUG_MSG("<hr> width attribute is obsolete.\n"); + BUG_MSG("<hr> width attribute is obsolete."); html->styleEngine->setNonCssHint (CSS_PROPERTY_WIDTH, CSS_TYPE_LENGTH_PERCENTAGE, a_Html_parse_length (html, width_ptr)); @@ -2971,7 +3001,7 @@ static void Html_tag_open_hr(DilloHtml *html, const char *tag, int tagsize) if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "size"))) { size = strtol(attrbuf, NULL, 10); if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) - BUG_MSG("<hr> size attribute is obsolete.\n"); + BUG_MSG("<hr> size attribute is obsolete."); } a_Html_tag_set_align_attr(html, tag, tagsize); @@ -2979,7 +3009,7 @@ static void Html_tag_open_hr(DilloHtml *html, const char *tag, int tagsize) /* TODO: evaluate attribute */ if (a_Html_get_attr(html, tag, tagsize, "noshade")) { if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) - BUG_MSG("<hr> noshade attribute is obsolete.\n"); + BUG_MSG("<hr> noshade attribute is obsolete."); html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_TOP_STYLE, CSS_TYPE_ENUM, BORDER_SOLID); html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_BOTTOM_STYLE, @@ -3045,7 +3075,7 @@ static void Html_tag_open_dt(DilloHtml *html, const char *tag, int tagsize) */ static void Html_tag_open_dd(DilloHtml *html, const char *tag, int tagsize) { - Html_add_textblock(html, 9); + Html_add_textblock(html, true, 9); } /* @@ -3134,7 +3164,7 @@ static void Html_tag_open_meta(DilloHtml *html, const char *tag, int tagsize) /* only valid inside HEAD */ if (!(html->InFlags & IN_HEAD)) { - BUG_MSG("META element must be inside the HEAD section\n"); + BUG_MSG("<meta> must be inside the HEAD section."); return; } @@ -3167,7 +3197,7 @@ static void Html_tag_open_meta(DilloHtml *html, const char *tag, int tagsize) if (a_Url_cmp(html->base_url, new_url) == 0) { /* redirection loop, or empty url string: ignore */ - BUG_MSG("META refresh: %s\n", + BUG_MSG("<meta> refresh: %s.", *mr_url ? "redirection loop" : "no target URL"); } else if (delay == 0) { /* zero-delay redirection */ @@ -3237,27 +3267,26 @@ void a_Html_load_stylesheet(DilloHtml *html, DilloUrl *url) dReturn_if (url == NULL || ! prefs.load_stylesheets); _MSG("Html_load_stylesheet: "); - if (a_Capi_get_buf(url, &data, &len)) { + if ((a_Capi_get_flags_with_redirection(url) & CAPI_Completed) && + a_Capi_get_buf(url, &data, &len)) { _MSG("cached URL=%s len=%d", URL_STR(url), len); - if (a_Capi_get_flags_with_redirection(url) & CAPI_Completed) { - if (strncmp("@charset \"", data, 10) == 0) { - char *endq = strchr(data+10, '"'); - - if (endq && (endq - data <= 51)) { - /* IANA limits charset names to 40 characters */ - char *content_type; - - *endq = '\0'; - content_type = dStrconcat("text/css; charset=", data+10, NULL); - *endq = '"'; - a_Capi_unref_buf(url); - a_Capi_set_content_type(url, content_type, "meta"); - dFree(content_type); - a_Capi_get_buf(url, &data, &len); - } + if (strncmp("@charset \"", data, 10) == 0) { + char *endq = strchr(data+10, '"'); + + if (endq && (endq - data <= 51)) { + /* IANA limits charset names to 40 characters */ + char *content_type; + + *endq = '\0'; + content_type = dStrconcat("text/css; charset=", data+10, NULL); + *endq = '"'; + a_Capi_unref_buf(url); + a_Capi_set_content_type(url, content_type, "meta"); + dFree(content_type); + a_Capi_get_buf(url, &data, &len); } - html->styleEngine->parse(html, url, data, len, CSS_ORIGIN_AUTHOR); } + html->styleEngine->parse(html, url, data, len, CSS_ORIGIN_AUTHOR); a_Capi_unref_buf(url); } else { /* Fill a Web structure for the cache query */ @@ -3296,7 +3325,7 @@ static void Html_tag_open_link(DilloHtml *html, const char *tag, int tagsize) /* Ignore LINK outside HEAD */ if (!(html->InFlags & IN_HEAD)) { - BUG_MSG("LINK element must be inside the HEAD section\n"); + BUG_MSG("<link> must be inside the HEAD section."); return; } /* Remote stylesheets enabled? */ @@ -3341,12 +3370,12 @@ static void Html_tag_open_base(DilloHtml *html, const char *tag, int tagsize) a_Url_free(html->base_url); html->base_url = BaseUrl; } else { - BUG_MSG("base URI is relative (it MUST be absolute)\n"); + BUG_MSG("<base> URI is relative (it MUST be absolute)."); a_Url_free(BaseUrl); } } } else { - BUG_MSG("the BASE element must appear in the HEAD section\n"); + BUG_MSG("<base> not inside HEAD section."); } } @@ -3635,10 +3664,10 @@ static int Html_needs_optional_close(int old_idx, int cur_idx) } else if (old_idx == i_TR) { /* TR closes TR */ return (cur_idx == i_TR); - } else if (old_idx == i_DD) { + } else if (old_idx == i_DD) { /* DD is closed by DD and DT */ return (cur_idx == i_DD || cur_idx == i_DT); - } else if (old_idx == i_OPTION) { + } else if (old_idx == i_OPTION) { return 1; // OPTION always needs close } @@ -3684,7 +3713,7 @@ static void Html_stack_cleanup_at_open(DilloHtml *html, int new_idx) /* we have an inline (or empty) container... */ if (Tags[oldtag_idx].EndTag == 'R') { - BUG_MSG("<%s> is not allowed to contain <%s>. -- closing <%s>\n", + BUG_MSG("<%s> is not allowed to contain <%s>. -- closing <%s>.", Tags[oldtag_idx].name, Tags[new_idx].name, Tags[oldtag_idx].name); } @@ -3713,7 +3742,7 @@ static void Html_test_section(DilloHtml *html, int new_idx, int IsCloseTag) int tag_idx; if (!(html->InFlags & IN_HTML) && html->DocType == DT_NONE) - BUG_MSG("the required DOCTYPE declaration is missing.\n"); + BUG_MSG("The required DOCTYPE declaration is missing."); if (!(html->InFlags & IN_HTML)) { tag = "<html>"; @@ -3763,6 +3792,7 @@ static void Html_test_section(DilloHtml *html, int new_idx, int IsCloseTag) static void Html_parse_common_attrs(DilloHtml *html, char *tag, int tagsize) { const char *attrbuf; + char lang[3]; if (tagsize >= 8 && /* length of "<t id=i>" */ (attrbuf = a_Html_get_attr(html, tag, tagsize, "id"))) { @@ -3788,24 +3818,25 @@ static void Html_parse_common_attrs(DilloHtml *html, char *tag, int tagsize) html->styleEngine->setStyle (attrbuf); } - /* handle "xml:lang" and "lang" attributes */ - int hasXmlLang = 0; + /* handle "xml:lang" and "lang" attributes + * We use only the first two chars of the value to deal with + * extended language tags (see http://www.rfc-editor.org/rfc/bcp/bcp47.txt) + */ + memset(lang, 0, sizeof(lang)); if (tagsize >= 14) { /* length of "<t xml:lang=i>" */ attrbuf = a_Html_get_attr(html, tag, tagsize, "xml:lang"); - if (attrbuf) { - html->styleEngine->setNonCssHint(PROPERTY_X_LANG, CSS_TYPE_STRING, - attrbuf); - hasXmlLang = 1; - } + if (attrbuf) + strncpy(lang, attrbuf, 2); } - if (!hasXmlLang && tagsize >= 10) { /* 'xml:lang' prevails over 'lang' */ + if (!lang[0] && tagsize >= 10) { /* 'xml:lang' prevails over 'lang' */ /* length of "<t lang=i>" */ attrbuf = a_Html_get_attr(html, tag, tagsize, "lang"); if (attrbuf) - html->styleEngine->setNonCssHint(PROPERTY_X_LANG, CSS_TYPE_STRING, - attrbuf); + strncpy(lang, attrbuf, 2); } + if (lang[0]) + html->styleEngine->setNonCssHint(PROPERTY_X_LANG, CSS_TYPE_STRING, lang); } /* @@ -3829,7 +3860,7 @@ static void Html_check_html5_obsolete(DilloHtml *html, int ni) } for (int i = 0; i < 9; i++) { if (indexes[i] == ni) { - BUG_MSG("<%s> is obsolete in HTML5.\n", Tags[ni].name); + BUG_MSG("<%s> is obsolete in HTML5.", Tags[ni].name); break; } } @@ -3837,8 +3868,12 @@ static void Html_check_html5_obsolete(DilloHtml *html, int ni) static void Html_display_block(DilloHtml *html) { - //HT2TB(html)->addParbreak (5, html->styleEngine->wordStyle ()); - Html_add_textblock(html, 0); + Html_add_textblock(html, !Html_will_textblock_be_out_of_flow (html), 0); +} + +static void Html_display_inline_block(DilloHtml *html) +{ + Html_add_textblock(html, false, 0); } static void Html_display_listitem(DilloHtml *html) @@ -3919,7 +3954,7 @@ static void Html_process_tag(DilloHtml *html, char *tag, int tagsize) /* TODO: this is only raising a warning, take some defined action. * Note: apache uses IMG inside PRE (we could use its "alt"). */ if ((html->InFlags & IN_PRE) && Html_tag_pre_excludes(ni)) - BUG_MSG("<pre> is not allowed to contain <%s>\n", Tags[ni].name); + BUG_MSG("<pre> is not allowed to contain <%s>.", Tags[ni].name); /* Make sure these elements don't nest each other */ if (html->InFlags & (IN_BUTTON | IN_SELECT | IN_TEXTAREA)) @@ -3943,6 +3978,9 @@ static void Html_process_tag(DilloHtml *html, char *tag, int tagsize) case DISPLAY_BLOCK: Html_display_block(html); break; + case DISPLAY_INLINE_BLOCK: + Html_display_inline_block(html); + break; case DISPLAY_LIST_ITEM: Html_display_listitem(html); break; @@ -3950,7 +3988,6 @@ static void Html_process_tag(DilloHtml *html, char *tag, int tagsize) S_TOP(html)->display_none = true; break; case DISPLAY_INLINE: - case DISPLAY_INLINE_BLOCK: // TODO: implement inline-block default: break; } @@ -4018,7 +4055,7 @@ static const char *Html_get_attr2(DilloHtml *html, const char *attrname, int tag_parsing_flags) { - int i, isocode, entsize, Found = 0, delimiter = 0, attr_pos = 0; + int i, entsize, Found = 0, delimiter = 0, attr_pos = 0; Dstr *Buf = html->attr_data; DilloHtmlTagParsingState state = SEEK_ATTR_START; @@ -4077,16 +4114,12 @@ static const char *Html_get_attr2(DilloHtml *html, state = FINISHED; } else if (tag[i] == '&' && (tag_parsing_flags & HTML_ParseEntities)) { - if ((isocode = Html_parse_entity(html, tag+i, - tagsize-i, &entsize)) >= 0) { - if (isocode >= 128) { - char buf[4]; - int k, n = a_Utf8_encode(isocode, buf); - for (k = 0; k < n; ++k) - dStr_append_c(Buf, buf[k]); - } else { - dStr_append_c(Buf, (char) isocode); - } + const char *entstr; + const bool_t is_attr = TRUE; + + if ((entstr = Html_parse_entity(html, tag+i, tagsize-i, &entsize, + is_attr))) { + dStr_append(Buf, entstr); i += entsize-1; } else { dStr_append_c(Buf, tag[i]); @@ -4228,7 +4261,7 @@ static int Html_write_raw(DilloHtml *html, char *buf, int bufsize, int Eof) buf_index = bufsize; } else { /* Tag: search end of tag (skipping over quoted strings) */ - html->CurrTagOfs = html->Start_Ofs + token_start; + html->CurrOfs = html->Start_Ofs + token_start; while ( buf_index < bufsize ) { buf_index++; @@ -4249,7 +4282,7 @@ static int Html_write_raw(DilloHtml *html, char *buf, int bufsize, int Eof) if (buf[offset] == ch || !buf[offset]) { buf_index = offset; } else { - BUG_MSG("attribute lacks closing quote\n"); + BUG_MSG("Attribute lacks closing quote."); break; } } @@ -4257,7 +4290,7 @@ static int Html_write_raw(DilloHtml *html, char *buf, int bufsize, int Eof) /* unterminated tag detected */ p = dStrndup(buf+token_start+1, strcspn(buf+token_start+1, " <\n\r\t")); - BUG_MSG("<%s> element lacks its closing '>'\n", p); + BUG_MSG("<%s> lacks its closing '>'.", p); dFree(p); --buf_index; break; @@ -4272,6 +4305,8 @@ static int Html_write_raw(DilloHtml *html, char *buf, int bufsize, int Eof) } } else { /* A Word: search for whitespace or tag open */ + html->CurrOfs = html->Start_Ofs + token_start; + while (++buf_index < bufsize) { buf_index += strcspn(buf + buf_index, " <\n\r\t\f\v"); if (buf[buf_index] == '<' && (ch = buf[buf_index + 1]) && diff --git a/src/html_charrefs.h b/src/html_charrefs.h new file mode 100644 index 00000000..38f3849f --- /dev/null +++ b/src/html_charrefs.h @@ -0,0 +1,2138 @@ +#ifndef HTML_CHARREFS_H +#define HTML_CHARREFS_H + +typedef struct { + const char *ref; + const char *html5_str; + const char *html4_str; +} Charref_t; + +#define NumRef 2125 +static const Charref_t Charrefs[NumRef] = { +{"AElig", "Æ", "Æ"}, +{"AMP", "&", NULL}, +{"Aacute", "Á", "Á"}, +{"Abreve", "Ă", NULL}, +{"Acirc", "Â", "Â"}, +{"Acy", "А", NULL}, +{"Afr", "𝔄", NULL}, +{"Agrave", "À", "À"}, +{"Alpha", "Α", "Α"}, +{"Amacr", "Ā", NULL}, +{"And", "⩓", NULL}, +{"Aogon", "Ą", NULL}, +{"Aopf", "𝔸", NULL}, +{"ApplyFunction", "", NULL}, +{"Aring", "Å", "Å"}, +{"Ascr", "𝒜", NULL}, +{"Assign", "≔", NULL}, +{"Atilde", "Ã", "Ã"}, +{"Auml", "Ä", "Ä"}, +{"Backslash", "∖", NULL}, +{"Barv", "⫧", NULL}, +{"Barwed", "⌆", NULL}, +{"Bcy", "Б", NULL}, +{"Because", "∵", NULL}, +{"Bernoullis", "ℬ", NULL}, +{"Beta", "Β", "Β"}, +{"Bfr", "𝔅", NULL}, +{"Bopf", "𝔹", NULL}, +{"Breve", "˘", NULL}, +{"Bscr", "ℬ", NULL}, +{"Bumpeq", "≎", NULL}, +{"CHcy", "Ч", NULL}, +{"COPY", "©", NULL}, +{"Cacute", "Ć", NULL}, +{"Cap", "⋒", NULL}, +{"CapitalDifferentialD", "ⅅ", NULL}, +{"Cayleys", "ℭ", NULL}, +{"Ccaron", "Č", NULL}, +{"Ccedil", "Ç", "Ç"}, +{"Ccirc", "Ĉ", NULL}, +{"Cconint", "∰", NULL}, +{"Cdot", "Ċ", NULL}, +{"Cedilla", "¸", NULL}, +{"CenterDot", "·", NULL}, +{"Cfr", "ℭ", NULL}, +{"Chi", "Χ", "Χ"}, +{"CircleDot", "⊙", NULL}, +{"CircleMinus", "⊖", NULL}, +{"CirclePlus", "⊕", NULL}, +{"CircleTimes", "⊗", NULL}, +{"ClockwiseContourIntegral", "∲", NULL}, +{"CloseCurlyDoubleQuote", "”", NULL}, +{"CloseCurlyQuote", "’", NULL}, +{"Colon", "∷", NULL}, +{"Colone", "⩴", NULL}, +{"Congruent", "≡", NULL}, +{"Conint", "∯", NULL}, +{"ContourIntegral", "∮", NULL}, +{"Copf", "ℂ", NULL}, +{"Coproduct", "∐", NULL}, +{"CounterClockwiseContourIntegral", "∳", NULL}, +{"Cross", "⨯", NULL}, +{"Cscr", "𝒞", NULL}, +{"Cup", "⋓", NULL}, +{"CupCap", "≍", NULL}, +{"DD", "ⅅ", NULL}, +{"DDotrahd", "⤑", NULL}, +{"DJcy", "Ђ", NULL}, +{"DScy", "Ѕ", NULL}, +{"DZcy", "Џ", NULL}, +{"Dagger", "‡", "‡"}, +{"Darr", "↡", NULL}, +{"Dashv", "⫤", NULL}, +{"Dcaron", "Ď", NULL}, +{"Dcy", "Д", NULL}, +{"Del", "∇", NULL}, +{"Delta", "Δ", "Δ"}, +{"Dfr", "𝔇", NULL}, +{"DiacriticalAcute", "´", NULL}, +{"DiacriticalDot", "˙", NULL}, +{"DiacriticalDoubleAcute", "˝", NULL}, +{"DiacriticalGrave", "`", NULL}, +{"DiacriticalTilde", "˜", NULL}, +{"Diamond", "⋄", NULL}, +{"DifferentialD", "ⅆ", NULL}, +{"Dopf", "𝔻", NULL}, +{"Dot", "¨", NULL}, +{"DotDot", "⃜", NULL}, +{"DotEqual", "≐", NULL}, +{"DoubleContourIntegral", "∯", NULL}, +{"DoubleDot", "¨", NULL}, +{"DoubleDownArrow", "⇓", NULL}, +{"DoubleLeftArrow", "⇐", NULL}, +{"DoubleLeftRightArrow", "⇔", NULL}, +{"DoubleLeftTee", "⫤", NULL}, +{"DoubleLongLeftArrow", "⟸", NULL}, +{"DoubleLongLeftRightArrow", "⟺", NULL}, +{"DoubleLongRightArrow", "⟹", NULL}, +{"DoubleRightArrow", "⇒", NULL}, +{"DoubleRightTee", "⊨", NULL}, +{"DoubleUpArrow", "⇑", NULL}, +{"DoubleUpDownArrow", "⇕", NULL}, +{"DoubleVerticalBar", "∥", NULL}, +{"DownArrow", "↓", NULL}, +{"DownArrowBar", "⤓", NULL}, +{"DownArrowUpArrow", "⇵", NULL}, +{"DownBreve", "̑", NULL}, +{"DownLeftRightVector", "⥐", NULL}, +{"DownLeftTeeVector", "⥞", NULL}, +{"DownLeftVector", "↽", NULL}, +{"DownLeftVectorBar", "⥖", NULL}, +{"DownRightTeeVector", "⥟", NULL}, +{"DownRightVector", "⇁", NULL}, +{"DownRightVectorBar", "⥗", NULL}, +{"DownTee", "⊤", NULL}, +{"DownTeeArrow", "↧", NULL}, +{"Downarrow", "⇓", NULL}, +{"Dscr", "𝒟", NULL}, +{"Dstrok", "Đ", NULL}, +{"ENG", "Ŋ", NULL}, +{"ETH", "Ð", "Ð"}, +{"Eacute", "É", "É"}, +{"Ecaron", "Ě", NULL}, +{"Ecirc", "Ê", "Ê"}, +{"Ecy", "Э", NULL}, +{"Edot", "Ė", NULL}, +{"Efr", "𝔈", NULL}, +{"Egrave", "È", "È"}, +{"Element", "∈", NULL}, +{"Emacr", "Ē", NULL}, +{"EmptySmallSquare", "◻", NULL}, +{"EmptyVerySmallSquare", "▫", NULL}, +{"Eogon", "Ę", NULL}, +{"Eopf", "𝔼", NULL}, +{"Epsilon", "Ε", "Ε"}, +{"Equal", "⩵", NULL}, +{"EqualTilde", "≂", NULL}, +{"Equilibrium", "⇌", NULL}, +{"Escr", "ℰ", NULL}, +{"Esim", "⩳", NULL}, +{"Eta", "Η", "Η"}, +{"Euml", "Ë", "Ë"}, +{"Exists", "∃", NULL}, +{"ExponentialE", "ⅇ", NULL}, +{"Fcy", "Ф", NULL}, +{"Ffr", "𝔉", NULL}, +{"FilledSmallSquare", "◼", NULL}, +{"FilledVerySmallSquare", "▪", NULL}, +{"Fopf", "𝔽", NULL}, +{"ForAll", "∀", NULL}, +{"Fouriertrf", "ℱ", NULL}, +{"Fscr", "ℱ", NULL}, +{"GJcy", "Ѓ", NULL}, +{"GT", ">", NULL}, +{"Gamma", "Γ", "Γ"}, +{"Gammad", "Ϝ", NULL}, +{"Gbreve", "Ğ", NULL}, +{"Gcedil", "Ģ", NULL}, +{"Gcirc", "Ĝ", NULL}, +{"Gcy", "Г", NULL}, +{"Gdot", "Ġ", NULL}, +{"Gfr", "𝔊", NULL}, +{"Gg", "⋙", NULL}, +{"Gopf", "𝔾", NULL}, +{"GreaterEqual", "≥", NULL}, +{"GreaterEqualLess", "⋛", NULL}, +{"GreaterFullEqual", "≧", NULL}, +{"GreaterGreater", "⪢", NULL}, +{"GreaterLess", "≷", NULL}, +{"GreaterSlantEqual", "⩾", NULL}, +{"GreaterTilde", "≳", NULL}, +{"Gscr", "𝒢", NULL}, +{"Gt", "≫", NULL}, +{"HARDcy", "Ъ", NULL}, +{"Hacek", "ˇ", NULL}, +{"Hat", "^", NULL}, +{"Hcirc", "Ĥ", NULL}, +{"Hfr", "ℌ", NULL}, +{"HilbertSpace", "ℋ", NULL}, +{"Hopf", "ℍ", NULL}, +{"HorizontalLine", "─", NULL}, +{"Hscr", "ℋ", NULL}, +{"Hstrok", "Ħ", NULL}, +{"HumpDownHump", "≎", NULL}, +{"HumpEqual", "≏", NULL}, +{"IEcy", "Е", NULL}, +{"IJlig", "IJ", NULL}, +{"IOcy", "Ё", NULL}, +{"Iacute", "Í", "Í"}, +{"Icirc", "Î", "Î"}, +{"Icy", "И", NULL}, +{"Idot", "İ", NULL}, +{"Ifr", "ℑ", NULL}, +{"Igrave", "Ì", "Ì"}, +{"Im", "ℑ", NULL}, +{"Imacr", "Ī", NULL}, +{"ImaginaryI", "ⅈ", NULL}, +{"Implies", "⇒", NULL}, +{"Int", "∬", NULL}, +{"Integral", "∫", NULL}, +{"Intersection", "⋂", NULL}, +{"InvisibleComma", "", NULL}, +{"InvisibleTimes", "", NULL}, +{"Iogon", "Į", NULL}, +{"Iopf", "𝕀", NULL}, +{"Iota", "Ι", "Ι"}, +{"Iscr", "ℐ", NULL}, +{"Itilde", "Ĩ", NULL}, +{"Iukcy", "І", NULL}, +{"Iuml", "Ï", "Ï"}, +{"Jcirc", "Ĵ", NULL}, +{"Jcy", "Й", NULL}, +{"Jfr", "𝔍", NULL}, +{"Jopf", "𝕁", NULL}, +{"Jscr", "𝒥", NULL}, +{"Jsercy", "Ј", NULL}, +{"Jukcy", "Є", NULL}, +{"KHcy", "Х", NULL}, +{"KJcy", "Ќ", NULL}, +{"Kappa", "Κ", "Κ"}, +{"Kcedil", "Ķ", NULL}, +{"Kcy", "К", NULL}, +{"Kfr", "𝔎", NULL}, +{"Kopf", "𝕂", NULL}, +{"Kscr", "𝒦", NULL}, +{"LJcy", "Љ", NULL}, +{"LT", "<", NULL}, +{"Lacute", "Ĺ", NULL}, +{"Lambda", "Λ", "Λ"}, +{"Lang", "⟪", NULL}, +{"Laplacetrf", "ℒ", NULL}, +{"Larr", "↞", NULL}, +{"Lcaron", "Ľ", NULL}, +{"Lcedil", "Ļ", NULL}, +{"Lcy", "Л", NULL}, +{"LeftAngleBracket", "⟨", NULL}, +{"LeftArrow", "←", NULL}, +{"LeftArrowBar", "⇤", NULL}, +{"LeftArrowRightArrow", "⇆", NULL}, +{"LeftCeiling", "⌈", NULL}, +{"LeftDoubleBracket", "⟦", NULL}, +{"LeftDownTeeVector", "⥡", NULL}, +{"LeftDownVector", "⇃", NULL}, +{"LeftDownVectorBar", "⥙", NULL}, +{"LeftFloor", "⌊", NULL}, +{"LeftRightArrow", "↔", NULL}, +{"LeftRightVector", "⥎", NULL}, +{"LeftTee", "⊣", NULL}, +{"LeftTeeArrow", "↤", NULL}, +{"LeftTeeVector", "⥚", NULL}, +{"LeftTriangle", "⊲", NULL}, +{"LeftTriangleBar", "⧏", NULL}, +{"LeftTriangleEqual", "⊴", NULL}, +{"LeftUpDownVector", "⥑", NULL}, +{"LeftUpTeeVector", "⥠", NULL}, +{"LeftUpVector", "↿", NULL}, +{"LeftUpVectorBar", "⥘", NULL}, +{"LeftVector", "↼", NULL}, +{"LeftVectorBar", "⥒", NULL}, +{"Leftarrow", "⇐", NULL}, +{"Leftrightarrow", "⇔", NULL}, +{"LessEqualGreater", "⋚", NULL}, +{"LessFullEqual", "≦", NULL}, +{"LessGreater", "≶", NULL}, +{"LessLess", "⪡", NULL}, +{"LessSlantEqual", "⩽", NULL}, +{"LessTilde", "≲", NULL}, +{"Lfr", "𝔏", NULL}, +{"Ll", "⋘", NULL}, +{"Lleftarrow", "⇚", NULL}, +{"Lmidot", "Ŀ", NULL}, +{"LongLeftArrow", "⟵", NULL}, +{"LongLeftRightArrow", "⟷", NULL}, +{"LongRightArrow", "⟶", NULL}, +{"Longleftarrow", "⟸", NULL}, +{"Longleftrightarrow", "⟺", NULL}, +{"Longrightarrow", "⟹", NULL}, +{"Lopf", "𝕃", NULL}, +{"LowerLeftArrow", "↙", NULL}, +{"LowerRightArrow", "↘", NULL}, +{"Lscr", "ℒ", NULL}, +{"Lsh", "↰", NULL}, +{"Lstrok", "Ł", NULL}, +{"Lt", "≪", NULL}, +{"Map", "⤅", NULL}, +{"Mcy", "М", NULL}, +{"MediumSpace", " ", NULL}, +{"Mellintrf", "ℳ", NULL}, +{"Mfr", "𝔐", NULL}, +{"MinusPlus", "∓", NULL}, +{"Mopf", "𝕄", NULL}, +{"Mscr", "ℳ", NULL}, +{"Mu", "Μ", "Μ"}, +{"NJcy", "Њ", NULL}, +{"Nacute", "Ń", NULL}, +{"Ncaron", "Ň", NULL}, +{"Ncedil", "Ņ", NULL}, +{"Ncy", "Н", NULL}, +{"NegativeMediumSpace", "", NULL}, +{"NegativeThickSpace", "", NULL}, +{"NegativeThinSpace", "", NULL}, +{"NegativeVeryThinSpace", "", NULL}, +{"NestedGreaterGreater", "≫", NULL}, +{"NestedLessLess", "≪", NULL}, +{"NewLine", "\n", NULL}, +{"Nfr", "𝔑", NULL}, +{"NoBreak", "", NULL}, +{"NonBreakingSpace", " ", NULL}, +{"Nopf", "ℕ", NULL}, +{"Not", "⫬", NULL}, +{"NotCongruent", "≢", NULL}, +{"NotCupCap", "≭", NULL}, +{"NotDoubleVerticalBar", "∦", NULL}, +{"NotElement", "∉", NULL}, +{"NotEqual", "≠", NULL}, +{"NotEqualTilde", "≂̸", NULL}, +{"NotExists", "∄", NULL}, +{"NotGreater", "≯", NULL}, +{"NotGreaterEqual", "≱", NULL}, +{"NotGreaterFullEqual", "≧̸", NULL}, +{"NotGreaterGreater", "≫̸", NULL}, +{"NotGreaterLess", "≹", NULL}, +{"NotGreaterSlantEqual", "⩾̸", NULL}, +{"NotGreaterTilde", "≵", NULL}, +{"NotHumpDownHump", "≎̸", NULL}, +{"NotHumpEqual", "≏̸", NULL}, +{"NotLeftTriangle", "⋪", NULL}, +{"NotLeftTriangleBar", "⧏̸", NULL}, +{"NotLeftTriangleEqual", "⋬", NULL}, +{"NotLess", "≮", NULL}, +{"NotLessEqual", "≰", NULL}, +{"NotLessGreater", "≸", NULL}, +{"NotLessLess", "≪̸", NULL}, +{"NotLessSlantEqual", "⩽̸", NULL}, +{"NotLessTilde", "≴", NULL}, +{"NotNestedGreaterGreater", "⪢̸", NULL}, +{"NotNestedLessLess", "⪡̸", NULL}, +{"NotPrecedes", "⊀", NULL}, +{"NotPrecedesEqual", "⪯̸", NULL}, +{"NotPrecedesSlantEqual", "⋠", NULL}, +{"NotReverseElement", "∌", NULL}, +{"NotRightTriangle", "⋫", NULL}, +{"NotRightTriangleBar", "⧐̸", NULL}, +{"NotRightTriangleEqual", "⋭", NULL}, +{"NotSquareSubset", "⊏̸", NULL}, +{"NotSquareSubsetEqual", "⋢", NULL}, +{"NotSquareSuperset", "⊐̸", NULL}, +{"NotSquareSupersetEqual", "⋣", NULL}, +{"NotSubset", "⊂⃒", NULL}, +{"NotSubsetEqual", "⊈", NULL}, +{"NotSucceeds", "⊁", NULL}, +{"NotSucceedsEqual", "⪰̸", NULL}, +{"NotSucceedsSlantEqual", "⋡", NULL}, +{"NotSucceedsTilde", "≿̸", NULL}, +{"NotSuperset", "⊃⃒", NULL}, +{"NotSupersetEqual", "⊉", NULL}, +{"NotTilde", "≁", NULL}, +{"NotTildeEqual", "≄", NULL}, +{"NotTildeFullEqual", "≇", NULL}, +{"NotTildeTilde", "≉", NULL}, +{"NotVerticalBar", "∤", NULL}, +{"Nscr", "𝒩", NULL}, +{"Ntilde", "Ñ", "Ñ"}, +{"Nu", "Ν", "Ν"}, +{"OElig", "Œ", "Œ"}, +{"Oacute", "Ó", "Ó"}, +{"Ocirc", "Ô", "Ô"}, +{"Ocy", "О", NULL}, +{"Odblac", "Ő", NULL}, +{"Ofr", "𝔒", NULL}, +{"Ograve", "Ò", "Ò"}, +{"Omacr", "Ō", NULL}, +{"Omega", "Ω", "Ω"}, +{"Omicron", "Ο", "Ο"}, +{"Oopf", "𝕆", NULL}, +{"OpenCurlyDoubleQuote", "“", NULL}, +{"OpenCurlyQuote", "‘", NULL}, +{"Or", "⩔", NULL}, +{"Oscr", "𝒪", NULL}, +{"Oslash", "Ø", "Ø"}, +{"Otilde", "Õ", "Õ"}, +{"Otimes", "⨷", NULL}, +{"Ouml", "Ö", "Ö"}, +{"OverBar", "‾", NULL}, +{"OverBrace", "⏞", NULL}, +{"OverBracket", "⎴", NULL}, +{"OverParenthesis", "⏜", NULL}, +{"PartialD", "∂", NULL}, +{"Pcy", "П", NULL}, +{"Pfr", "𝔓", NULL}, +{"Phi", "Φ", "Φ"}, +{"Pi", "Π", "Π"}, +{"PlusMinus", "±", NULL}, +{"Poincareplane", "ℌ", NULL}, +{"Popf", "ℙ", NULL}, +{"Pr", "⪻", NULL}, +{"Precedes", "≺", NULL}, +{"PrecedesEqual", "⪯", NULL}, +{"PrecedesSlantEqual", "≼", NULL}, +{"PrecedesTilde", "≾", NULL}, +{"Prime", "″", "″"}, +{"Product", "∏", NULL}, +{"Proportion", "∷", NULL}, +{"Proportional", "∝", NULL}, +{"Pscr", "𝒫", NULL}, +{"Psi", "Ψ", "Ψ"}, +{"QUOT", "\"", NULL}, +{"Qfr", "𝔔", NULL}, +{"Qopf", "ℚ", NULL}, +{"Qscr", "𝒬", NULL}, +{"RBarr", "⤐", NULL}, +{"REG", "®", NULL}, +{"Racute", "Ŕ", NULL}, +{"Rang", "⟫", NULL}, +{"Rarr", "↠", NULL}, +{"Rarrtl", "⤖", NULL}, +{"Rcaron", "Ř", NULL}, +{"Rcedil", "Ŗ", NULL}, +{"Rcy", "Р", NULL}, +{"Re", "ℜ", NULL}, +{"ReverseElement", "∋", NULL}, +{"ReverseEquilibrium", "⇋", NULL}, +{"ReverseUpEquilibrium", "⥯", NULL}, +{"Rfr", "ℜ", NULL}, +{"Rho", "Ρ", "Ρ"}, +{"RightAngleBracket", "⟩", NULL}, +{"RightArrow", "→", NULL}, +{"RightArrowBar", "⇥", NULL}, +{"RightArrowLeftArrow", "⇄", NULL}, +{"RightCeiling", "⌉", NULL}, +{"RightDoubleBracket", "⟧", NULL}, +{"RightDownTeeVector", "⥝", NULL}, +{"RightDownVector", "⇂", NULL}, +{"RightDownVectorBar", "⥕", NULL}, +{"RightFloor", "⌋", NULL}, +{"RightTee", "⊢", NULL}, +{"RightTeeArrow", "↦", NULL}, +{"RightTeeVector", "⥛", NULL}, +{"RightTriangle", "⊳", NULL}, +{"RightTriangleBar", "⧐", NULL}, +{"RightTriangleEqual", "⊵", NULL}, +{"RightUpDownVector", "⥏", NULL}, +{"RightUpTeeVector", "⥜", NULL}, +{"RightUpVector", "↾", NULL}, +{"RightUpVectorBar", "⥔", NULL}, +{"RightVector", "⇀", NULL}, +{"RightVectorBar", "⥓", NULL}, +{"Rightarrow", "⇒", NULL}, +{"Ropf", "ℝ", NULL}, +{"RoundImplies", "⥰", NULL}, +{"Rrightarrow", "⇛", NULL}, +{"Rscr", "ℛ", NULL}, +{"Rsh", "↱", NULL}, +{"RuleDelayed", "⧴", NULL}, +{"SHCHcy", "Щ", NULL}, +{"SHcy", "Ш", NULL}, +{"SOFTcy", "Ь", NULL}, +{"Sacute", "Ś", NULL}, +{"Sc", "⪼", NULL}, +{"Scaron", "Š", "Š"}, +{"Scedil", "Ş", NULL}, +{"Scirc", "Ŝ", NULL}, +{"Scy", "С", NULL}, +{"Sfr", "𝔖", NULL}, +{"ShortDownArrow", "↓", NULL}, +{"ShortLeftArrow", "←", NULL}, +{"ShortRightArrow", "→", NULL}, +{"ShortUpArrow", "↑", NULL}, +{"Sigma", "Σ", "Σ"}, +{"SmallCircle", "∘", NULL}, +{"Sopf", "𝕊", NULL}, +{"Sqrt", "√", NULL}, +{"Square", "□", NULL}, +{"SquareIntersection", "⊓", NULL}, +{"SquareSubset", "⊏", NULL}, +{"SquareSubsetEqual", "⊑", NULL}, +{"SquareSuperset", "⊐", NULL}, +{"SquareSupersetEqual", "⊒", NULL}, +{"SquareUnion", "⊔", NULL}, +{"Sscr", "𝒮", NULL}, +{"Star", "⋆", NULL}, +{"Sub", "⋐", NULL}, +{"Subset", "⋐", NULL}, +{"SubsetEqual", "⊆", NULL}, +{"Succeeds", "≻", NULL}, +{"SucceedsEqual", "⪰", NULL}, +{"SucceedsSlantEqual", "≽", NULL}, +{"SucceedsTilde", "≿", NULL}, +{"SuchThat", "∋", NULL}, +{"Sum", "∑", NULL}, +{"Sup", "⋑", NULL}, +{"Superset", "⊃", NULL}, +{"SupersetEqual", "⊇", NULL}, +{"Supset", "⋑", NULL}, +{"THORN", "Þ", "Þ"}, +{"TRADE", "™", NULL}, +{"TSHcy", "Ћ", NULL}, +{"TScy", "Ц", NULL}, +{"Tab", "\t", NULL}, +{"Tau", "Τ", "Τ"}, +{"Tcaron", "Ť", NULL}, +{"Tcedil", "Ţ", NULL}, +{"Tcy", "Т", NULL}, +{"Tfr", "𝔗", NULL}, +{"Therefore", "∴", NULL}, +{"Theta", "Θ", "Θ"}, +{"ThickSpace", " ", NULL}, +{"ThinSpace", " ", NULL}, +{"Tilde", "∼", NULL}, +{"TildeEqual", "≃", NULL}, +{"TildeFullEqual", "≅", NULL}, +{"TildeTilde", "≈", NULL}, +{"Topf", "𝕋", NULL}, +{"TripleDot", "⃛", NULL}, +{"Tscr", "𝒯", NULL}, +{"Tstrok", "Ŧ", NULL}, +{"Uacute", "Ú", "Ú"}, +{"Uarr", "↟", NULL}, +{"Uarrocir", "⥉", NULL}, +{"Ubrcy", "Ў", NULL}, +{"Ubreve", "Ŭ", NULL}, +{"Ucirc", "Û", "Û"}, +{"Ucy", "У", NULL}, +{"Udblac", "Ű", NULL}, +{"Ufr", "𝔘", NULL}, +{"Ugrave", "Ù", "Ù"}, +{"Umacr", "Ū", NULL}, +{"UnderBar", "_", NULL}, +{"UnderBrace", "⏟", NULL}, +{"UnderBracket", "⎵", NULL}, +{"UnderParenthesis", "⏝", NULL}, +{"Union", "⋃", NULL}, +{"UnionPlus", "⊎", NULL}, +{"Uogon", "Ų", NULL}, +{"Uopf", "𝕌", NULL}, +{"UpArrow", "↑", NULL}, +{"UpArrowBar", "⤒", NULL}, +{"UpArrowDownArrow", "⇅", NULL}, +{"UpDownArrow", "↕", NULL}, +{"UpEquilibrium", "⥮", NULL}, +{"UpTee", "⊥", NULL}, +{"UpTeeArrow", "↥", NULL}, +{"Uparrow", "⇑", NULL}, +{"Updownarrow", "⇕", NULL}, +{"UpperLeftArrow", "↖", NULL}, +{"UpperRightArrow", "↗", NULL}, +{"Upsi", "ϒ", NULL}, +{"Upsilon", "Υ", "Υ"}, +{"Uring", "Ů", NULL}, +{"Uscr", "𝒰", NULL}, +{"Utilde", "Ũ", NULL}, +{"Uuml", "Ü", "Ü"}, +{"VDash", "⊫", NULL}, +{"Vbar", "⫫", NULL}, +{"Vcy", "В", NULL}, +{"Vdash", "⊩", NULL}, +{"Vdashl", "⫦", NULL}, +{"Vee", "⋁", NULL}, +{"Verbar", "‖", NULL}, +{"Vert", "‖", NULL}, +{"VerticalBar", "∣", NULL}, +{"VerticalLine", "|", NULL}, +{"VerticalSeparator", "❘", NULL}, +{"VerticalTilde", "≀", NULL}, +{"VeryThinSpace", " ", NULL}, +{"Vfr", "𝔙", NULL}, +{"Vopf", "𝕍", NULL}, +{"Vscr", "𝒱", NULL}, +{"Vvdash", "⊪", NULL}, +{"Wcirc", "Ŵ", NULL}, +{"Wedge", "⋀", NULL}, +{"Wfr", "𝔚", NULL}, +{"Wopf", "𝕎", NULL}, +{"Wscr", "𝒲", NULL}, +{"Xfr", "𝔛", NULL}, +{"Xi", "Ξ", "Ξ"}, +{"Xopf", "𝕏", NULL}, +{"Xscr", "𝒳", NULL}, +{"YAcy", "Я", NULL}, +{"YIcy", "Ї", NULL}, +{"YUcy", "Ю", NULL}, +{"Yacute", "Ý", "Ý"}, +{"Ycirc", "Ŷ", NULL}, +{"Ycy", "Ы", NULL}, +{"Yfr", "𝔜", NULL}, +{"Yopf", "𝕐", NULL}, +{"Yscr", "𝒴", NULL}, +{"Yuml", "Ÿ", "Ÿ"}, +{"ZHcy", "Ж", NULL}, +{"Zacute", "Ź", NULL}, +{"Zcaron", "Ž", NULL}, +{"Zcy", "З", NULL}, +{"Zdot", "Ż", NULL}, +{"ZeroWidthSpace", "", NULL}, +{"Zeta", "Ζ", "Ζ"}, +{"Zfr", "ℨ", NULL}, +{"Zopf", "ℤ", NULL}, +{"Zscr", "𝒵", NULL}, +{"aacute", "á", "á"}, +{"abreve", "ă", NULL}, +{"ac", "∾", NULL}, +{"acE", "∾̳", NULL}, +{"acd", "∿", NULL}, +{"acirc", "â", "â"}, +{"acute", "´", "´"}, +{"acy", "а", NULL}, +{"aelig", "æ", "æ"}, +{"af", "", NULL}, +{"afr", "𝔞", NULL}, +{"agrave", "à", "à"}, +{"alefsym", "ℵ", "ℵ"}, +{"aleph", "ℵ", NULL}, +{"alpha", "α", "α"}, +{"amacr", "ā", NULL}, +{"amalg", "⨿", NULL}, +{"amp", "&", "&"}, +{"and", "∧", "∧"}, +{"andand", "⩕", NULL}, +{"andd", "⩜", NULL}, +{"andslope", "⩘", NULL}, +{"andv", "⩚", NULL}, +{"ang", "∠", "∠"}, +{"ange", "⦤", NULL}, +{"angle", "∠", NULL}, +{"angmsd", "∡", NULL}, +{"angmsdaa", "⦨", NULL}, +{"angmsdab", "⦩", NULL}, +{"angmsdac", "⦪", NULL}, +{"angmsdad", "⦫", NULL}, +{"angmsdae", "⦬", NULL}, +{"angmsdaf", "⦭", NULL}, +{"angmsdag", "⦮", NULL}, +{"angmsdah", "⦯", NULL}, +{"angrt", "∟", NULL}, +{"angrtvb", "⊾", NULL}, +{"angrtvbd", "⦝", NULL}, +{"angsph", "∢", NULL}, +{"angst", "Å", NULL}, +{"angzarr", "⍼", NULL}, +{"aogon", "ą", NULL}, +{"aopf", "𝕒", NULL}, +{"ap", "≈", NULL}, +{"apE", "⩰", NULL}, +{"apacir", "⩯", NULL}, +{"ape", "≊", NULL}, +{"apid", "≋", NULL}, +{"apos", "'", NULL}, +{"approx", "≈", NULL}, +{"approxeq", "≊", NULL}, +{"aring", "å", "å"}, +{"ascr", "𝒶", NULL}, +{"ast", "*", NULL}, +{"asymp", "≈", "≈"}, +{"asympeq", "≍", NULL}, +{"atilde", "ã", "ã"}, +{"auml", "ä", "ä"}, +{"awconint", "∳", NULL}, +{"awint", "⨑", NULL}, +{"bNot", "⫭", NULL}, +{"backcong", "≌", NULL}, +{"backepsilon", "϶", NULL}, +{"backprime", "‵", NULL}, +{"backsim", "∽", NULL}, +{"backsimeq", "⋍", NULL}, +{"barvee", "⊽", NULL}, +{"barwed", "⌅", NULL}, +{"barwedge", "⌅", NULL}, +{"bbrk", "⎵", NULL}, +{"bbrktbrk", "⎶", NULL}, +{"bcong", "≌", NULL}, +{"bcy", "б", NULL}, +{"bdquo", "„", "„"}, +{"becaus", "∵", NULL}, +{"because", "∵", NULL}, +{"bemptyv", "⦰", NULL}, +{"bepsi", "϶", NULL}, +{"bernou", "ℬ", NULL}, +{"beta", "β", "β"}, +{"beth", "ℶ", NULL}, +{"between", "≬", NULL}, +{"bfr", "𝔟", NULL}, +{"bigcap", "⋂", NULL}, +{"bigcirc", "◯", NULL}, +{"bigcup", "⋃", NULL}, +{"bigodot", "⨀", NULL}, +{"bigoplus", "⨁", NULL}, +{"bigotimes", "⨂", NULL}, +{"bigsqcup", "⨆", NULL}, +{"bigstar", "★", NULL}, +{"bigtriangledown", "▽", NULL}, +{"bigtriangleup", "△", NULL}, +{"biguplus", "⨄", NULL}, +{"bigvee", "⋁", NULL}, +{"bigwedge", "⋀", NULL}, +{"bkarow", "⤍", NULL}, +{"blacklozenge", "⧫", NULL}, +{"blacksquare", "▪", NULL}, +{"blacktriangle", "▴", NULL}, +{"blacktriangledown", "▾", NULL}, +{"blacktriangleleft", "◂", NULL}, +{"blacktriangleright", "▸", NULL}, +{"blank", "␣", NULL}, +{"blk12", "▒", NULL}, +{"blk14", "░", NULL}, +{"blk34", "▓", NULL}, +{"block", "█", NULL}, +{"bne", "=⃥", NULL}, +{"bnequiv", "≡⃥", NULL}, +{"bnot", "⌐", NULL}, +{"bopf", "𝕓", NULL}, +{"bot", "⊥", NULL}, +{"bottom", "⊥", NULL}, +{"bowtie", "⋈", NULL}, +{"boxDL", "╗", NULL}, +{"boxDR", "╔", NULL}, +{"boxDl", "╖", NULL}, +{"boxDr", "╓", NULL}, +{"boxH", "═", NULL}, +{"boxHD", "╦", NULL}, +{"boxHU", "╩", NULL}, +{"boxHd", "╤", NULL}, +{"boxHu", "╧", NULL}, +{"boxUL", "╝", NULL}, +{"boxUR", "╚", NULL}, +{"boxUl", "╜", NULL}, +{"boxUr", "╙", NULL}, +{"boxV", "║", NULL}, +{"boxVH", "╬", NULL}, +{"boxVL", "╣", NULL}, +{"boxVR", "╠", NULL}, +{"boxVh", "╫", NULL}, +{"boxVl", "╢", NULL}, +{"boxVr", "╟", NULL}, +{"boxbox", "⧉", NULL}, +{"boxdL", "╕", NULL}, +{"boxdR", "╒", NULL}, +{"boxdl", "┐", NULL}, +{"boxdr", "┌", NULL}, +{"boxh", "─", NULL}, +{"boxhD", "╥", NULL}, +{"boxhU", "╨", NULL}, +{"boxhd", "┬", NULL}, +{"boxhu", "┴", NULL}, +{"boxminus", "⊟", NULL}, +{"boxplus", "⊞", NULL}, +{"boxtimes", "⊠", NULL}, +{"boxuL", "╛", NULL}, +{"boxuR", "╘", NULL}, +{"boxul", "┘", NULL}, +{"boxur", "└", NULL}, +{"boxv", "│", NULL}, +{"boxvH", "╪", NULL}, +{"boxvL", "╡", NULL}, +{"boxvR", "╞", NULL}, +{"boxvh", "┼", NULL}, +{"boxvl", "┤", NULL}, +{"boxvr", "├", NULL}, +{"bprime", "‵", NULL}, +{"breve", "˘", NULL}, +{"brvbar", "¦", "¦"}, +{"bscr", "𝒷", NULL}, +{"bsemi", "⁏", NULL}, +{"bsim", "∽", NULL}, +{"bsime", "⋍", NULL}, +{"bsol", "\\", NULL}, +{"bsolb", "⧅", NULL}, +{"bsolhsub", "⟈", NULL}, +{"bull", "•", "•"}, +{"bullet", "•", NULL}, +{"bump", "≎", NULL}, +{"bumpE", "⪮", NULL}, +{"bumpe", "≏", NULL}, +{"bumpeq", "≏", NULL}, +{"cacute", "ć", NULL}, +{"cap", "∩", "∩"}, +{"capand", "⩄", NULL}, +{"capbrcup", "⩉", NULL}, +{"capcap", "⩋", NULL}, +{"capcup", "⩇", NULL}, +{"capdot", "⩀", NULL}, +{"caps", "∩︀", NULL}, +{"caret", "⁁", NULL}, +{"caron", "ˇ", NULL}, +{"ccaps", "⩍", NULL}, +{"ccaron", "č", NULL}, +{"ccedil", "ç", "ç"}, +{"ccirc", "ĉ", NULL}, +{"ccups", "⩌", NULL}, +{"ccupssm", "⩐", NULL}, +{"cdot", "ċ", NULL}, +{"cedil", "¸", "¸"}, +{"cemptyv", "⦲", NULL}, +{"cent", "¢", "¢"}, +{"centerdot", "·", NULL}, +{"cfr", "𝔠", NULL}, +{"chcy", "ч", NULL}, +{"check", "✓", NULL}, +{"checkmark", "✓", NULL}, +{"chi", "χ", "χ"}, +{"cir", "○", NULL}, +{"cirE", "⧃", NULL}, +{"circ", "ˆ", "ˆ"}, +{"circeq", "≗", NULL}, +{"circlearrowleft", "↺", NULL}, +{"circlearrowright", "↻", NULL}, +{"circledR", "®", NULL}, +{"circledS", "Ⓢ", NULL}, +{"circledast", "⊛", NULL}, +{"circledcirc", "⊚", NULL}, +{"circleddash", "⊝", NULL}, +{"cire", "≗", NULL}, +{"cirfnint", "⨐", NULL}, +{"cirmid", "⫯", NULL}, +{"cirscir", "⧂", NULL}, +{"clubs", "♣", "♣"}, +{"clubsuit", "♣", NULL}, +{"colon", ":", NULL}, +{"colone", "≔", NULL}, +{"coloneq", "≔", NULL}, +{"comma", ",", NULL}, +{"commat", "@", NULL}, +{"comp", "∁", NULL}, +{"compfn", "∘", NULL}, +{"complement", "∁", NULL}, +{"complexes", "ℂ", NULL}, +{"cong", "≅", "≅"}, +{"congdot", "⩭", NULL}, +{"conint", "∮", NULL}, +{"copf", "𝕔", NULL}, +{"coprod", "∐", NULL}, +{"copy", "©", "©"}, +{"copysr", "℗", NULL}, +{"crarr", "↵", "↵"}, +{"cross", "✗", NULL}, +{"cscr", "𝒸", NULL}, +{"csub", "⫏", NULL}, +{"csube", "⫑", NULL}, +{"csup", "⫐", NULL}, +{"csupe", "⫒", NULL}, +{"ctdot", "⋯", NULL}, +{"cudarrl", "⤸", NULL}, +{"cudarrr", "⤵", NULL}, +{"cuepr", "⋞", NULL}, +{"cuesc", "⋟", NULL}, +{"cularr", "↶", NULL}, +{"cularrp", "⤽", NULL}, +{"cup", "∪", "∪"}, +{"cupbrcap", "⩈", NULL}, +{"cupcap", "⩆", NULL}, +{"cupcup", "⩊", NULL}, +{"cupdot", "⊍", NULL}, +{"cupor", "⩅", NULL}, +{"cups", "∪︀", NULL}, +{"curarr", "↷", NULL}, +{"curarrm", "⤼", NULL}, +{"curlyeqprec", "⋞", NULL}, +{"curlyeqsucc", "⋟", NULL}, +{"curlyvee", "⋎", NULL}, +{"curlywedge", "⋏", NULL}, +{"curren", "¤", "¤"}, +{"curvearrowleft", "↶", NULL}, +{"curvearrowright", "↷", NULL}, +{"cuvee", "⋎", NULL}, +{"cuwed", "⋏", NULL}, +{"cwconint", "∲", NULL}, +{"cwint", "∱", NULL}, +{"cylcty", "⌭", NULL}, +{"dArr", "⇓", "⇓"}, +{"dHar", "⥥", NULL}, +{"dagger", "†", "†"}, +{"daleth", "ℸ", NULL}, +{"darr", "↓", "↓"}, +{"dash", "‐", NULL}, +{"dashv", "⊣", NULL}, +{"dbkarow", "⤏", NULL}, +{"dblac", "˝", NULL}, +{"dcaron", "ď", NULL}, +{"dcy", "д", NULL}, +{"dd", "ⅆ", NULL}, +{"ddagger", "‡", NULL}, +{"ddarr", "⇊", NULL}, +{"ddotseq", "⩷", NULL}, +{"deg", "°", "°"}, +{"delta", "δ", "δ"}, +{"demptyv", "⦱", NULL}, +{"dfisht", "⥿", NULL}, +{"dfr", "𝔡", NULL}, +{"dharl", "⇃", NULL}, +{"dharr", "⇂", NULL}, +{"diam", "⋄", NULL}, +{"diamond", "⋄", NULL}, +{"diamondsuit", "♦", NULL}, +{"diams", "♦", "♦"}, +{"die", "¨", NULL}, +{"digamma", "ϝ", NULL}, +{"disin", "⋲", NULL}, +{"div", "÷", NULL}, +{"divide", "÷", "÷"}, +{"divideontimes", "⋇", NULL}, +{"divonx", "⋇", NULL}, +{"djcy", "ђ", NULL}, +{"dlcorn", "⌞", NULL}, +{"dlcrop", "⌍", NULL}, +{"dollar", "$", NULL}, +{"dopf", "𝕕", NULL}, +{"dot", "˙", NULL}, +{"doteq", "≐", NULL}, +{"doteqdot", "≑", NULL}, +{"dotminus", "∸", NULL}, +{"dotplus", "∔", NULL}, +{"dotsquare", "⊡", NULL}, +{"doublebarwedge", "⌆", NULL}, +{"downarrow", "↓", NULL}, +{"downdownarrows", "⇊", NULL}, +{"downharpoonleft", "⇃", NULL}, +{"downharpoonright", "⇂", NULL}, +{"drbkarow", "⤐", NULL}, +{"drcorn", "⌟", NULL}, +{"drcrop", "⌌", NULL}, +{"dscr", "𝒹", NULL}, +{"dscy", "ѕ", NULL}, +{"dsol", "⧶", NULL}, +{"dstrok", "đ", NULL}, +{"dtdot", "⋱", NULL}, +{"dtri", "▿", NULL}, +{"dtrif", "▾", NULL}, +{"duarr", "⇵", NULL}, +{"duhar", "⥯", NULL}, +{"dwangle", "⦦", NULL}, +{"dzcy", "џ", NULL}, +{"dzigrarr", "⟿", NULL}, +{"eDDot", "⩷", NULL}, +{"eDot", "≑", NULL}, +{"eacute", "é", "é"}, +{"easter", "⩮", NULL}, +{"ecaron", "ě", NULL}, +{"ecir", "≖", NULL}, +{"ecirc", "ê", "ê"}, +{"ecolon", "≕", NULL}, +{"ecy", "э", NULL}, +{"edot", "ė", NULL}, +{"ee", "ⅇ", NULL}, +{"efDot", "≒", NULL}, +{"efr", "𝔢", NULL}, +{"eg", "⪚", NULL}, +{"egrave", "è", "è"}, +{"egs", "⪖", NULL}, +{"egsdot", "⪘", NULL}, +{"el", "⪙", NULL}, +{"elinters", "⏧", NULL}, +{"ell", "ℓ", NULL}, +{"els", "⪕", NULL}, +{"elsdot", "⪗", NULL}, +{"emacr", "ē", NULL}, +{"empty", "∅", "∅"}, +{"emptyset", "∅", NULL}, +{"emptyv", "∅", NULL}, +{"emsp", " ", " "}, +{"emsp13", " ", NULL}, +{"emsp14", " ", NULL}, +{"eng", "ŋ", NULL}, +{"ensp", " ", " "}, +{"eogon", "ę", NULL}, +{"eopf", "𝕖", NULL}, +{"epar", "⋕", NULL}, +{"eparsl", "⧣", NULL}, +{"eplus", "⩱", NULL}, +{"epsi", "ε", NULL}, +{"epsilon", "ε", "ε"}, +{"epsiv", "ϵ", NULL}, +{"eqcirc", "≖", NULL}, +{"eqcolon", "≕", NULL}, +{"eqsim", "≂", NULL}, +{"eqslantgtr", "⪖", NULL}, +{"eqslantless", "⪕", NULL}, +{"equals", "=", NULL}, +{"equest", "≟", NULL}, +{"equiv", "≡", "≡"}, +{"equivDD", "⩸", NULL}, +{"eqvparsl", "⧥", NULL}, +{"erDot", "≓", NULL}, +{"erarr", "⥱", NULL}, +{"escr", "ℯ", NULL}, +{"esdot", "≐", NULL}, +{"esim", "≂", NULL}, +{"eta", "η", "η"}, +{"eth", "ð", "ð"}, +{"euml", "ë", "ë"}, +{"euro", "€", "€"}, +{"excl", "!", NULL}, +{"exist", "∃", "∃"}, +{"expectation", "ℰ", NULL}, +{"exponentiale", "ⅇ", NULL}, +{"fallingdotseq", "≒", NULL}, +{"fcy", "ф", NULL}, +{"female", "♀", NULL}, +{"ffilig", "ffi", NULL}, +{"fflig", "ff", NULL}, +{"ffllig", "ffl", NULL}, +{"ffr", "𝔣", NULL}, +{"filig", "fi", NULL}, +{"fjlig", "fj", NULL}, +{"flat", "♭", NULL}, +{"fllig", "fl", NULL}, +{"fltns", "▱", NULL}, +{"fnof", "ƒ", "ƒ"}, +{"fopf", "𝕗", NULL}, +{"forall", "∀", "∀"}, +{"fork", "⋔", NULL}, +{"forkv", "⫙", NULL}, +{"fpartint", "⨍", NULL}, +{"frac12", "½", "½"}, +{"frac13", "⅓", NULL}, +{"frac14", "¼", "¼"}, +{"frac15", "⅕", NULL}, +{"frac16", "⅙", NULL}, +{"frac18", "⅛", NULL}, +{"frac23", "⅔", NULL}, +{"frac25", "⅖", NULL}, +{"frac34", "¾", "¾"}, +{"frac35", "⅗", NULL}, +{"frac38", "⅜", NULL}, +{"frac45", "⅘", NULL}, +{"frac56", "⅚", NULL}, +{"frac58", "⅝", NULL}, +{"frac78", "⅞", NULL}, +{"frasl", "⁄", "⁄"}, +{"frown", "⌢", NULL}, +{"fscr", "𝒻", NULL}, +{"gE", "≧", NULL}, +{"gEl", "⪌", NULL}, +{"gacute", "ǵ", NULL}, +{"gamma", "γ", "γ"}, +{"gammad", "ϝ", NULL}, +{"gap", "⪆", NULL}, +{"gbreve", "ğ", NULL}, +{"gcirc", "ĝ", NULL}, +{"gcy", "г", NULL}, +{"gdot", "ġ", NULL}, +{"ge", "≥", "≥"}, +{"gel", "⋛", NULL}, +{"geq", "≥", NULL}, +{"geqq", "≧", NULL}, +{"geqslant", "⩾", NULL}, +{"ges", "⩾", NULL}, +{"gescc", "⪩", NULL}, +{"gesdot", "⪀", NULL}, +{"gesdoto", "⪂", NULL}, +{"gesdotol", "⪄", NULL}, +{"gesl", "⋛︀", NULL}, +{"gesles", "⪔", NULL}, +{"gfr", "𝔤", NULL}, +{"gg", "≫", NULL}, +{"ggg", "⋙", NULL}, +{"gimel", "ℷ", NULL}, +{"gjcy", "ѓ", NULL}, +{"gl", "≷", NULL}, +{"glE", "⪒", NULL}, +{"gla", "⪥", NULL}, +{"glj", "⪤", NULL}, +{"gnE", "≩", NULL}, +{"gnap", "⪊", NULL}, +{"gnapprox", "⪊", NULL}, +{"gne", "⪈", NULL}, +{"gneq", "⪈", NULL}, +{"gneqq", "≩", NULL}, +{"gnsim", "⋧", NULL}, +{"gopf", "𝕘", NULL}, +{"grave", "`", NULL}, +{"gscr", "ℊ", NULL}, +{"gsim", "≳", NULL}, +{"gsime", "⪎", NULL}, +{"gsiml", "⪐", NULL}, +{"gt", ">", ">"}, +{"gtcc", "⪧", NULL}, +{"gtcir", "⩺", NULL}, +{"gtdot", "⋗", NULL}, +{"gtlPar", "⦕", NULL}, +{"gtquest", "⩼", NULL}, +{"gtrapprox", "⪆", NULL}, +{"gtrarr", "⥸", NULL}, +{"gtrdot", "⋗", NULL}, +{"gtreqless", "⋛", NULL}, +{"gtreqqless", "⪌", NULL}, +{"gtrless", "≷", NULL}, +{"gtrsim", "≳", NULL}, +{"gvertneqq", "≩︀", NULL}, +{"gvnE", "≩︀", NULL}, +{"hArr", "⇔", "⇔"}, +{"hairsp", " ", NULL}, +{"half", "½", NULL}, +{"hamilt", "ℋ", NULL}, +{"hardcy", "ъ", NULL}, +{"harr", "↔", "↔"}, +{"harrcir", "⥈", NULL}, +{"harrw", "↭", NULL}, +{"hbar", "ℏ", NULL}, +{"hcirc", "ĥ", NULL}, +{"hearts", "♥", "♥"}, +{"heartsuit", "♥", NULL}, +{"hellip", "…", "…"}, +{"hercon", "⊹", NULL}, +{"hfr", "𝔥", NULL}, +{"hksearow", "⤥", NULL}, +{"hkswarow", "⤦", NULL}, +{"hoarr", "⇿", NULL}, +{"homtht", "∻", NULL}, +{"hookleftarrow", "↩", NULL}, +{"hookrightarrow", "↪", NULL}, +{"hopf", "𝕙", NULL}, +{"horbar", "―", NULL}, +{"hscr", "𝒽", NULL}, +{"hslash", "ℏ", NULL}, +{"hstrok", "ħ", NULL}, +{"hybull", "⁃", NULL}, +{"hyphen", "‐", NULL}, +{"iacute", "í", "í"}, +{"ic", "", NULL}, +{"icirc", "î", "î"}, +{"icy", "и", NULL}, +{"iecy", "е", NULL}, +{"iexcl", "¡", "¡"}, +{"iff", "⇔", NULL}, +{"ifr", "𝔦", NULL}, +{"igrave", "ì", "ì"}, +{"ii", "ⅈ", NULL}, +{"iiiint", "⨌", NULL}, +{"iiint", "∭", NULL}, +{"iinfin", "⧜", NULL}, +{"iiota", "℩", NULL}, +{"ijlig", "ij", NULL}, +{"imacr", "ī", NULL}, +{"image", "ℑ", "ℑ"}, +{"imagline", "ℐ", NULL}, +{"imagpart", "ℑ", NULL}, +{"imath", "ı", NULL}, +{"imof", "⊷", NULL}, +{"imped", "Ƶ", NULL}, +{"in", "∈", NULL}, +{"incare", "℅", NULL}, +{"infin", "∞", "∞"}, +{"infintie", "⧝", NULL}, +{"inodot", "ı", NULL}, +{"int", "∫", "∫"}, +{"intcal", "⊺", NULL}, +{"integers", "ℤ", NULL}, +{"intercal", "⊺", NULL}, +{"intlarhk", "⨗", NULL}, +{"intprod", "⨼", NULL}, +{"iocy", "ё", NULL}, +{"iogon", "į", NULL}, +{"iopf", "𝕚", NULL}, +{"iota", "ι", "ι"}, +{"iprod", "⨼", NULL}, +{"iquest", "¿", "¿"}, +{"iscr", "𝒾", NULL}, +{"isin", "∈", "∈"}, +{"isinE", "⋹", NULL}, +{"isindot", "⋵", NULL}, +{"isins", "⋴", NULL}, +{"isinsv", "⋳", NULL}, +{"isinv", "∈", NULL}, +{"it", "", NULL}, +{"itilde", "ĩ", NULL}, +{"iukcy", "і", NULL}, +{"iuml", "ï", "ï"}, +{"jcirc", "ĵ", NULL}, +{"jcy", "й", NULL}, +{"jfr", "𝔧", NULL}, +{"jmath", "ȷ", NULL}, +{"jopf", "𝕛", NULL}, +{"jscr", "𝒿", NULL}, +{"jsercy", "ј", NULL}, +{"jukcy", "є", NULL}, +{"kappa", "κ", "κ"}, +{"kappav", "ϰ", NULL}, +{"kcedil", "ķ", NULL}, +{"kcy", "к", NULL}, +{"kfr", "𝔨", NULL}, +{"kgreen", "ĸ", NULL}, +{"khcy", "х", NULL}, +{"kjcy", "ќ", NULL}, +{"kopf", "𝕜", NULL}, +{"kscr", "𝓀", NULL}, +{"lAarr", "⇚", NULL}, +{"lArr", "⇐", "⇐"}, +{"lAtail", "⤛", NULL}, +{"lBarr", "⤎", NULL}, +{"lE", "≦", NULL}, +{"lEg", "⪋", NULL}, +{"lHar", "⥢", NULL}, +{"lacute", "ĺ", NULL}, +{"laemptyv", "⦴", NULL}, +{"lagran", "ℒ", NULL}, +{"lambda", "λ", "λ"}, +{"lang", "⟨", "〈"}, +{"langd", "⦑", NULL}, +{"langle", "⟨", NULL}, +{"lap", "⪅", NULL}, +{"laquo", "«", "«"}, +{"larr", "←", "←"}, +{"larrb", "⇤", NULL}, +{"larrbfs", "⤟", NULL}, +{"larrfs", "⤝", NULL}, +{"larrhk", "↩", NULL}, +{"larrlp", "↫", NULL}, +{"larrpl", "⤹", NULL}, +{"larrsim", "⥳", NULL}, +{"larrtl", "↢", NULL}, +{"lat", "⪫", NULL}, +{"latail", "⤙", NULL}, +{"late", "⪭", NULL}, +{"lates", "⪭︀", NULL}, +{"lbarr", "⤌", NULL}, +{"lbbrk", "❲", NULL}, +{"lbrace", "{", NULL}, +{"lbrack", "[", NULL}, +{"lbrke", "⦋", NULL}, +{"lbrksld", "⦏", NULL}, +{"lbrkslu", "⦍", NULL}, +{"lcaron", "ľ", NULL}, +{"lcedil", "ļ", NULL}, +{"lceil", "⌈", "⌈"}, +{"lcub", "{", NULL}, +{"lcy", "л", NULL}, +{"ldca", "⤶", NULL}, +{"ldquo", "“", "“"}, +{"ldquor", "„", NULL}, +{"ldrdhar", "⥧", NULL}, +{"ldrushar", "⥋", NULL}, +{"ldsh", "↲", NULL}, +{"le", "≤", "≤"}, +{"leftarrow", "←", NULL}, +{"leftarrowtail", "↢", NULL}, +{"leftharpoondown", "↽", NULL}, +{"leftharpoonup", "↼", NULL}, +{"leftleftarrows", "⇇", NULL}, +{"leftrightarrow", "↔", NULL}, +{"leftrightarrows", "⇆", NULL}, +{"leftrightharpoons", "⇋", NULL}, +{"leftrightsquigarrow", "↭", NULL}, +{"leftthreetimes", "⋋", NULL}, +{"leg", "⋚", NULL}, +{"leq", "≤", NULL}, +{"leqq", "≦", NULL}, +{"leqslant", "⩽", NULL}, +{"les", "⩽", NULL}, +{"lescc", "⪨", NULL}, +{"lesdot", "⩿", NULL}, +{"lesdoto", "⪁", NULL}, +{"lesdotor", "⪃", NULL}, +{"lesg", "⋚︀", NULL}, +{"lesges", "⪓", NULL}, +{"lessapprox", "⪅", NULL}, +{"lessdot", "⋖", NULL}, +{"lesseqgtr", "⋚", NULL}, +{"lesseqqgtr", "⪋", NULL}, +{"lessgtr", "≶", NULL}, +{"lesssim", "≲", NULL}, +{"lfisht", "⥼", NULL}, +{"lfloor", "⌊", "⌊"}, +{"lfr", "𝔩", NULL}, +{"lg", "≶", NULL}, +{"lgE", "⪑", NULL}, +{"lhard", "↽", NULL}, +{"lharu", "↼", NULL}, +{"lharul", "⥪", NULL}, +{"lhblk", "▄", NULL}, +{"ljcy", "љ", NULL}, +{"ll", "≪", NULL}, +{"llarr", "⇇", NULL}, +{"llcorner", "⌞", NULL}, +{"llhard", "⥫", NULL}, +{"lltri", "◺", NULL}, +{"lmidot", "ŀ", NULL}, +{"lmoust", "⎰", NULL}, +{"lmoustache", "⎰", NULL}, +{"lnE", "≨", NULL}, +{"lnap", "⪉", NULL}, +{"lnapprox", "⪉", NULL}, +{"lne", "⪇", NULL}, +{"lneq", "⪇", NULL}, +{"lneqq", "≨", NULL}, +{"lnsim", "⋦", NULL}, +{"loang", "⟬", NULL}, +{"loarr", "⇽", NULL}, +{"lobrk", "⟦", NULL}, +{"longleftarrow", "⟵", NULL}, +{"longleftrightarrow", "⟷", NULL}, +{"longmapsto", "⟼", NULL}, +{"longrightarrow", "⟶", NULL}, +{"looparrowleft", "↫", NULL}, +{"looparrowright", "↬", NULL}, +{"lopar", "⦅", NULL}, +{"lopf", "𝕝", NULL}, +{"loplus", "⨭", NULL}, +{"lotimes", "⨴", NULL}, +{"lowast", "∗", "∗"}, +{"lowbar", "_", NULL}, +{"loz", "◊", "◊"}, +{"lozenge", "◊", NULL}, +{"lozf", "⧫", NULL}, +{"lpar", "(", NULL}, +{"lparlt", "⦓", NULL}, +{"lrarr", "⇆", NULL}, +{"lrcorner", "⌟", NULL}, +{"lrhar", "⇋", NULL}, +{"lrhard", "⥭", NULL}, +{"lrm", "", ""}, +{"lrtri", "⊿", NULL}, +{"lsaquo", "‹", "‹"}, +{"lscr", "𝓁", NULL}, +{"lsh", "↰", NULL}, +{"lsim", "≲", NULL}, +{"lsime", "⪍", NULL}, +{"lsimg", "⪏", NULL}, +{"lsqb", "[", NULL}, +{"lsquo", "‘", "‘"}, +{"lsquor", "‚", NULL}, +{"lstrok", "ł", NULL}, +{"lt", "<", "<"}, +{"ltcc", "⪦", NULL}, +{"ltcir", "⩹", NULL}, +{"ltdot", "⋖", NULL}, +{"lthree", "⋋", NULL}, +{"ltimes", "⋉", NULL}, +{"ltlarr", "⥶", NULL}, +{"ltquest", "⩻", NULL}, +{"ltrPar", "⦖", NULL}, +{"ltri", "◃", NULL}, +{"ltrie", "⊴", NULL}, +{"ltrif", "◂", NULL}, +{"lurdshar", "⥊", NULL}, +{"luruhar", "⥦", NULL}, +{"lvertneqq", "≨︀", NULL}, +{"lvnE", "≨︀", NULL}, +{"mDDot", "∺", NULL}, +{"macr", "¯", "¯"}, +{"male", "♂", NULL}, +{"malt", "✠", NULL}, +{"maltese", "✠", NULL}, +{"map", "↦", NULL}, +{"mapsto", "↦", NULL}, +{"mapstodown", "↧", NULL}, +{"mapstoleft", "↤", NULL}, +{"mapstoup", "↥", NULL}, +{"marker", "▮", NULL}, +{"mcomma", "⨩", NULL}, +{"mcy", "м", NULL}, +{"mdash", "—", "—"}, +{"measuredangle", "∡", NULL}, +{"mfr", "𝔪", NULL}, +{"mho", "℧", NULL}, +{"micro", "µ", "µ"}, +{"mid", "∣", NULL}, +{"midast", "*", NULL}, +{"midcir", "⫰", NULL}, +{"middot", "·", "·"}, +{"minus", "−", "−"}, +{"minusb", "⊟", NULL}, +{"minusd", "∸", NULL}, +{"minusdu", "⨪", NULL}, +{"mlcp", "⫛", NULL}, +{"mldr", "…", NULL}, +{"mnplus", "∓", NULL}, +{"models", "⊧", NULL}, +{"mopf", "𝕞", NULL}, +{"mp", "∓", NULL}, +{"mscr", "𝓂", NULL}, +{"mstpos", "∾", NULL}, +{"mu", "μ", "μ"}, +{"multimap", "⊸", NULL}, +{"mumap", "⊸", NULL}, +{"nGg", "⋙̸", NULL}, +{"nGt", "≫⃒", NULL}, +{"nGtv", "≫̸", NULL}, +{"nLeftarrow", "⇍", NULL}, +{"nLeftrightarrow", "⇎", NULL}, +{"nLl", "⋘̸", NULL}, +{"nLt", "≪⃒", NULL}, +{"nLtv", "≪̸", NULL}, +{"nRightarrow", "⇏", NULL}, +{"nVDash", "⊯", NULL}, +{"nVdash", "⊮", NULL}, +{"nabla", "∇", "∇"}, +{"nacute", "ń", NULL}, +{"nang", "∠⃒", NULL}, +{"nap", "≉", NULL}, +{"napE", "⩰̸", NULL}, +{"napid", "≋̸", NULL}, +{"napos", "ʼn", NULL}, +{"napprox", "≉", NULL}, +{"natur", "♮", NULL}, +{"natural", "♮", NULL}, +{"naturals", "ℕ", NULL}, +{"nbsp", " ", " "}, +{"nbump", "≎̸", NULL}, +{"nbumpe", "≏̸", NULL}, +{"ncap", "⩃", NULL}, +{"ncaron", "ň", NULL}, +{"ncedil", "ņ", NULL}, +{"ncong", "≇", NULL}, +{"ncongdot", "⩭̸", NULL}, +{"ncup", "⩂", NULL}, +{"ncy", "н", NULL}, +{"ndash", "–", "–"}, +{"ne", "≠", "≠"}, +{"neArr", "⇗", NULL}, +{"nearhk", "⤤", NULL}, +{"nearr", "↗", NULL}, +{"nearrow", "↗", NULL}, +{"nedot", "≐̸", NULL}, +{"nequiv", "≢", NULL}, +{"nesear", "⤨", NULL}, +{"nesim", "≂̸", NULL}, +{"nexist", "∄", NULL}, +{"nexists", "∄", NULL}, +{"nfr", "𝔫", NULL}, +{"ngE", "≧̸", NULL}, +{"nge", "≱", NULL}, +{"ngeq", "≱", NULL}, +{"ngeqq", "≧̸", NULL}, +{"ngeqslant", "⩾̸", NULL}, +{"nges", "⩾̸", NULL}, +{"ngsim", "≵", NULL}, +{"ngt", "≯", NULL}, +{"ngtr", "≯", NULL}, +{"nhArr", "⇎", NULL}, +{"nharr", "↮", NULL}, +{"nhpar", "⫲", NULL}, +{"ni", "∋", "∋"}, +{"nis", "⋼", NULL}, +{"nisd", "⋺", NULL}, +{"niv", "∋", NULL}, +{"njcy", "њ", NULL}, +{"nlArr", "⇍", NULL}, +{"nlE", "≦̸", NULL}, +{"nlarr", "↚", NULL}, +{"nldr", "‥", NULL}, +{"nle", "≰", NULL}, +{"nleftarrow", "↚", NULL}, +{"nleftrightarrow", "↮", NULL}, +{"nleq", "≰", NULL}, +{"nleqq", "≦̸", NULL}, +{"nleqslant", "⩽̸", NULL}, +{"nles", "⩽̸", NULL}, +{"nless", "≮", NULL}, +{"nlsim", "≴", NULL}, +{"nlt", "≮", NULL}, +{"nltri", "⋪", NULL}, +{"nltrie", "⋬", NULL}, +{"nmid", "∤", NULL}, +{"nopf", "𝕟", NULL}, +{"not", "¬", "¬"}, +{"notin", "∉", "∉"}, +{"notinE", "⋹̸", NULL}, +{"notindot", "⋵̸", NULL}, +{"notinva", "∉", NULL}, +{"notinvb", "⋷", NULL}, +{"notinvc", "⋶", NULL}, +{"notni", "∌", NULL}, +{"notniva", "∌", NULL}, +{"notnivb", "⋾", NULL}, +{"notnivc", "⋽", NULL}, +{"npar", "∦", NULL}, +{"nparallel", "∦", NULL}, +{"nparsl", "⫽⃥", NULL}, +{"npart", "∂̸", NULL}, +{"npolint", "⨔", NULL}, +{"npr", "⊀", NULL}, +{"nprcue", "⋠", NULL}, +{"npre", "⪯̸", NULL}, +{"nprec", "⊀", NULL}, +{"npreceq", "⪯̸", NULL}, +{"nrArr", "⇏", NULL}, +{"nrarr", "↛", NULL}, +{"nrarrc", "⤳̸", NULL}, +{"nrarrw", "↝̸", NULL}, +{"nrightarrow", "↛", NULL}, +{"nrtri", "⋫", NULL}, +{"nrtrie", "⋭", NULL}, +{"nsc", "⊁", NULL}, +{"nsccue", "⋡", NULL}, +{"nsce", "⪰̸", NULL}, +{"nscr", "𝓃", NULL}, +{"nshortmid", "∤", NULL}, +{"nshortparallel", "∦", NULL}, +{"nsim", "≁", NULL}, +{"nsime", "≄", NULL}, +{"nsimeq", "≄", NULL}, +{"nsmid", "∤", NULL}, +{"nspar", "∦", NULL}, +{"nsqsube", "⋢", NULL}, +{"nsqsupe", "⋣", NULL}, +{"nsub", "⊄", "⊄"}, +{"nsubE", "⫅̸", NULL}, +{"nsube", "⊈", NULL}, +{"nsubset", "⊂⃒", NULL}, +{"nsubseteq", "⊈", NULL}, +{"nsubseteqq", "⫅̸", NULL}, +{"nsucc", "⊁", NULL}, +{"nsucceq", "⪰̸", NULL}, +{"nsup", "⊅", NULL}, +{"nsupE", "⫆̸", NULL}, +{"nsupe", "⊉", NULL}, +{"nsupset", "⊃⃒", NULL}, +{"nsupseteq", "⊉", NULL}, +{"nsupseteqq", "⫆̸", NULL}, +{"ntgl", "≹", NULL}, +{"ntilde", "ñ", "ñ"}, +{"ntlg", "≸", NULL}, +{"ntriangleleft", "⋪", NULL}, +{"ntrianglelefteq", "⋬", NULL}, +{"ntriangleright", "⋫", NULL}, +{"ntrianglerighteq", "⋭", NULL}, +{"nu", "ν", "ν"}, +{"num", "#", NULL}, +{"numero", "№", NULL}, +{"numsp", " ", NULL}, +{"nvDash", "⊭", NULL}, +{"nvHarr", "⤄", NULL}, +{"nvap", "≍⃒", NULL}, +{"nvdash", "⊬", NULL}, +{"nvge", "≥⃒", NULL}, +{"nvgt", ">⃒", NULL}, +{"nvinfin", "⧞", NULL}, +{"nvlArr", "⤂", NULL}, +{"nvle", "≤⃒", NULL}, +{"nvlt", "<⃒", NULL}, +{"nvltrie", "⊴⃒", NULL}, +{"nvrArr", "⤃", NULL}, +{"nvrtrie", "⊵⃒", NULL}, +{"nvsim", "∼⃒", NULL}, +{"nwArr", "⇖", NULL}, +{"nwarhk", "⤣", NULL}, +{"nwarr", "↖", NULL}, +{"nwarrow", "↖", NULL}, +{"nwnear", "⤧", NULL}, +{"oS", "Ⓢ", NULL}, +{"oacute", "ó", "ó"}, +{"oast", "⊛", NULL}, +{"ocir", "⊚", NULL}, +{"ocirc", "ô", "ô"}, +{"ocy", "о", NULL}, +{"odash", "⊝", NULL}, +{"odblac", "ő", NULL}, +{"odiv", "⨸", NULL}, +{"odot", "⊙", NULL}, +{"odsold", "⦼", NULL}, +{"oelig", "œ", "œ"}, +{"ofcir", "⦿", NULL}, +{"ofr", "𝔬", NULL}, +{"ogon", "˛", NULL}, +{"ograve", "ò", "ò"}, +{"ogt", "⧁", NULL}, +{"ohbar", "⦵", NULL}, +{"ohm", "Ω", NULL}, +{"oint", "∮", NULL}, +{"olarr", "↺", NULL}, +{"olcir", "⦾", NULL}, +{"olcross", "⦻", NULL}, +{"oline", "‾", "‾"}, +{"olt", "⧀", NULL}, +{"omacr", "ō", NULL}, +{"omega", "ω", "ω"}, +{"omicron", "ο", "ο"}, +{"omid", "⦶", NULL}, +{"ominus", "⊖", NULL}, +{"oopf", "𝕠", NULL}, +{"opar", "⦷", NULL}, +{"operp", "⦹", NULL}, +{"oplus", "⊕", "⊕"}, +{"or", "∨", "∨"}, +{"orarr", "↻", NULL}, +{"ord", "⩝", NULL}, +{"order", "ℴ", NULL}, +{"orderof", "ℴ", NULL}, +{"ordf", "ª", "ª"}, +{"ordm", "º", "º"}, +{"origof", "⊶", NULL}, +{"oror", "⩖", NULL}, +{"orslope", "⩗", NULL}, +{"orv", "⩛", NULL}, +{"oscr", "ℴ", NULL}, +{"oslash", "ø", "ø"}, +{"osol", "⊘", NULL}, +{"otilde", "õ", "õ"}, +{"otimes", "⊗", "⊗"}, +{"otimesas", "⨶", NULL}, +{"ouml", "ö", "ö"}, +{"ovbar", "⌽", NULL}, +{"par", "∥", NULL}, +{"para", "¶", "¶"}, +{"parallel", "∥", NULL}, +{"parsim", "⫳", NULL}, +{"parsl", "⫽", NULL}, +{"part", "∂", "∂"}, +{"pcy", "п", NULL}, +{"percnt", "%", NULL}, +{"period", ".", NULL}, +{"permil", "‰", "‰"}, +{"perp", "⊥", "⊥"}, +{"pertenk", "‱", NULL}, +{"pfr", "𝔭", NULL}, +{"phi", "φ", "φ"}, +{"phiv", "ϕ", NULL}, +{"phmmat", "ℳ", NULL}, +{"phone", "☎", NULL}, +{"pi", "π", "π"}, +{"pitchfork", "⋔", NULL}, +{"piv", "ϖ", "ϖ"}, +{"planck", "ℏ", NULL}, +{"planckh", "ℎ", NULL}, +{"plankv", "ℏ", NULL}, +{"plus", "+", NULL}, +{"plusacir", "⨣", NULL}, +{"plusb", "⊞", NULL}, +{"pluscir", "⨢", NULL}, +{"plusdo", "∔", NULL}, +{"plusdu", "⨥", NULL}, +{"pluse", "⩲", NULL}, +{"plusmn", "±", "±"}, +{"plussim", "⨦", NULL}, +{"plustwo", "⨧", NULL}, +{"pm", "±", NULL}, +{"pointint", "⨕", NULL}, +{"popf", "𝕡", NULL}, +{"pound", "£", "£"}, +{"pr", "≺", NULL}, +{"prE", "⪳", NULL}, +{"prap", "⪷", NULL}, +{"prcue", "≼", NULL}, +{"pre", "⪯", NULL}, +{"prec", "≺", NULL}, +{"precapprox", "⪷", NULL}, +{"preccurlyeq", "≼", NULL}, +{"preceq", "⪯", NULL}, +{"precnapprox", "⪹", NULL}, +{"precneqq", "⪵", NULL}, +{"precnsim", "⋨", NULL}, +{"precsim", "≾", NULL}, +{"prime", "′", "′"}, +{"primes", "ℙ", NULL}, +{"prnE", "⪵", NULL}, +{"prnap", "⪹", NULL}, +{"prnsim", "⋨", NULL}, +{"prod", "∏", "∏"}, +{"profalar", "⌮", NULL}, +{"profline", "⌒", NULL}, +{"profsurf", "⌓", NULL}, +{"prop", "∝", "∝"}, +{"propto", "∝", NULL}, +{"prsim", "≾", NULL}, +{"prurel", "⊰", NULL}, +{"pscr", "𝓅", NULL}, +{"psi", "ψ", "ψ"}, +{"puncsp", " ", NULL}, +{"qfr", "𝔮", NULL}, +{"qint", "⨌", NULL}, +{"qopf", "𝕢", NULL}, +{"qprime", "⁗", NULL}, +{"qscr", "𝓆", NULL}, +{"quaternions", "ℍ", NULL}, +{"quatint", "⨖", NULL}, +{"quest", "?", NULL}, +{"questeq", "≟", NULL}, +{"quot", "\"", "\""}, +{"rAarr", "⇛", NULL}, +{"rArr", "⇒", "⇒"}, +{"rAtail", "⤜", NULL}, +{"rBarr", "⤏", NULL}, +{"rHar", "⥤", NULL}, +{"race", "∽̱", NULL}, +{"racute", "ŕ", NULL}, +{"radic", "√", "√"}, +{"raemptyv", "⦳", NULL}, +{"rang", "⟩", "〉"}, +{"rangd", "⦒", NULL}, +{"range", "⦥", NULL}, +{"rangle", "⟩", NULL}, +{"raquo", "»", "»"}, +{"rarr", "→", "→"}, +{"rarrap", "⥵", NULL}, +{"rarrb", "⇥", NULL}, +{"rarrbfs", "⤠", NULL}, +{"rarrc", "⤳", NULL}, +{"rarrfs", "⤞", NULL}, +{"rarrhk", "↪", NULL}, +{"rarrlp", "↬", NULL}, +{"rarrpl", "⥅", NULL}, +{"rarrsim", "⥴", NULL}, +{"rarrtl", "↣", NULL}, +{"rarrw", "↝", NULL}, +{"ratail", "⤚", NULL}, +{"ratio", "∶", NULL}, +{"rationals", "ℚ", NULL}, +{"rbarr", "⤍", NULL}, +{"rbbrk", "❳", NULL}, +{"rbrace", "}", NULL}, +{"rbrack", "]", NULL}, +{"rbrke", "⦌", NULL}, +{"rbrksld", "⦎", NULL}, +{"rbrkslu", "⦐", NULL}, +{"rcaron", "ř", NULL}, +{"rcedil", "ŗ", NULL}, +{"rceil", "⌉", "⌉"}, +{"rcub", "}", NULL}, +{"rcy", "р", NULL}, +{"rdca", "⤷", NULL}, +{"rdldhar", "⥩", NULL}, +{"rdquo", "”", "”"}, +{"rdquor", "”", NULL}, +{"rdsh", "↳", NULL}, +{"real", "ℜ", "ℜ"}, +{"realine", "ℛ", NULL}, +{"realpart", "ℜ", NULL}, +{"reals", "ℝ", NULL}, +{"rect", "▭", NULL}, +{"reg", "®", "®"}, +{"rfisht", "⥽", NULL}, +{"rfloor", "⌋", "⌋"}, +{"rfr", "𝔯", NULL}, +{"rhard", "⇁", NULL}, +{"rharu", "⇀", NULL}, +{"rharul", "⥬", NULL}, +{"rho", "ρ", "ρ"}, +{"rhov", "ϱ", NULL}, +{"rightarrow", "→", NULL}, +{"rightarrowtail", "↣", NULL}, +{"rightharpoondown", "⇁", NULL}, +{"rightharpoonup", "⇀", NULL}, +{"rightleftarrows", "⇄", NULL}, +{"rightleftharpoons", "⇌", NULL}, +{"rightrightarrows", "⇉", NULL}, +{"rightsquigarrow", "↝", NULL}, +{"rightthreetimes", "⋌", NULL}, +{"ring", "˚", NULL}, +{"risingdotseq", "≓", NULL}, +{"rlarr", "⇄", NULL}, +{"rlhar", "⇌", NULL}, +{"rlm", "", ""}, +{"rmoust", "⎱", NULL}, +{"rmoustache", "⎱", NULL}, +{"rnmid", "⫮", NULL}, +{"roang", "⟭", NULL}, +{"roarr", "⇾", NULL}, +{"robrk", "⟧", NULL}, +{"ropar", "⦆", NULL}, +{"ropf", "𝕣", NULL}, +{"roplus", "⨮", NULL}, +{"rotimes", "⨵", NULL}, +{"rpar", ")", NULL}, +{"rpargt", "⦔", NULL}, +{"rppolint", "⨒", NULL}, +{"rrarr", "⇉", NULL}, +{"rsaquo", "›", "›"}, +{"rscr", "𝓇", NULL}, +{"rsh", "↱", NULL}, +{"rsqb", "]", NULL}, +{"rsquo", "’", "’"}, +{"rsquor", "’", NULL}, +{"rthree", "⋌", NULL}, +{"rtimes", "⋊", NULL}, +{"rtri", "▹", NULL}, +{"rtrie", "⊵", NULL}, +{"rtrif", "▸", NULL}, +{"rtriltri", "⧎", NULL}, +{"ruluhar", "⥨", NULL}, +{"rx", "℞", NULL}, +{"sacute", "ś", NULL}, +{"sbquo", "‚", "‚"}, +{"sc", "≻", NULL}, +{"scE", "⪴", NULL}, +{"scap", "⪸", NULL}, +{"scaron", "š", "š"}, +{"sccue", "≽", NULL}, +{"sce", "⪰", NULL}, +{"scedil", "ş", NULL}, +{"scirc", "ŝ", NULL}, +{"scnE", "⪶", NULL}, +{"scnap", "⪺", NULL}, +{"scnsim", "⋩", NULL}, +{"scpolint", "⨓", NULL}, +{"scsim", "≿", NULL}, +{"scy", "с", NULL}, +{"sdot", "⋅", "⋅"}, +{"sdotb", "⊡", NULL}, +{"sdote", "⩦", NULL}, +{"seArr", "⇘", NULL}, +{"searhk", "⤥", NULL}, +{"searr", "↘", NULL}, +{"searrow", "↘", NULL}, +{"sect", "§", "§"}, +{"semi", ";", NULL}, +{"seswar", "⤩", NULL}, +{"setminus", "∖", NULL}, +{"setmn", "∖", NULL}, +{"sext", "✶", NULL}, +{"sfr", "𝔰", NULL}, +{"sfrown", "⌢", NULL}, +{"sharp", "♯", NULL}, +{"shchcy", "щ", NULL}, +{"shcy", "ш", NULL}, +{"shortmid", "∣", NULL}, +{"shortparallel", "∥", NULL}, +{"shy", "", ""}, +{"sigma", "σ", "σ"}, +{"sigmaf", "ς", "ς"}, +{"sigmav", "ς", NULL}, +{"sim", "∼", "∼"}, +{"simdot", "⩪", NULL}, +{"sime", "≃", NULL}, +{"simeq", "≃", NULL}, +{"simg", "⪞", NULL}, +{"simgE", "⪠", NULL}, +{"siml", "⪝", NULL}, +{"simlE", "⪟", NULL}, +{"simne", "≆", NULL}, +{"simplus", "⨤", NULL}, +{"simrarr", "⥲", NULL}, +{"slarr", "←", NULL}, +{"smallsetminus", "∖", NULL}, +{"smashp", "⨳", NULL}, +{"smeparsl", "⧤", NULL}, +{"smid", "∣", NULL}, +{"smile", "⌣", NULL}, +{"smt", "⪪", NULL}, +{"smte", "⪬", NULL}, +{"smtes", "⪬︀", NULL}, +{"softcy", "ь", NULL}, +{"sol", "/", NULL}, +{"solb", "⧄", NULL}, +{"solbar", "⌿", NULL}, +{"sopf", "𝕤", NULL}, +{"spades", "♠", "♠"}, +{"spadesuit", "♠", NULL}, +{"spar", "∥", NULL}, +{"sqcap", "⊓", NULL}, +{"sqcaps", "⊓︀", NULL}, +{"sqcup", "⊔", NULL}, +{"sqcups", "⊔︀", NULL}, +{"sqsub", "⊏", NULL}, +{"sqsube", "⊑", NULL}, +{"sqsubset", "⊏", NULL}, +{"sqsubseteq", "⊑", NULL}, +{"sqsup", "⊐", NULL}, +{"sqsupe", "⊒", NULL}, +{"sqsupset", "⊐", NULL}, +{"sqsupseteq", "⊒", NULL}, +{"squ", "□", NULL}, +{"square", "□", NULL}, +{"squarf", "▪", NULL}, +{"squf", "▪", NULL}, +{"srarr", "→", NULL}, +{"sscr", "𝓈", NULL}, +{"ssetmn", "∖", NULL}, +{"ssmile", "⌣", NULL}, +{"sstarf", "⋆", NULL}, +{"star", "☆", NULL}, +{"starf", "★", NULL}, +{"straightepsilon", "ϵ", NULL}, +{"straightphi", "ϕ", NULL}, +{"strns", "¯", NULL}, +{"sub", "⊂", "⊂"}, +{"subE", "⫅", NULL}, +{"subdot", "⪽", NULL}, +{"sube", "⊆", "⊆"}, +{"subedot", "⫃", NULL}, +{"submult", "⫁", NULL}, +{"subnE", "⫋", NULL}, +{"subne", "⊊", NULL}, +{"subplus", "⪿", NULL}, +{"subrarr", "⥹", NULL}, +{"subset", "⊂", NULL}, +{"subseteq", "⊆", NULL}, +{"subseteqq", "⫅", NULL}, +{"subsetneq", "⊊", NULL}, +{"subsetneqq", "⫋", NULL}, +{"subsim", "⫇", NULL}, +{"subsub", "⫕", NULL}, +{"subsup", "⫓", NULL}, +{"succ", "≻", NULL}, +{"succapprox", "⪸", NULL}, +{"succcurlyeq", "≽", NULL}, +{"succeq", "⪰", NULL}, +{"succnapprox", "⪺", NULL}, +{"succneqq", "⪶", NULL}, +{"succnsim", "⋩", NULL}, +{"succsim", "≿", NULL}, +{"sum", "∑", "∑"}, +{"sung", "♪", NULL}, +{"sup", "⊃", "⊃"}, +{"sup1", "¹", "¹"}, +{"sup2", "²", "²"}, +{"sup3", "³", "³"}, +{"supE", "⫆", NULL}, +{"supdot", "⪾", NULL}, +{"supdsub", "⫘", NULL}, +{"supe", "⊇", "⊇"}, +{"supedot", "⫄", NULL}, +{"suphsol", "⟉", NULL}, +{"suphsub", "⫗", NULL}, +{"suplarr", "⥻", NULL}, +{"supmult", "⫂", NULL}, +{"supnE", "⫌", NULL}, +{"supne", "⊋", NULL}, +{"supplus", "⫀", NULL}, +{"supset", "⊃", NULL}, +{"supseteq", "⊇", NULL}, +{"supseteqq", "⫆", NULL}, +{"supsetneq", "⊋", NULL}, +{"supsetneqq", "⫌", NULL}, +{"supsim", "⫈", NULL}, +{"supsub", "⫔", NULL}, +{"supsup", "⫖", NULL}, +{"swArr", "⇙", NULL}, +{"swarhk", "⤦", NULL}, +{"swarr", "↙", NULL}, +{"swarrow", "↙", NULL}, +{"swnwar", "⤪", NULL}, +{"szlig", "ß", "ß"}, +{"target", "⌖", NULL}, +{"tau", "τ", "τ"}, +{"tbrk", "⎴", NULL}, +{"tcaron", "ť", NULL}, +{"tcedil", "ţ", NULL}, +{"tcy", "т", NULL}, +{"tdot", "⃛", NULL}, +{"telrec", "⌕", NULL}, +{"tfr", "𝔱", NULL}, +{"there4", "∴", "∴"}, +{"therefore", "∴", NULL}, +{"theta", "θ", "θ"}, +{"thetasym", "ϑ", "ϑ"}, +{"thetav", "ϑ", NULL}, +{"thickapprox", "≈", NULL}, +{"thicksim", "∼", NULL}, +{"thinsp", " ", " "}, +{"thkap", "≈", NULL}, +{"thksim", "∼", NULL}, +{"thorn", "þ", "þ"}, +{"tilde", "˜", "˜"}, +{"times", "×", "×"}, +{"timesb", "⊠", NULL}, +{"timesbar", "⨱", NULL}, +{"timesd", "⨰", NULL}, +{"tint", "∭", NULL}, +{"toea", "⤨", NULL}, +{"top", "⊤", NULL}, +{"topbot", "⌶", NULL}, +{"topcir", "⫱", NULL}, +{"topf", "𝕥", NULL}, +{"topfork", "⫚", NULL}, +{"tosa", "⤩", NULL}, +{"tprime", "‴", NULL}, +{"trade", "™", "™"}, +{"triangle", "▵", NULL}, +{"triangledown", "▿", NULL}, +{"triangleleft", "◃", NULL}, +{"trianglelefteq", "⊴", NULL}, +{"triangleq", "≜", NULL}, +{"triangleright", "▹", NULL}, +{"trianglerighteq", "⊵", NULL}, +{"tridot", "◬", NULL}, +{"trie", "≜", NULL}, +{"triminus", "⨺", NULL}, +{"triplus", "⨹", NULL}, +{"trisb", "⧍", NULL}, +{"tritime", "⨻", NULL}, +{"trpezium", "⏢", NULL}, +{"tscr", "𝓉", NULL}, +{"tscy", "ц", NULL}, +{"tshcy", "ћ", NULL}, +{"tstrok", "ŧ", NULL}, +{"twixt", "≬", NULL}, +{"twoheadleftarrow", "↞", NULL}, +{"twoheadrightarrow", "↠", NULL}, +{"uArr", "⇑", "⇑"}, +{"uHar", "⥣", NULL}, +{"uacute", "ú", "ú"}, +{"uarr", "↑", "↑"}, +{"ubrcy", "ў", NULL}, +{"ubreve", "ŭ", NULL}, +{"ucirc", "û", "û"}, +{"ucy", "у", NULL}, +{"udarr", "⇅", NULL}, +{"udblac", "ű", NULL}, +{"udhar", "⥮", NULL}, +{"ufisht", "⥾", NULL}, +{"ufr", "𝔲", NULL}, +{"ugrave", "ù", "ù"}, +{"uharl", "↿", NULL}, +{"uharr", "↾", NULL}, +{"uhblk", "▀", NULL}, +{"ulcorn", "⌜", NULL}, +{"ulcorner", "⌜", NULL}, +{"ulcrop", "⌏", NULL}, +{"ultri", "◸", NULL}, +{"umacr", "ū", NULL}, +{"uml", "¨", "¨"}, +{"uogon", "ų", NULL}, +{"uopf", "𝕦", NULL}, +{"uparrow", "↑", NULL}, +{"updownarrow", "↕", NULL}, +{"upharpoonleft", "↿", NULL}, +{"upharpoonright", "↾", NULL}, +{"uplus", "⊎", NULL}, +{"upsi", "υ", NULL}, +{"upsih", "ϒ", "ϒ"}, +{"upsilon", "υ", "υ"}, +{"upuparrows", "⇈", NULL}, +{"urcorn", "⌝", NULL}, +{"urcorner", "⌝", NULL}, +{"urcrop", "⌎", NULL}, +{"uring", "ů", NULL}, +{"urtri", "◹", NULL}, +{"uscr", "𝓊", NULL}, +{"utdot", "⋰", NULL}, +{"utilde", "ũ", NULL}, +{"utri", "▵", NULL}, +{"utrif", "▴", NULL}, +{"uuarr", "⇈", NULL}, +{"uuml", "ü", "ü"}, +{"uwangle", "⦧", NULL}, +{"vArr", "⇕", NULL}, +{"vBar", "⫨", NULL}, +{"vBarv", "⫩", NULL}, +{"vDash", "⊨", NULL}, +{"vangrt", "⦜", NULL}, +{"varepsilon", "ϵ", NULL}, +{"varkappa", "ϰ", NULL}, +{"varnothing", "∅", NULL}, +{"varphi", "ϕ", NULL}, +{"varpi", "ϖ", NULL}, +{"varpropto", "∝", NULL}, +{"varr", "↕", NULL}, +{"varrho", "ϱ", NULL}, +{"varsigma", "ς", NULL}, +{"varsubsetneq", "⊊︀", NULL}, +{"varsubsetneqq", "⫋︀", NULL}, +{"varsupsetneq", "⊋︀", NULL}, +{"varsupsetneqq", "⫌︀", NULL}, +{"vartheta", "ϑ", NULL}, +{"vartriangleleft", "⊲", NULL}, +{"vartriangleright", "⊳", NULL}, +{"vcy", "в", NULL}, +{"vdash", "⊢", NULL}, +{"vee", "∨", NULL}, +{"veebar", "⊻", NULL}, +{"veeeq", "≚", NULL}, +{"vellip", "⋮", NULL}, +{"verbar", "|", NULL}, +{"vert", "|", NULL}, +{"vfr", "𝔳", NULL}, +{"vltri", "⊲", NULL}, +{"vnsub", "⊂⃒", NULL}, +{"vnsup", "⊃⃒", NULL}, +{"vopf", "𝕧", NULL}, +{"vprop", "∝", NULL}, +{"vrtri", "⊳", NULL}, +{"vscr", "𝓋", NULL}, +{"vsubnE", "⫋︀", NULL}, +{"vsubne", "⊊︀", NULL}, +{"vsupnE", "⫌︀", NULL}, +{"vsupne", "⊋︀", NULL}, +{"vzigzag", "⦚", NULL}, +{"wcirc", "ŵ", NULL}, +{"wedbar", "⩟", NULL}, +{"wedge", "∧", NULL}, +{"wedgeq", "≙", NULL}, +{"weierp", "℘", "℘"}, +{"wfr", "𝔴", NULL}, +{"wopf", "𝕨", NULL}, +{"wp", "℘", NULL}, +{"wr", "≀", NULL}, +{"wreath", "≀", NULL}, +{"wscr", "𝓌", NULL}, +{"xcap", "⋂", NULL}, +{"xcirc", "◯", NULL}, +{"xcup", "⋃", NULL}, +{"xdtri", "▽", NULL}, +{"xfr", "𝔵", NULL}, +{"xhArr", "⟺", NULL}, +{"xharr", "⟷", NULL}, +{"xi", "ξ", "ξ"}, +{"xlArr", "⟸", NULL}, +{"xlarr", "⟵", NULL}, +{"xmap", "⟼", NULL}, +{"xnis", "⋻", NULL}, +{"xodot", "⨀", NULL}, +{"xopf", "𝕩", NULL}, +{"xoplus", "⨁", NULL}, +{"xotime", "⨂", NULL}, +{"xrArr", "⟹", NULL}, +{"xrarr", "⟶", NULL}, +{"xscr", "𝓍", NULL}, +{"xsqcup", "⨆", NULL}, +{"xuplus", "⨄", NULL}, +{"xutri", "△", NULL}, +{"xvee", "⋁", NULL}, +{"xwedge", "⋀", NULL}, +{"yacute", "ý", "ý"}, +{"yacy", "я", NULL}, +{"ycirc", "ŷ", NULL}, +{"ycy", "ы", NULL}, +{"yen", "¥", "¥"}, +{"yfr", "𝔶", NULL}, +{"yicy", "ї", NULL}, +{"yopf", "𝕪", NULL}, +{"yscr", "𝓎", NULL}, +{"yucy", "ю", NULL}, +{"yuml", "ÿ", "ÿ"}, +{"zacute", "ź", NULL}, +{"zcaron", "ž", NULL}, +{"zcy", "з", NULL}, +{"zdot", "ż", NULL}, +{"zeetrf", "ℨ", NULL}, +{"zeta", "ζ", "ζ"}, +{"zfr", "𝔷", NULL}, +{"zhcy", "ж", NULL}, +{"zigrarr", "⇝", NULL}, +{"zopf", "𝕫", NULL}, +{"zscr", "𝓏", NULL}, +{"zwj", "", ""}, +{"zwnj", "", ""}, +}; +#endif /* HTML_CHARREFS_H */ diff --git a/src/html_common.hh b/src/html_common.hh index 147807b3..68ed0d08 100644 --- a/src/html_common.hh +++ b/src/html_common.hh @@ -156,8 +156,7 @@ public: //BUG: for now everything is public char *content_type, *charset; bool stop_parser; - size_t CurrTagOfs; - size_t OldTagOfs, OldTagLine; + size_t CurrOfs, OldOfs, OldLine; DilloHtmlDocumentType DocType; /* as given by DOCTYPE tag */ float DocTypeVersion; /* HTML or XHTML version number */ @@ -211,7 +210,7 @@ public: void bugMessage(const char *format, ... ); void connectSignals(dw::core::Widget *dw); void write(char *Buf, int BufSize, int Eof); - int getCurTagLineNumber(); + int getCurrLineNumber(); void finishParsing(int ClientKey); int formNew(DilloHtmlMethod method, const DilloUrl *action, DilloHtmlEnc enc, const char *charset); diff --git a/src/image.cc b/src/image.cc index 9915023a..97270eef 100644 --- a/src/image.cc +++ b/src/image.cc @@ -106,9 +106,11 @@ void a_Image_set_parms(DilloImage *Image, void *v_imgbuf, DilloUrl *url, int version, uint_t width, uint_t height, DilloImgType type) { - _MSG("a_Image_set_parms: width=%d height=%d\n", width, height); + _MSG("a_Image_set_parms: width=%d height=%d iw=%d ih=%d\n", + width, height, Image->width, Image->height); - bool resize = (Image->width != width || Image->height != height); + /* Resize from 0,0 to width,height */ + bool resize = true; I2IR(Image)->setBuffer((Imgbuf*)v_imgbuf, resize); if (!Image->BitVec) @@ -304,6 +304,7 @@ static void Jpeg_write(DilloJpeg *jpeg, void *Buf, uint_t BufSize) (uint_t)jpeg->cinfo.image_width, (uint_t)jpeg->cinfo.image_height, type, 1 / 2.2); + jpeg->Image = NULL; /* safeguard: may be freed by its owner later */ /* decompression step 4 (see libjpeg.doc) */ jpeg->state = DILLO_JPEG_STARTING; diff --git a/src/klist.c b/src/klist.c index 813269a3..e5e695e2 100644 --- a/src/klist.c +++ b/src/klist.c @@ -74,7 +74,7 @@ int a_Klist_insert(Klist_t **Klist, void *Data) a_Klist_get_data((*Klist), (*Klist)->Counter)); Node = dNew(KlistNode_t, 1); - Node->Key = (*Klist)->Counter; + Node->Key = (*Klist)->Counter; Node->Data = Data; dList_insert_sorted((*Klist)->List, Node, Klist_node_by_node_cmp); return (*Klist)->Counter; diff --git a/src/menu.cc b/src/menu.cc index b93106e1..e86c3a06 100644 --- a/src/menu.cc +++ b/src/menu.cc @@ -232,17 +232,31 @@ static void Menu_stylesheet_cb(Fl_Widget*, void *vUrl) } } +static void Menu_bugmeter_validate(const char *validator_url) +{ + if (popup_url && + dStrAsciiCasecmp(URL_SCHEME(popup_url), "dpi")) { + const char *popup_str = URL_STR(popup_url), + *ptr = strrchr(popup_str, '#'); + char *no_fragment = ptr ? dStrndup(popup_str, ptr - popup_str) + : dStrdup(popup_str); + char *encoded = a_Url_encode_hex_str(no_fragment); + Dstr *dstr = dStr_sized_new(128); + + dStr_sprintf(dstr, validator_url, encoded); + a_UIcmd_open_urlstr(popup_bw, dstr->str); + dStr_free(dstr, 1); + dFree(encoded); + dFree(no_fragment); + } +} + /* * Validate URL with the W3C */ static void Menu_bugmeter_validate_w3c_cb(Fl_Widget*, void*) { - Dstr *dstr = dStr_sized_new(128); - - dStr_sprintf(dstr, "http://validator.w3.org/check?uri=%s", - URL_STR(popup_url)); - a_UIcmd_open_urlstr(popup_bw, dstr->str); - dStr_free(dstr, 1); + Menu_bugmeter_validate("http://validator.w3.org/check?uri=%s"); } /* @@ -250,13 +264,8 @@ static void Menu_bugmeter_validate_w3c_cb(Fl_Widget*, void*) */ static void Menu_bugmeter_validate_wdg_cb(Fl_Widget*, void*) { - Dstr *dstr = dStr_sized_new(128); - - dStr_sprintf(dstr, - "http://www.htmlhelp.org/cgi-bin/validate.cgi?url=%s&warnings=yes", - URL_STR(popup_url)); - a_UIcmd_open_urlstr(popup_bw, dstr->str); - dStr_free(dstr, 1); + Menu_bugmeter_validate( + "http://www.htmlhelp.org/cgi-bin/validate.cgi?url=%s&warnings=yes"); } /* diff --git a/src/paths.cc b/src/paths.cc index 32478728..bb233de8 100644 --- a/src/paths.cc +++ b/src/paths.cc @@ -38,16 +38,16 @@ void Paths::init(void) oldWorkingDir = dGetcwd(); rc = chdir("/tmp"); if (rc == -1) { - MSG("paths: error changing directory to /tmp: %s\n", + MSG("paths: Error changing directory to /tmp: %s\n", dStrerror(errno)); } path = dStrconcat(dGethomedir(), "/.dillo", NULL); if (stat(path, &st) == -1) { if (errno == ENOENT) { - MSG("paths: creating directory %s.\n", path); + MSG("paths: Creating directory '%s/'\n", path); if (mkdir(path, 0700) < 0) { - MSG("paths: error creating directory %s: %s\n", + MSG("paths: Error creating directory %s: %s\n", path, dStrerror(errno)); } } else { @@ -62,6 +62,7 @@ typedef struct { DilloImage *Image; /* Image meta data */ DilloUrl *url; /* Primary Key for the dicache */ int version; /* Secondary Key for the dicache */ + int bgcolor; /* Parent widget background color */ png_uint_32 width; /* png image width */ png_uint_32 height; /* png image height */ @@ -102,8 +103,8 @@ void Png_error_handling(png_structp png_ptr, png_const_charp msg) { DilloPng *png; - MSG("Png_error_handling: %s\n", msg); png = png_get_error_ptr(png_ptr); + MSG("Png_error_handling: %s: %s\n", URL_STR(png->url), msg); png->error = 1; png->state = IS_finished; @@ -204,6 +205,7 @@ Png_datainfo_callback(png_structp png_ptr, png_infop info_ptr) a_Dicache_set_parms(png->url, png->version, png->Image, (uint_t)png->width, (uint_t)png->height, DILLO_IMG_TYPE_RGB, file_gamma); + png->Image = NULL; /* safeguard: hereafter it may be freed by its owner */ } static void @@ -244,9 +246,9 @@ static void /* TODO: maybe change prefs.bg_color to `a_Dw_widget_get_bg_color`, * when background colors are correctly implementated */ - bg_blue = (png->Image->bg_color) & 0xFF; - bg_green = (png->Image->bg_color>>8) & 0xFF; - bg_red = (png->Image->bg_color>>16) & 0xFF; + bg_blue = (png->bgcolor) & 0xFF; + bg_green = (png->bgcolor>>8) & 0xFF; + bg_red = (png->bgcolor>>16) & 0xFF; for (i = 0; i < png->width; i++) { a = *(data+3); @@ -433,6 +435,7 @@ void *a_Png_new(DilloImage *Image, DilloUrl *url, int version) png->Image = Image; png->url = url; png->version = version; + png->bgcolor = Image->bg_color; png->error = 0; png->ipbuf = NULL; png->ipbufstart = 0; diff --git a/src/prefs.c b/src/prefs.c index fbd17f33..abcbfcd0 100644 --- a/src/prefs.c +++ b/src/prefs.c @@ -63,10 +63,13 @@ void a_Prefs_init(void) prefs.http_language = NULL; prefs.http_proxy = NULL; prefs.http_max_conns = 6; + prefs.http_persistent_conns = FALSE; prefs.http_proxyuser = NULL; prefs.http_referer = dStrdup(PREFS_HTTP_REFERER); prefs.http_user_agent = dStrdup(PREFS_HTTP_USER_AGENT); prefs.limit_text_width = FALSE; + prefs.adjust_min_width = FALSE; + prefs.adjust_table_min_width = TRUE; prefs.load_images=TRUE; prefs.load_background_images=FALSE; prefs.load_stylesheets=TRUE; diff --git a/src/prefs.h b/src/prefs.h index bb97651e..ac52786e 100644 --- a/src/prefs.h +++ b/src/prefs.h @@ -66,6 +66,8 @@ typedef struct { int panel_size; bool_t small_icons; bool_t limit_text_width; + bool_t adjust_min_width; + bool_t adjust_table_min_width; bool_t w3c_plus_heuristics; bool_t focus_new_tab; double font_factor; @@ -91,6 +93,7 @@ typedef struct { bool_t load_background_images; bool_t load_stylesheets; bool_t parse_embedded_css; + bool_t http_persistent_conns; int32_t buffered_drawing; char *font_serif; char *font_sans_serif; diff --git a/src/prefsparser.cc b/src/prefsparser.cc index 81b097e7..d54d017b 100644 --- a/src/prefsparser.cc +++ b/src/prefsparser.cc @@ -73,11 +73,14 @@ int PrefsParser::parseOption(char *name, char *value) { "home", &prefs.home, PREFS_URL }, { "http_language", &prefs.http_language, PREFS_STRING }, { "http_max_conns", &prefs.http_max_conns, PREFS_INT32 }, + { "http_persistent_conns", &prefs.http_persistent_conns, PREFS_BOOL }, { "http_proxy", &prefs.http_proxy, PREFS_URL }, { "http_proxyuser", &prefs.http_proxyuser, PREFS_STRING }, { "http_referer", &prefs.http_referer, PREFS_STRING }, { "http_user_agent", &prefs.http_user_agent, PREFS_STRING }, { "limit_text_width", &prefs.limit_text_width, PREFS_BOOL }, + { "adjust_min_width", &prefs.adjust_min_width, PREFS_BOOL }, + { "adjust_table_min_width", &prefs.adjust_table_min_width, PREFS_BOOL }, { "load_images", &prefs.load_images, PREFS_BOOL }, { "load_background_images", &prefs.load_background_images, PREFS_BOOL }, { "load_stylesheets", &prefs.load_stylesheets, PREFS_BOOL }, diff --git a/src/styleengine.cc b/src/styleengine.cc index e07ab3d5..98b02b69 100644 --- a/src/styleengine.cc +++ b/src/styleengine.cc @@ -71,6 +71,7 @@ StyleEngine::StyleEngine (dw::core::Layout *layout, this->pageUrl = pageUrl ? a_Url_dup(pageUrl) : NULL; this->baseUrl = baseUrl ? a_Url_dup(baseUrl) : NULL; importDepth = 0; + dpmm = layout->dpiX () / 25.4; /* assume dpiX == dpiY */ stackPush (); Node *n = stack->getLastRef (); @@ -112,7 +113,7 @@ StyleEngine::~StyleEngine () { void StyleEngine::stackPush () { static const Node emptyNode = { - NULL, NULL, NULL, NULL, NULL, NULL, false, NULL + NULL, NULL, NULL, NULL, NULL, NULL, false, false, NULL }; stack->setSize (stack->size () + 1, emptyNode); @@ -145,6 +146,8 @@ void StyleEngine::startElement (int element, BrowserWindow *bw) { dn->element = element; n->doctreeNode = dn; + if (stack->size () > 1) + n->displayNone = stack->getRef (stack->size () - 2)->displayNone; } void StyleEngine::startElement (const char *tagname, BrowserWindow *bw) { @@ -152,7 +155,7 @@ void StyleEngine::startElement (const char *tagname, BrowserWindow *bw) { } void StyleEngine::setId (const char *id) { - DoctreeNode *dn = doctree->top (); + DoctreeNode *dn = doctree->top (); assert (dn->id == NULL); dn->id = dStrdup (id); } @@ -364,8 +367,8 @@ void StyleEngine::apply (int i, StyleAttrs *attrs, CssPropertyList *props, DilloUrl *imgUrl = NULL; /* Determine font first so it can be used to resolve relative lengths. */ - for (int i = 0; i < props->size (); i++) { - CssProperty *p = props->getRef (i); + for (int j = 0; j < props->size (); j++) { + CssProperty *p = props->getRef (j); switch (p->name) { case CSS_PROPERTY_FONT_FAMILY: @@ -512,8 +515,8 @@ void StyleEngine::apply (int i, StyleAttrs *attrs, CssPropertyList *props, attrs->font = Font::create (layout, &fontAttrs); - for (int i = 0; i < props->size (); i++) { - CssProperty *p = props->getRef (i); + for (int j = 0; j < props->size (); j++) { + CssProperty *p = props->getRef (j); switch (p->name) { /* \todo missing cases */ @@ -588,6 +591,12 @@ void StyleEngine::apply (int i, StyleAttrs *attrs, CssPropertyList *props, computeValue (&attrs->hBorderSpacing, p->value.intVal,attrs->font); computeValue (&attrs->vBorderSpacing, p->value.intVal,attrs->font); break; + case CSS_PROPERTY_BOTTOM: + computeLength (&attrs->bottom, p->value.intVal, attrs->font); + break; + case CSS_PROPERTY_CLEAR: + attrs->clear = (ClearType) p->value.intVal; + break; case CSS_PROPERTY_COLOR: attrs->color = Color::create (layout, p->value.intVal); break; @@ -596,6 +605,14 @@ void StyleEngine::apply (int i, StyleAttrs *attrs, CssPropertyList *props, break; case CSS_PROPERTY_DISPLAY: attrs->display = (DisplayType) p->value.intVal; + if (attrs->display == DISPLAY_NONE) + stack->getRef (i)->displayNone = true; + break; + case CSS_PROPERTY_FLOAT: + attrs->vloat = (FloatType) p->value.intVal; + break; + case CSS_PROPERTY_LEFT: + computeLength (&attrs->left, p->value.intVal, attrs->font); break; case CSS_PROPERTY_LINE_HEIGHT: if (p->type == CSS_TYPE_ENUM) { //only valid enum value is "normal" @@ -636,6 +653,9 @@ void StyleEngine::apply (int i, StyleAttrs *attrs, CssPropertyList *props, if (attrs->margin.top < 0) // \todo fix negative margins in dw/* attrs->margin.top = 0; break; + case CSS_PROPERTY_OVERFLOW: + attrs->overflow = (Overflow) p->value.intVal; + break; case CSS_PROPERTY_PADDING_TOP: computeValue (&attrs->padding.top, p->value.intVal, attrs->font); break; @@ -648,6 +668,12 @@ void StyleEngine::apply (int i, StyleAttrs *attrs, CssPropertyList *props, case CSS_PROPERTY_PADDING_RIGHT: computeValue (&attrs->padding.right, p->value.intVal, attrs->font); break; + case CSS_PROPERTY_POSITION: + attrs->position = (Position) p->value.intVal; + break; + case CSS_PROPERTY_RIGHT: + computeLength (&attrs->right, p->value.intVal, attrs->font); + break; case CSS_PROPERTY_TEXT_ALIGN: attrs->textAlign = (TextAlignType) p->value.intVal; break; @@ -660,6 +686,9 @@ void StyleEngine::apply (int i, StyleAttrs *attrs, CssPropertyList *props, case CSS_PROPERTY_TEXT_TRANSFORM: attrs->textTransform = (TextTransform) p->value.intVal; break; + case CSS_PROPERTY_TOP: + computeLength (&attrs->top, p->value.intVal, attrs->font); + break; case CSS_PROPERTY_VERTICAL_ALIGN: attrs->valign = (VAlignType) p->value.intVal; break; @@ -687,6 +716,18 @@ void StyleEngine::apply (int i, StyleAttrs *attrs, CssPropertyList *props, else if (attrs->wordSpacing < -1000) attrs->wordSpacing = -1000; break; + case CSS_PROPERTY_MIN_WIDTH: + computeLength (&attrs->minWidth, p->value.intVal, attrs->font); + break; + case CSS_PROPERTY_MAX_WIDTH: + computeLength (&attrs->maxWidth, p->value.intVal, attrs->font); + break; + case CSS_PROPERTY_MIN_HEIGHT: + computeLength (&attrs->minHeight, p->value.intVal, attrs->font); + break; + case CSS_PROPERTY_MAX_HEIGHT: + computeLength (&attrs->maxHeight, p->value.intVal, attrs->font); + break; case PROPERTY_X_LINK: attrs->x_link = p->value.intVal; break; @@ -709,7 +750,7 @@ void StyleEngine::apply (int i, StyleAttrs *attrs, CssPropertyList *props, } if (imgUrl && prefs.load_background_images && - attrs->display != DISPLAY_NONE && + !stack->getRef (i)->displayNone && !(URL_FLAGS(pageUrl) & URL_SpamSafe)) { attrs->backgroundImage = StyleImage::create(); @@ -741,11 +782,6 @@ void StyleEngine::apply (int i, StyleAttrs *attrs, CssPropertyList *props, * \brief Resolve relative lengths to absolute values. */ bool StyleEngine::computeValue (int *dest, CssLength value, Font *font) { - static float dpmm; - - if (dpmm == 0.0) - dpmm = layout->dpiX () / 25.4; /* assume dpiX == dpiY */ - switch (CSS_LENGTH_TYPE (value)) { case CSS_LENGTH_TYPE_PX: *dest = (int) CSS_LENGTH_VALUE (value); diff --git a/src/styleengine.hh b/src/styleengine.hh index 41f892d7..db3e3b85 100644 --- a/src/styleengine.hh +++ b/src/styleengine.hh @@ -27,6 +27,7 @@ class StyleEngine { dw::core::style::Style *wordStyle; dw::core::style::Style *backgroundStyle; bool inheritBackgroundColor; + bool displayNone; DoctreeNode *doctreeNode; }; @@ -35,6 +36,7 @@ class StyleEngine { CssContext *cssContext; Doctree *doctree; int importDepth; + float dpmm; DilloUrl *pageUrl, *baseUrl; void stackPush (); diff --git a/src/table.cc b/src/table.cc index a3002ebf..188becbc 100644 --- a/src/table.cc +++ b/src/table.cc @@ -15,6 +15,7 @@ #include "dw/style.hh" #include "dw/textblock.hh" #include "dw/table.hh" +#include "dw/simpletablecell.hh" #include "prefs.h" #include "msg.h" @@ -48,13 +49,13 @@ void Html_tag_open_table(DilloHtml *html, const char *tag, int tagsize) if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "cellspacing"))) { cellspacing = strtol (attrbuf, NULL, 10); if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) - BUG_MSG("<table> cellspacing attribute is obsolete.\n"); + BUG_MSG("<table> cellspacing attribute is obsolete."); } if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "cellpadding"))) { cellpadding = strtol (attrbuf, NULL, 10); if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) - BUG_MSG("<table> cellpadding attribute is obsolete.\n"); + BUG_MSG("<table> cellpadding attribute is obsolete."); } if (border != -1) { @@ -88,7 +89,7 @@ void Html_tag_open_table(DilloHtml *html, const char *tag, int tagsize) CSS_TYPE_LENGTH_PERCENTAGE, a_Html_parse_length (html, attrbuf)); if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) - BUG_MSG("<table> width attribute is obsolete.\n"); + BUG_MSG("<table> width attribute is obsolete."); } if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "align"))) { @@ -102,7 +103,7 @@ void Html_tag_open_table(DilloHtml *html, const char *tag, int tagsize) html->styleEngine->setNonCssHint (CSS_PROPERTY_TEXT_ALIGN, CSS_TYPE_ENUM, TEXT_ALIGN_CENTER); if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) - BUG_MSG("<table> align attribute is obsolete.\n"); + BUG_MSG("<table> align attribute is obsolete."); } if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "bgcolor"))) { @@ -111,7 +112,7 @@ void Html_tag_open_table(DilloHtml *html, const char *tag, int tagsize) html->styleEngine->setNonCssHint (CSS_PROPERTY_BACKGROUND_COLOR, CSS_TYPE_COLOR, bgcolor); if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) - BUG_MSG("<table> bgcolor attribute is obsolete.\n"); + BUG_MSG("<table> bgcolor attribute is obsolete."); } html->style (); // evaluate now, so we can build non-css hints for the cells @@ -192,7 +193,7 @@ void Html_tag_open_tr(DilloHtml *html, const char *tag, int tagsize) html->styleEngine->setNonCssHint (CSS_PROPERTY_BACKGROUND_COLOR, CSS_TYPE_COLOR, bgcolor); if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) - BUG_MSG("<tr> bgcolor attribute is obsolete.\n"); + BUG_MSG("<tr> bgcolor attribute is obsolete."); } if (a_Html_get_attr (html, tag, tagsize, "align")) { @@ -379,7 +380,7 @@ static void Html_tag_open_table_cell(DilloHtml *html, } if (a_Html_get_attr(html, tag, tagsize, "nowrap")) { if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) - BUG_MSG("<t%c> nowrap attribute is obsolete.\n", + BUG_MSG("<t%c> nowrap attribute is obsolete.", (tagsize >=3 && (D_ASCII_TOLOWER(tag[2]) == 'd')) ? 'd' : 'h'); html->styleEngine->setNonCssHint(CSS_PROPERTY_WHITE_SPACE, CSS_TYPE_ENUM, WHITE_SPACE_NOWRAP); @@ -392,7 +393,7 @@ static void Html_tag_open_table_cell(DilloHtml *html, CSS_TYPE_LENGTH_PERCENTAGE, a_Html_parse_length (html, attrbuf)); if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) - BUG_MSG("<t%c> width attribute is obsolete.\n", + BUG_MSG("<t%c> width attribute is obsolete.", (tagsize >=3 && (D_ASCII_TOLOWER(tag[2]) == 'd')) ? 'd' : 'h'); } @@ -404,7 +405,7 @@ static void Html_tag_open_table_cell(DilloHtml *html, html->styleEngine->setNonCssHint (CSS_PROPERTY_BACKGROUND_COLOR, CSS_TYPE_COLOR, bgcolor); if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) - BUG_MSG("<t%c> bgcolor attribute is obsolete.\n", + BUG_MSG("<t%c> bgcolor attribute is obsolete.", (tagsize >=3 && (D_ASCII_TOLOWER(tag[2]) == 'd')) ? 'd' : 'h'); } @@ -423,12 +424,12 @@ static void Html_tag_content_table_cell(DilloHtml *html, switch (S_TOP(html)->table_mode) { case DILLO_HTML_TABLE_MODE_NONE: - BUG_MSG("<t%c> outside <table>\n", + BUG_MSG("<t%c> outside <table>.", (tagsize >=3 && (D_ASCII_TOLOWER(tag[2]) == 'd')) ? 'd' : 'h'); return; case DILLO_HTML_TABLE_MODE_TOP: - BUG_MSG("<t%c> outside <tr>\n", + BUG_MSG("<t%c> outside <tr>.", (tagsize >=3 && (D_ASCII_TOLOWER(tag[2]) == 'd')) ? 'd' : 'h'); /* a_Dw_table_add_cell takes care that dillo does not crash. */ /* continues */ @@ -445,11 +446,11 @@ static void Html_tag_content_table_cell(DilloHtml *html, rowspan = MAX(1, strtol (attrbuf, NULL, 10)); if (html->style ()->textAlign == TEXT_ALIGN_STRING) - col_tb = new dw::TableCell ( + col_tb = new AlignedTableCell ( ((dw::Table*)S_TOP(html)->table)->getCellRef (), prefs.limit_text_width); else - col_tb = new Textblock (prefs.limit_text_width); + col_tb = new SimpleTableCell (prefs.limit_text_width); if (html->style()->borderCollapse == BORDER_MODEL_COLLAPSE){ Html_set_collapsing_border_model(html, col_tb); @@ -816,7 +816,7 @@ void UI::set_location(const char *str) { if (!str) str = ""; Location->value(str); - Location->position(strlen(str)); + Location->position((Fl::focus() == Location) ? strlen(str) : 0); } /* diff --git a/src/uicmd.cc b/src/uicmd.cc index e1100219..5225be75 100644 --- a/src/uicmd.cc +++ b/src/uicmd.cc @@ -71,6 +71,7 @@ static const char *save_dir = ""; static BrowserWindow *UIcmd_tab_new(CustTabs *tabs, UI *old_ui, int focus); static void close_tab_btn_cb (Fl_Widget *w, void *cb_data); static char *UIcmd_make_search_str(const char *str); +static void UIcmd_set_window_labels(Fl_Window *win, const char *str); //---------------------------------------------------------------------------- @@ -433,7 +434,7 @@ void CustTabs::switch_tab(CustTabButton *cbtn) // Update window title if ((bw = a_UIcmd_get_bw_by_widget(cbtn->ui()))) { const char *title = (cbtn->ui())->label(); - cbtn->window()->copy_label(title ? title : ""); + UIcmd_set_window_labels(cbtn->window(), title ? title : ""); } // Update focus priority increase_focus_counter(); @@ -568,6 +569,18 @@ BrowserWindow *a_UIcmd_browser_window_new(int ww, int wh, } /* + * Set the window name and icon name. + */ +static void UIcmd_set_window_labels(Fl_Window *win, const char *str) +{ + const char *copy; + + win->Fl_Widget::copy_label(str); + copy = win->label(); + win->label(copy, copy); +} + +/* * Create a new Tab button, UI and its associated BrowserWindow data * structure. */ @@ -606,7 +619,7 @@ static BrowserWindow *UIcmd_tab_new(CustTabs *tabs, UI *old_ui, int focus) // Clear the window title if (focus) - new_ui->window()->copy_label(new_ui->label()); + UIcmd_set_window_labels(new_ui->window(), new_ui->label()); // WORKAROUND: see findbar_toggle() new_ui->findbar_toggle(0); @@ -1413,7 +1426,7 @@ void a_UIcmd_set_page_title(BrowserWindow *bw, const char *label) if (a_UIcmd_get_bw_by_widget(BW2UI(bw)->tabs()->wizard()->value()) == bw) { // This is the focused bw, set window title - BW2UI(bw)->window()->copy_label(title); + UIcmd_set_window_labels(BW2UI(bw)->window(), title); } } @@ -366,23 +366,23 @@ DilloUrl* a_Url_new(const char *url_str, const char *base_url) dReturn_val_if_fail (url_str != NULL, NULL); - /* Count illegal characters (0x00-0x1F, 0x7F and space) */ + /* Count illegal characters (0x00-0x1F, 0x7F-0xFF and space) */ n_ic = n_ic_spc = 0; for (p = (char*)url_str; *p; p++) { n_ic_spc += (*p == ' ') ? 1 : 0; - n_ic += (*p != ' ' && *p > 0x1F && *p != 0x7F) ? 0 : 1; + n_ic += (*p != ' ' && *p > 0x1F && *p < 0x7F) ? 0 : 1; } if (n_ic) { /* Encode illegal characters (they could also be stripped). * There's no standard for illegal chars; we chose to encode. */ p = str1 = dNew(char, strlen(url_str) + 2*n_ic + 1); for (i = 0; url_str[i]; ++i) - if (url_str[i] > 0x1F && url_str[i] != 0x7F && url_str[i] != ' ') + if (url_str[i] > 0x1F && url_str[i] < 0x7F && url_str[i] != ' ') *p++ = url_str[i]; - else { - *p++ = '%'; - *p++ = HEX[(url_str[i] >> 4) & 15]; - *p++ = HEX[url_str[i] & 15]; + else { + *p++ = '%'; + *p++ = HEX[(url_str[i] >> 4) & 15]; + *p++ = HEX[url_str[i] & 15]; } *p = 0; urlstr = str1; @@ -509,7 +509,7 @@ void a_Url_set_ismap_coords(DilloUrl *u, char *coord_str) if (!u->ismap_url_len) { /* Save base-url length (without coords) */ - u->ismap_url_len = URL_STR_(u) ? u->url_string->len : 0; + u->ismap_url_len = URL_STR_(u) ? u->url_string->len : 0; a_Url_set_flags(u, URL_FLAGS(u) | URL_Ismap); } if (u->url_string) { @@ -611,7 +611,7 @@ char *a_Url_encode_hex_str(const char *str) /* * RFC-3986 suggests this stripping when "importing" URLs from other media. * Strip: "URL:", enclosing < >, and embedded whitespace. - * (We also strip illegal chars: 00-1F and 7F) + * (We also strip illegal chars: 00-1F and 7F-FF) */ char *a_Url_string_strip_delimiters(const char *str) { @@ -626,7 +626,7 @@ char *a_Url_string_strip_delimiters(const char *str) text++; for (p = new_str; *text; text++) - if (*text > 0x1F && *text != 0x7F && *text != ' ') + if (*text > 0x1F && *text < 0x7F && *text != ' ') *p++ = *text; if (p > new_str && p[-1] == '>') --p; @@ -688,14 +688,17 @@ static uint_t Url_host_public_internal_dots(const char *host) if (tld_len > 0) { /* These TLDs were chosen by examining the current publicsuffix list - * in February 2014 and picking out those where it was simplest for + * in October 2014 and picking out those where it was simplest for * them to describe the situation by beginning with a "*.[tld]" rule * or every rule was "[something].[tld]". + * + * TODO: Consider the old publicsuffix code again. This TLD list has + * shrunk and shrunk over the years, and has become a poorer and + * poorer approximation of administrative boundaries. */ - const char *const tlds[] = {"bd","bn","ck","cy","er","et","fj","fk", + const char *const tlds[] = {"bd","bn","ck","cy","er","fj","fk", "gu","il","jm","ke","kh","kw","mm","mz", - "ni","np","nz","pg","tr","uk","ye","za", - "zm","zw"}; + "ni","np","pg","ye","za","zm","zw"}; uint_t i, tld_num = sizeof(tlds) / sizeof(tlds[0]); for (i = 0; i < tld_num; i++) { @@ -13,15 +13,6 @@ #include "../dlib/dlib.h" -#define DILLO_URL_HTTP_PORT 80 -#define DILLO_URL_HTTPS_PORT 443 -#define DILLO_URL_FTP_PORT 21 -#define DILLO_URL_MAILTO_PORT 25 -#define DILLO_URL_NEWS_PORT 119 -#define DILLO_URL_TELNET_PORT 23 -#define DILLO_URL_GOPHER_PORT 70 - - /* * Values for DilloUrl->flags. * Specifies which which action to perform with an URL. @@ -128,7 +128,7 @@ DilloWeb* a_Web_new(BrowserWindow *bw, const DilloUrl *url, web->flags = 0; web->Image = NULL; web->filename = NULL; - web->stream = NULL; + web->stream = NULL; web->SavedBytes = 0; web->bgColor = 0x000000; /* Dummy value will be overwritten * in a_Web_dispatch_by_type. */ diff --git a/test/Makefile.am b/test/Makefile.am index 156c8667..3b474466 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -1,5 +1,7 @@ AM_CPPFLAGS = \ - -I$(top_srcdir) + -I$(top_srcdir) \ + -DCUR_WORKING_DIR='"@BASE_CUR_WORKING_DIR@/test"' + AM_CFLAGS = @LIBFLTK_CFLAGS@ AM_CXXFLAGS = @LIBFLTK_CXXFLAGS@ @@ -7,6 +9,7 @@ noinst_PROGRAMS = \ dw-anchors-test \ dw-example \ dw-find-test \ + dw-float-test \ dw-links \ dw-links2 \ dw-image-background \ @@ -14,6 +17,7 @@ noinst_PROGRAMS = \ dw-images-scaled \ dw-images-scaled2 \ dw-lists \ + dw-simple-container-test \ dw-table-aligned \ dw-table \ dw-border-test \ @@ -34,7 +38,7 @@ dw_anchors_test_LDADD = \ $(top_builddir)/dw/libDw-fltk.a \ $(top_builddir)/dw/libDw-core.a \ $(top_builddir)/lout/liblout.a \ - @LIBFLTK_LIBS@ + @LIBFLTK_LIBS@ @LIBX11_LIBS@ dw_example_SOURCES = dw_example.cc dw_example_LDADD = \ @@ -42,7 +46,7 @@ dw_example_LDADD = \ $(top_builddir)/dw/libDw-fltk.a \ $(top_builddir)/dw/libDw-core.a \ $(top_builddir)/lout/liblout.a \ - @LIBFLTK_LIBS@ + @LIBFLTK_LIBS@ @LIBX11_LIBS@ dw_find_test_SOURCES = dw_find_test.cc dw_find_test_LDADD = \ @@ -50,7 +54,15 @@ dw_find_test_LDADD = \ $(top_builddir)/dw/libDw-fltk.a \ $(top_builddir)/dw/libDw-core.a \ $(top_builddir)/lout/liblout.a \ - @LIBFLTK_LIBS@ + @LIBFLTK_LIBS@ @LIBX11_LIBS@ + +dw_float_test_SOURCES = dw_float_test.cc +dw_float_test_LDADD = \ + ../dw/libDw-widgets.a \ + ../dw/libDw-fltk.a \ + ../dw/libDw-core.a \ + ../lout/liblout.a \ + @LIBFLTK_LIBS@ @LIBX11_LIBS@ dw_links_SOURCES = dw_links.cc dw_links_LDADD = \ @@ -58,7 +70,7 @@ dw_links_LDADD = \ $(top_builddir)/dw/libDw-fltk.a \ $(top_builddir)/dw/libDw-core.a \ $(top_builddir)/lout/liblout.a \ - @LIBFLTK_LIBS@ + @LIBFLTK_LIBS@ @LIBX11_LIBS@ dw_links2_SOURCES = dw_links2.cc dw_links2_LDADD = \ @@ -66,7 +78,7 @@ dw_links2_LDADD = \ $(top_builddir)/dw/libDw-fltk.a \ $(top_builddir)/dw/libDw-core.a \ $(top_builddir)/lout/liblout.a \ - @LIBFLTK_LIBS@ + @LIBFLTK_LIBS@ @LIBX11_LIBS@ dw_image_background_SOURCES = dw_image_background.cc dw_image_background_LDADD = \ @@ -74,7 +86,7 @@ dw_image_background_LDADD = \ $(top_builddir)/dw/libDw-fltk.a \ $(top_builddir)/dw/libDw-core.a \ $(top_builddir)/lout/liblout.a \ - @LIBFLTK_LIBS@ + @LIBFLTK_LIBS@ @LIBX11_LIBS@ dw_images_simple_SOURCES = dw_images_simple.cc dw_images_simple_LDADD = \ @@ -82,7 +94,7 @@ dw_images_simple_LDADD = \ $(top_builddir)/dw/libDw-fltk.a \ $(top_builddir)/dw/libDw-core.a \ $(top_builddir)/lout/liblout.a \ - @LIBFLTK_LIBS@ + @LIBFLTK_LIBS@ @LIBX11_LIBS@ dw_images_scaled_SOURCES = dw_images_scaled.cc dw_images_scaled_LDADD = \ @@ -90,7 +102,7 @@ dw_images_scaled_LDADD = \ $(top_builddir)/dw/libDw-fltk.a \ $(top_builddir)/dw/libDw-core.a \ $(top_builddir)/lout/liblout.a \ - @LIBFLTK_LIBS@ + @LIBFLTK_LIBS@ @LIBX11_LIBS@ dw_images_scaled2_SOURCES = dw_images_scaled2.cc dw_images_scaled2_LDADD = \ @@ -98,7 +110,7 @@ dw_images_scaled2_LDADD = \ $(top_builddir)/dw/libDw-fltk.a \ $(top_builddir)/dw/libDw-core.a \ $(top_builddir)/lout/liblout.a \ - @LIBFLTK_LIBS@ + @LIBFLTK_LIBS@ @LIBX11_LIBS@ dw_lists_SOURCES = dw_lists.cc dw_lists_LDADD = \ @@ -106,6 +118,17 @@ dw_lists_LDADD = \ $(top_builddir)/dw/libDw-fltk.a \ $(top_builddir)/dw/libDw-core.a \ $(top_builddir)/lout/liblout.a \ + @LIBFLTK_LIBS@ @LIBX11_LIBS@ + +dw_simple_container_test_SOURCES = \ + dw_simple_container.hh \ + dw_simple_container.cc \ + dw_simple_container_test.cc +dw_simple_container_test_LDADD = \ + $(top_builddir)/dw/libDw-widgets.a \ + $(top_builddir)/dw/libDw-fltk.a \ + $(top_builddir)/dw/libDw-core.a \ + $(top_builddir)/lout/liblout.a \ @LIBFLTK_LIBS@ dw_table_aligned_SOURCES = dw_table_aligned.cc @@ -114,7 +137,7 @@ dw_table_aligned_LDADD = \ $(top_builddir)/dw/libDw-fltk.a \ $(top_builddir)/dw/libDw-core.a \ $(top_builddir)/lout/liblout.a \ - @LIBFLTK_LIBS@ + @LIBFLTK_LIBS@ @LIBX11_LIBS@ dw_table_SOURCES = dw_table.cc dw_table_LDADD = \ @@ -122,7 +145,7 @@ dw_table_LDADD = \ $(top_builddir)/dw/libDw-fltk.a \ $(top_builddir)/dw/libDw-core.a \ $(top_builddir)/lout/liblout.a \ - @LIBFLTK_LIBS@ + @LIBFLTK_LIBS@ @LIBX11_LIBS@ dw_border_test_SOURCES = dw_border_test.cc dw_border_test_LDADD = \ @@ -130,7 +153,7 @@ dw_border_test_LDADD = \ $(top_builddir)/dw/libDw-fltk.a \ $(top_builddir)/dw/libDw-core.a \ $(top_builddir)/lout/liblout.a \ - @LIBFLTK_LIBS@ + @LIBFLTK_LIBS@ @LIBX11_LIBS@ dw_imgbuf_mem_test_SOURCES = dw_imgbuf_mem_test.cc @@ -139,7 +162,7 @@ dw_imgbuf_mem_test_LDADD = \ $(top_builddir)/dw/libDw-fltk.a \ $(top_builddir)/dw/libDw-core.a \ $(top_builddir)/lout/liblout.a \ - @LIBFLTK_LIBS@ + @LIBFLTK_LIBS@ @LIBX11_LIBS@ dw_resource_test_SOURCES = dw_resource_test.cc dw_resource_test_LDADD = \ @@ -147,7 +170,7 @@ dw_resource_test_LDADD = \ $(top_builddir)/dw/libDw-fltk.a \ $(top_builddir)/dw/libDw-core.a \ $(top_builddir)/lout/liblout.a \ - @LIBFLTK_LIBS@ + @LIBFLTK_LIBS@ @LIBX11_LIBS@ dw_ui_test_SOURCES = \ dw_ui_test.cc \ @@ -158,7 +181,7 @@ dw_ui_test_LDADD = \ $(top_builddir)/dw/libDw-fltk.a \ $(top_builddir)/dw/libDw-core.a \ $(top_builddir)/lout/liblout.a \ - @LIBFLTK_LIBS@ + @LIBFLTK_LIBS@ @LIBX11_LIBS@ shapes_SOURCES = shapes.cc shapes_LDADD = \ @@ -180,7 +203,7 @@ liang_LDADD = \ $(top_builddir)/dw/libDw-fltk.a \ $(top_builddir)/dw/libDw-core.a \ $(top_builddir)/lout/liblout.a \ - @LIBFLTK_LIBS@ + @LIBFLTK_LIBS@ @LIBX11_LIBS@ trie_SOURCES = trie.cc @@ -189,7 +212,7 @@ trie_LDADD = \ $(top_builddir)/dw/libDw-fltk.a \ $(top_builddir)/dw/libDw-core.a \ $(top_builddir)/lout/liblout.a \ - @LIBFLTK_LIBS@ + @LIBFLTK_LIBS@ @LIBX11_LIBS@ notsosimplevector_SOURCES = notsosimplevector.cc @@ -199,4 +222,4 @@ unicode_test_SOURCES = unicode_test.cc unicode_test_LDADD = \ $(top_builddir)/lout/liblout.a \ - @LIBFLTK_LIBS@ + @LIBFLTK_LIBS@ @LIBX11_LIBS@ diff --git a/test/containers.cc b/test/containers.cc index 993a299d..af317d7e 100644 --- a/test/containers.cc +++ b/test/containers.cc @@ -4,6 +4,16 @@ using namespace lout::object; using namespace lout::container::typed; +class ReverseComparator: public Comparator +{ +private: + Comparator *reversed; + +public: + ReverseComparator (Comparator *reversed) { this->reversed = reversed; } + int compare(Object *o1, Object *o2) { return - reversed->compare (o1, o2); } +}; + void testHashSet () { puts ("--- testHashSet ---"); @@ -38,6 +48,8 @@ void testHashTable () void testVector1 () { + ReverseComparator reverse (&standardComparator); + puts ("--- testVector (1) ---"); Vector<String> v (true, 1); @@ -47,6 +59,9 @@ void testVector1 () v.put (new String ("three")); puts (v.toString()); + v.sort (&reverse); + puts (v.toString()); + v.sort (); puts (v.toString()); } @@ -95,12 +110,48 @@ void testVector2 () } } +void testVector3 () +{ + // Regression test: resulted once incorrently (0, 2, 3), should + // result in (1, 2, 3). + + puts ("--- testVector (3) ---"); + + Vector<String> v (true, 1); + String k ("omega"); + + v.put (new String ("alpha")); + printf (" -> %d\n", v.bsearch (&k, false)); + v.put (new String ("beta")); + printf (" -> %d\n", v.bsearch (&k, false)); + v.put (new String ("gamma")); + printf (" -> %d\n", v.bsearch (&k, false)); +} + +void testStackAsQueue () +{ + puts ("--- testStackAsQueue ---"); + + Stack<Integer> s (true); + + for (int i = 1; i <= 10; i++) + s.pushUnder (new Integer (i)); + + while (s.size () > 0) { + Integer *i = s.getTop (); + printf ("%d\n", i->getValue ()); + s.pop (); + } +} + int main (int argc, char *argv[]) { testHashSet (); testHashTable (); testVector1 (); testVector2 (); + testVector3 (); + testStackAsQueue (); return 0; } diff --git a/test/cookies.c b/test/cookies.c index 40661650..ff744c97 100644 --- a/test/cookies.c +++ b/test/cookies.c @@ -880,17 +880,17 @@ int main() path(); /* LEADING/TRAILING DOTS AND A LITTLE PUBLIC SUFFIX */ - a_Cookies_set("name=val; domain=co.uk", "www.co.uk", "/", NULL); - expect(__LINE__, "", "http", "www.co.uk", "/"); + a_Cookies_set("name=val; domain=co.il", "www.co.il", "/", NULL); + expect(__LINE__, "", "http", "www.co.il", "/"); - a_Cookies_set("name=val; domain=.co.uk", "www.co.uk", "/", NULL); - expect(__LINE__, "", "http", "www.co.uk", "/"); + a_Cookies_set("name=val; domain=.co.il", "www.co.il", "/", NULL); + expect(__LINE__, "", "http", "www.co.il", "/"); - a_Cookies_set("name=val; domain=co.uk.", "www.co.uk.", "/", NULL); - expect(__LINE__, "", "http", "www.co.uk.", "/"); + a_Cookies_set("name=val; domain=co.il.", "www.co.il.", "/", NULL); + expect(__LINE__, "", "http", "www.co.il.", "/"); - a_Cookies_set("name=val; domain=.co.uk.", "www.co.uk.", "/", NULL); - expect(__LINE__, "", "http", ".www.co.uk.", "/"); + a_Cookies_set("name=val; domain=.co.il.", "www.co.il.", "/", NULL); + expect(__LINE__, "", "http", ".www.co.il.", "/"); a_Cookies_set("name=val; domain=co.org", "www.co.org", "/", NULL); expect(__LINE__, "Cookie: name=val\r\n", "http", "www.co.org", "/"); diff --git a/test/dw_float_test.cc b/test/dw_float_test.cc new file mode 100644 index 00000000..807002b2 --- /dev/null +++ b/test/dw_float_test.cc @@ -0,0 +1,145 @@ +#include <FL/Fl.H> +#include <FL/Fl_Window.H> + +#include "../dw/core.hh" +#include "../dw/fltkcore.hh" +#include "../dw/fltkviewport.hh" +#include "../dw/textblock.hh" + +using namespace dw; +using namespace dw::core; +using namespace dw::core::style; +using namespace dw::fltk; + +static Textblock *firstFloat; +static Style *wordStyle; + +static void addTextToFloatTimeout (void *data) +{ + printf("addTextToFloatTimeout\n"); + + const char *fWords[] = { "This", "is", "a", "float,", "which", "is", + "set", "aside", "from", "the", "main", + "text.", NULL }; + + for(int k = 0; fWords[k]; k++) { + firstFloat->addText(fWords[k], wordStyle); + firstFloat->addSpace(wordStyle); + } + + firstFloat->flush(); + + Fl::repeat_timeout (2, addTextToFloatTimeout, NULL); +} + +int main(int argc, char **argv) +{ + FltkPlatform *platform = new FltkPlatform (); + Layout *layout = new Layout (platform); + + Fl_Window *window = new Fl_Window(400, 600, "Dw Floats Example"); + window->begin(); + + FltkViewport *viewport = new FltkViewport (0, 0, 400, 600); + layout->attachView (viewport); + + StyleAttrs styleAttrs; + styleAttrs.initValues (); + styleAttrs.margin.setVal (5); + + FontAttrs fontAttrs; + fontAttrs.name = "Bitstream Charter"; + fontAttrs.size = 14; + fontAttrs.weight = 400; + fontAttrs.style = FONT_STYLE_NORMAL; + fontAttrs.letterSpacing = 0; + styleAttrs.font = core::style::Font::create (layout, &fontAttrs); + + styleAttrs.color = Color::create (layout, 0x000000); + styleAttrs.backgroundColor = Color::create (layout, 0xffffff); + + Style *widgetStyle = Style::create (&styleAttrs); + + styleAttrs.borderWidth.setVal (1); + styleAttrs.setBorderColor (Color::create (layout, 0x808080)); + styleAttrs.setBorderStyle (BORDER_DASHED); + styleAttrs.width = createAbsLength(100); + styleAttrs.vloat = FLOAT_LEFT; + Style *leftFloatStyle = Style::create (&styleAttrs); + + styleAttrs.width = createAbsLength(80); + styleAttrs.vloat = FLOAT_RIGHT; + Style *rightFloatStyle = Style::create (&styleAttrs); + + Textblock *textblock = new Textblock (false); + textblock->setStyle (widgetStyle); + layout->setWidget (textblock); + + widgetStyle->unref(); + + styleAttrs.borderWidth.setVal (0); + styleAttrs.width = LENGTH_AUTO; + styleAttrs.vloat = FLOAT_NONE; + styleAttrs.margin.setVal (0); + styleAttrs.backgroundColor = NULL; + + wordStyle = Style::create (&styleAttrs); + + for(int i = 1; i <= 10; i++) { + char buf[16]; + snprintf(buf, sizeof(buf), "%d%s", + i, (i == 1 ? "st" : (i == 2 ? "nd" : (i == 3 ? "rd" : "th")))); + + const char *words[] = { "This", "is", "the", buf, "paragraph.", + "Here", "comes", "some", "more", "text", + "to", "demonstrate", "word", "wrapping.", + NULL }; + + for(int j = 0; words[j]; j++) { + textblock->addText(words[j], wordStyle); + textblock->addSpace(wordStyle); + + if ((i == 3 || i == 5) && j == 8) { + textblock->addText("[float]", wordStyle); + textblock->addSpace(wordStyle); + + Textblock *vloat = new Textblock (false); + textblock->addWidget(vloat, i == 3 ? leftFloatStyle : rightFloatStyle); + + const char *fWords[] = { "This", "is", "a", "float,", "which", "is", + "set", "aside", "from", "the", "main", + "text.", NULL }; + + vloat->addText(i == 3 ? "Left:" : "Right:", wordStyle); + vloat->addSpace(wordStyle); + + for(int k = 0; fWords[k]; k++) { + vloat->addText(fWords[k], wordStyle); + vloat->addSpace(wordStyle); + } + + vloat->flush (); + + if(i == 3) + firstFloat = vloat; + } + } + + textblock->addParbreak(10, wordStyle); + } + + leftFloatStyle->unref(); + rightFloatStyle->unref(); + + textblock->flush (); + + window->resizable(viewport); + window->show(); + Fl::add_timeout (2, addTextToFloatTimeout, NULL); + int errorCode = Fl::run(); + + wordStyle->unref(); + delete layout; + + return errorCode; +} diff --git a/test/dw_simple_container.cc b/test/dw_simple_container.cc new file mode 100644 index 00000000..e19511a6 --- /dev/null +++ b/test/dw_simple_container.cc @@ -0,0 +1,241 @@ +/* + * Dillo Widget + * + * Copyright 2014 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <math.h> + +#include "dw_simple_container.hh" + +using namespace dw::core; +using namespace dw::core::style; +using namespace lout::misc; + +namespace dw { + +int SimpleContainer::CLASS_ID = -1; + +// ---------------------------------------------------------------------- + +SimpleContainer::SimpleContainerIterator::SimpleContainerIterator + (SimpleContainer *simpleContainer, Content::Type mask, bool atEnd) : + Iterator (simpleContainer, mask, atEnd) +{ + content.type = atEnd ? Content::END : Content::START; +} + +lout::object::Object *SimpleContainer::SimpleContainerIterator::clone () +{ + SimpleContainerIterator *sci = + new SimpleContainerIterator ((SimpleContainer*)getWidget(), + getMask(), false); + sci->content = content; + return sci; +} + +int SimpleContainer::SimpleContainerIterator::index () +{ + switch (content.type) { + case Content::START: + return 0; + case Content::WIDGET_IN_FLOW: + return 1; + case Content::END: + return 2; + default: + assertNotReached (); + return 0; + } +} + +int SimpleContainer::SimpleContainerIterator::compareTo + (lout::object::Comparable *other) +{ + return index () - ((SimpleContainerIterator*)other)->index (); +} + +bool SimpleContainer::SimpleContainerIterator::next () +{ + SimpleContainer *simpleContainer = (SimpleContainer*)getWidget(); + + if (content.type == Content::END) + return false; + + // simple containers only contain widgets: + if ((getMask() & Content::WIDGET_IN_FLOW) == 0) { + content.type = Content::END; + return false; + } + + if (content.type == Content::START) { + if (simpleContainer->child != NULL) { + content.type = Content::WIDGET_IN_FLOW; + content.widget = simpleContainer->child; + return true; + } else { + content.type = Content::END; + return false; + } + } else /* if (content.type == Content::WIDGET) */ { + content.type = Content::END; + return false; + } +} + +bool SimpleContainer::SimpleContainerIterator::prev () +{ + SimpleContainer *simpleContainer = (SimpleContainer*)getWidget(); + + if (content.type == Content::START) + return false; + + // simple containers only contain widgets: + if ((getMask() & Content::WIDGET_IN_FLOW) == 0) { + content.type = Content::START; + return false; + } + + if (content.type == Content::END) { + if (simpleContainer->child != NULL) { + content.type = Content::WIDGET_IN_FLOW; + content.widget = simpleContainer->child; + return true; + } else { + content.type = Content::START; + return false; + } + } else /* if (content.type == Content::WIDGET) */ { + content.type = Content::START; + return false; + } +} + +void SimpleContainer::SimpleContainerIterator::highlight (int start, + int end, + HighlightLayer layer) +{ + /** todo Needs this an implementation? */ +} + +void SimpleContainer::SimpleContainerIterator::unhighlight (int direction, + HighlightLayer + layer) +{ + /** todo Needs this an implementation? */ +} + +void SimpleContainer::SimpleContainerIterator::getAllocation (int start, + int end, + Allocation + *allocation) +{ + /** \bug Not implemented. */ +} + +// ---------------------------------------------------------------------- + +SimpleContainer::SimpleContainer () +{ + registerName ("dw::SimpleContainer", &CLASS_ID); + child = NULL; +} + +SimpleContainer::~SimpleContainer () +{ + if (child) + delete child; +} + +void SimpleContainer::sizeRequestImpl (Requisition *requisition) +{ + Requisition childReq; + if (child) + child->sizeRequest (&childReq); + else + childReq.width = childReq.ascent = childReq.descent = 0; + + requisition->width = childReq.width + boxDiffWidth (); + requisition->ascent = childReq.ascent + boxOffsetY (); + requisition->descent = childReq.descent + boxRestHeight (); + + correctRequisition (requisition, splitHeightPreserveAscent); +} + + +void SimpleContainer::getExtremesImpl (Extremes *extremes) +{ + Extremes childExtr; + if (child) + child->getExtremes (&childExtr); + else + childExtr.minWidth = childExtr.maxWidth = 0; + + extremes->minWidth = childExtr.minWidth + boxDiffWidth (); + extremes->maxWidth = childExtr.maxWidth + boxDiffWidth (); + + correctExtremes (extremes); +} + + +void SimpleContainer::sizeAllocateImpl (Allocation *allocation) +{ + Allocation childAlloc; + + if (child) { + childAlloc.x = allocation->x + boxOffsetX (); + childAlloc.y = allocation->y + boxOffsetY (); + childAlloc.width = allocation->width - boxDiffWidth (); + childAlloc.ascent = allocation->ascent - boxOffsetY (); + childAlloc.descent = allocation->descent - boxRestHeight (); + child->sizeAllocate (&childAlloc); + } +} + +void SimpleContainer::draw (View *view, Rectangle *area) +{ + drawWidgetBox (view, area, false); + Rectangle childArea; + if (child && child->intersects (area, &childArea)) + child->draw (view, &childArea); +} + +Iterator *SimpleContainer::iterator (Content::Type mask, bool atEnd) +{ + return new SimpleContainerIterator (this, mask, atEnd); +} + +void SimpleContainer::removeChild (Widget *child) +{ + assert (child == this->child); + this->child = NULL; + + queueResize (0, true); +} + +void SimpleContainer::setChild (Widget *child) +{ + if (this->child) + delete this->child; + + this->child = child; + if (this->child) + this->child->setParent (this); + + queueResize (0, true); +} + +} // namespace dw diff --git a/test/dw_simple_container.hh b/test/dw_simple_container.hh new file mode 100644 index 00000000..fdb67bec --- /dev/null +++ b/test/dw_simple_container.hh @@ -0,0 +1,56 @@ +#ifndef __DW_SIMPLE_CONTAINER_HH__ +#define __DW_SIMPLE_CONTAINER_HH__ + +#include "dw/core.hh" + +namespace dw { + +/** + * Simple widget used for testing concepts. + */ +class SimpleContainer: public core::Widget +{ +private: + class SimpleContainerIterator: public core::Iterator + { + private: + int index (); + + public: + SimpleContainerIterator (SimpleContainer *simpleContainer, + core::Content::Type mask, + bool atEnd); + + lout::object::Object *clone (); + int compareTo (lout::object::Comparable *other); + + bool next (); + bool prev (); + void highlight (int start, int end, core::HighlightLayer layer); + void unhighlight (int direction, core::HighlightLayer layer); + void getAllocation (int start, int end, core::Allocation *allocation); + }; + + Widget *child; + +protected: + void sizeRequestImpl (core::Requisition *requisition); + void getExtremesImpl (core::Extremes *extremes); + void sizeAllocateImpl (core::Allocation *allocation); + +public: + static int CLASS_ID; + + SimpleContainer (); + ~SimpleContainer (); + + void draw (core::View *view, core::Rectangle *area); + core::Iterator *iterator (core::Content::Type mask, bool atEnd); + void removeChild (Widget *child); + + void setChild (core::Widget *child); +}; + +} // namespace dw + +#endif // __DW_SIMPLE_CONTAINER_HH__ diff --git a/test/dw_simple_container_test.cc b/test/dw_simple_container_test.cc new file mode 100644 index 00000000..83d0a77f --- /dev/null +++ b/test/dw_simple_container_test.cc @@ -0,0 +1,114 @@ +/* + * Dillo Widget + * + * Copyright 2014 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + + +#include <FL/Fl_Window.H> +#include <FL/Fl.H> + +#include "../dw/core.hh" +#include "../dw/fltkcore.hh" +#include "../dw/fltkviewport.hh" +#include "dw_simple_container.hh" +#include "../dw/textblock.hh" + +using namespace dw; +using namespace dw::core; +using namespace dw::core::style; +using namespace dw::fltk; + +int main(int argc, char **argv) +{ + FltkPlatform *platform = new FltkPlatform (); + Layout *layout = new Layout (platform); + + Fl_Window *window = new Fl_Window(200, 300, "Dw Example"); + window->box(FL_NO_BOX); + window->begin(); + + FltkViewport *viewport = new FltkViewport (0, 0, 200, 300); + layout->attachView (viewport); + + StyleAttrs styleAttrs; + styleAttrs.initValues (); + styleAttrs.margin.setVal (5); + + FontAttrs fontAttrs; + fontAttrs.name = "Bitstream Charter"; + fontAttrs.size = 14; + fontAttrs.weight = 400; + fontAttrs.style = FONT_STYLE_NORMAL; + fontAttrs.letterSpacing = 0; + fontAttrs.fontVariant = FONT_VARIANT_NORMAL; + styleAttrs.font = style::Font::create (layout, &fontAttrs); + + styleAttrs.color = Color::create (layout, 0x000000); + styleAttrs.backgroundColor = Color::create (layout, 0xffffff); + + Style *textblockStyle1 = Style::create (&styleAttrs); + + styleAttrs.backgroundColor = NULL; + styleAttrs.margin.setVal (0); + + Style *textblockStyle2 = Style::create (&styleAttrs); + Style *wordStyle = Style::create (&styleAttrs); + + styleAttrs.borderWidth.setVal (5); + styleAttrs.setBorderColor (Color::create (layout, 0x800080)); + styleAttrs.setBorderStyle (BORDER_DASHED); + styleAttrs.padding.setVal (5); + + Style *containerStyle = Style::create (&styleAttrs); + + Textblock *textblock1 = new Textblock (false); + textblock1->setStyle (textblockStyle1); + layout->setWidget (textblock1); + + SimpleContainer *simpleContainer = new SimpleContainer (); + simpleContainer->setStyle (containerStyle); + textblock1->addWidget (simpleContainer, containerStyle); + + Textblock *textblock2 = new Textblock (false); + textblock2->setStyle (textblockStyle2); + simpleContainer->setChild (textblock2); + + const char *words[] = { "This", "is", "only", "a", "short", "paragraph.", + NULL }; + + for(int j = 0; words[j]; j++) { + textblock2->addText(words[j], wordStyle); + textblock2->addSpace(wordStyle); + } + + textblockStyle1->unref(); + textblockStyle2->unref(); + containerStyle->unref(); + wordStyle->unref(); + + textblock1->flush (); + textblock2->flush (); + + window->resizable(viewport); + window->show(); + int errorCode = Fl::run(); + + delete layout; + + return errorCode; +} diff --git a/test/dw_table.cc b/test/dw_table.cc index 5416d05b..9bec1a09 100644 --- a/test/dw_table.cc +++ b/test/dw_table.cc @@ -26,7 +26,6 @@ #include "../dw/fltkcore.hh" #include "../dw/fltkviewport.hh" #include "../dw/table.hh" -#include "../dw/tablecell.hh" using namespace dw; using namespace dw::core; diff --git a/test/dw_table_aligned.cc b/test/dw_table_aligned.cc index 96cb0602..bef3d521 100644 --- a/test/dw_table_aligned.cc +++ b/test/dw_table_aligned.cc @@ -26,7 +26,7 @@ #include "../dw/fltkcore.hh" #include "../dw/fltkviewport.hh" #include "../dw/table.hh" -#include "../dw/tablecell.hh" +#include "../dw/alignedtablecell.hh" using namespace dw; using namespace dw::core; @@ -87,10 +87,10 @@ int main(int argc, char **argv) Style *wordStyle = Style::create (&styleAttrs); - TableCell *ref = NULL; + AlignedTableCell *ref = NULL; for(int i = 0; i < 10; i++) { //for(int i = 0; i < 1; i++) { - TableCell *cell = new TableCell (ref, false); + AlignedTableCell *cell = new AlignedTableCell (ref, false); cell->setStyle (cellStyle); ref = cell; table->addRow (wordStyle); diff --git a/test/floats-and-absolute.html b/test/floats-and-absolute.html new file mode 100644 index 00000000..658ab16b --- /dev/null +++ b/test/floats-and-absolute.html @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> + <head> + <title>Floats And Absolute Positions</title> + <style type="text/css"> + div.main { + margin: 0 0 0 100px; + top: 3cm; + position: absolute; + } + + div.margin { + position: absolute; + top: 3cm; + width: 120px; + } + </style> + </head> + <body> + <h1>Floats And Absolute Positions</h1> + <div class="main"> + <img style="float: left" src="http://upload.wikimedia.org/wikipedia/commons/thumb/b/bc/Library_of_Ashurbanipal_The_Flood_Tablet.jpg/218px-Library_of_Ashurbanipal_The_Flood_Tablet.jpg" /> + <p>Sed ut perspiciatis, unde omnis iste natus error sit + voluptatem accusantium doloremque laudantium, totam rem + aperiam eaque ipsa, quae ab illo inventore veritatis et quasi + architecto beatae vitae dicta sunt, explicabo. nemo enim ipsam + voluptatem, quia voluptas sit, aspernatur aut odit aut fugit, + sed quia consequuntur magni dolores eos, qui ratione + voluptatem sequi nesciunt, neque porro quisquam est, qui + dolorem ipsum, quia dolor sit, amet, consectetur, adipisci + velit, sed quia non numquam eius modi tempora incidunt, ut + labore et dolore magnam aliquam quaerat voluptatem. ut enim ad + minima veniam, quis nostrum exercitationem ullam corporis + suscipit laboriosam, nisi ut aliquid ex ea commodi + consequatur? quis autem vel eum iure reprehenderit, qui in ea + voluptate velit esse, quam nihil molestiae consequatur, vel + illum, qui dolorem eum fugiat, quo voluptas nulla + pariatur?</p> + <p>Ἐν ἀρχῇ ἦν ὁ Λόγος, καὶ ὁ Λόγος ἦν πρὸς τὸν Θεόν, καὶ Θεὸς ἦν + ὁ Λόγος. Οὗτος ἦν ἐν ἀρχῇ πρὸς τὸν Θεόν. πάντα δι' αὐτοῦ + ἐγένετο, καὶ χωρὶς αὐτοῦ ἐγένετο οὐδὲ ἕν ὃ γέγονεν. ἐν αὐτῷ + ζωὴ ἦν, καὶ ἡ ζωὴ ἦν τὸ φῶς τῶν ἀνθρώπων. καὶ τὸ φῶς ἐν τῇ + σκοτίᾳ φαίνει, καὶ ἡ σκοτία αὐτὸ οὐ κατέλαβεν.</p> + </div> + <div class="margin">Margin, actually on the left side.</div> + <body> +</html> + + + diff --git a/test/floats-and-margins.html b/test/floats-and-margins.html new file mode 100644 index 00000000..ac64b9e1 --- /dev/null +++ b/test/floats-and-margins.html @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> + <head> + <title>Floats And Margins</title> + <style type="text/css"> + </style> + </head> + <body> + <p> + <img src="http://upload.wikimedia.org/wikipedia/commons/thumb/b/bc/Library_of_Ashurbanipal_The_Flood_Tablet.jpg/200px-Library_of_Ashurbanipal_The_Flood_Tablet.jpg" style="float: left; margin: 1cm 1cm 1cm 0" /> + Ἐν ἀρχῇ ἦν ὁ Λόγος, καὶ ὁ Λόγος ἦν πρὸς τὸν Θεόν, καὶ Θεὸς ἦν ὁ + Λόγος. Οὗτος ἦν ἐν ἀρχῇ πρὸς τὸν Θεόν. πάντα δι' αὐτοῦ ἐγένετο, + καὶ χωρὶς αὐτοῦ ἐγένετο οὐδὲ ἕν ὃ γέγονεν. ἐν αὐτῷ ζωὴ ἦν, καὶ ἡ + ζωὴ ἦν τὸ φῶς τῶν ἀνθρώπων. καὶ τὸ φῶς ἐν τῇ σκοτίᾳ φαίνει, καὶ + ἡ σκοτία αὐτὸ οὐ κατέλαβεν. + </p> + <p style="margin: 0 3cm"> + <img src="http://upload.wikimedia.org/wikipedia/commons/thumb/2/26/GilgameshTablet.jpg/200px-GilgameshTablet.jpg" style="float: right; margin: 1cm 0 1cm 1cm"/> + Sed ut perspiciatis, unde omnis iste natus error sit voluptatem + accusantium doloremque laudantium, totam rem aperiam eaque ipsa, + quae ab illo inventore veritatis et quasi architecto beatae + vitae dicta sunt, explicabo. nemo enim ipsam voluptatem, quia + voluptas sit, aspernatur aut odit aut fugit, sed quia + consequuntur magni dolores eos, qui ratione voluptatem sequi + nesciunt, neque porro quisquam est, qui dolorem ipsum, quia + dolor sit, amet, consectetur, adipisci velit, sed quia non + numquam eius modi tempora incidunt, ut labore et dolore magnam + aliquam quaerat voluptatem. ut enim ad minima veniam, quis + nostrum exercitationem ullam corporis suscipit laboriosam, nisi + ut aliquid ex ea commodi consequatur? quis autem vel eum iure + reprehenderit, qui in ea voluptate velit esse, quam nihil + molestiae consequatur, vel illum, qui dolorem eum fugiat, quo + voluptas nulla pariatur? + </p> + <body> +</html> + + + diff --git a/test/floats-table.html b/test/floats-table.html new file mode 100644 index 00000000..77d77563 --- /dev/null +++ b/test/floats-table.html @@ -0,0 +1,24 @@ +<p>Demonstrating how to include floats into witdth extremes. + +<table> + <tr> + <td style="border: 1px dashed black"> + <div style="float:left; border: 1px dashed black">Somelongwordwhichmustnotbebrokensotakingmuchspaceinatablecolumn</div> + Some short text. + <td style="border: 1px dashed black"> + Sed ut perspiciatis, unde omnis iste natus error sit voluptatem + accusantium doloremque laudantium, totam rem aperiam eaque ipsa, + quae ab illo inventore veritatis et quasi architecto beatae + vitae dicta sunt, explicabo. nemo enim ipsam voluptatem, quia + voluptas sit, aspernatur aut odit aut fugit, sed quia + consequuntur magni dolores eos, qui ratione voluptatem sequi + nesciunt, neque porro quisquam est, qui dolorem ipsum, quia + dolor sit, amet, consectetur, adipisci velit, sed quia non + numquam eius modi tempora incidunt, ut labore et dolore magnam + aliquam quaerat voluptatem. ut enim ad minima veniam, quis + nostrum exercitationem ullam corporis suscipit laboriosam, nisi + ut aliquid ex ea commodi consequatur? quis autem vel eum iure + reprehenderit, qui in ea voluptate velit esse, quam nihil + molestiae consequatur, vel illum, qui dolorem eum fugiat, quo + voluptas nulla pariatur? +</table> diff --git a/test/floats-worm.html b/test/floats-worm.html new file mode 100644 index 00000000..9e050933 --- /dev/null +++ b/test/floats-worm.html @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> + <head> + <title>Floats and iterators: search for "worm" and pay attention + to the order.</title> + <style type="text/css"> + .float1, .float2 { + padding: 0.5em; + border: 1px dashed #404040; + width: 30%; + } + .float1 { + float: left; + margin: 0.5em 0.5em 0.5em 0; + } + .float2 { + float: right; + margin: 0.5em 0 0.5em 0.5em; + } + + </style> + </head> + <body> + <div class="float1">1: apple apple worm apple apple</div> + <p>2: apple apple apple apple apple apple apple apple apple apple + apple apple apple apple apple apple apple apple apple apple + apple apple apple apple worm apple apple apple apple apple apple + apple apple apple apple apple apple apple apple apple apple + apple apple apple apple apple apple apple apple</p> + <div class="float2">3: apple apple worm apple apple</div> + <p>4: apple apple apple apple apple apple apple apple apple apple + apple apple apple apple apple apple apple apple apple apple + apple apple apple apple worm apple apple apple apple apple apple + apple apple apple apple apple apple apple apple apple apple + apple apple apple apple apple apple apple apple</p> + <div class="float1">5: apple apple worm apple apple</div> + <body> +</html> + + + diff --git a/test/floats1.html b/test/floats1.html new file mode 100644 index 00000000..eae30c4a --- /dev/null +++ b/test/floats1.html @@ -0,0 +1,46 @@ +<div>First paragraph. First paragraph. First paragraph. First +paragraph. First paragraph. First paragraph. First paragraph. First +paragraph. First paragraph. First paragraph. First paragraph. First +paragraph. First paragraph. First paragraph. First paragraph. First +paragraph. First paragraph. First paragraph. First paragraph. First +paragraph. First paragraph. First paragraph. First paragraph. First +paragraph. First paragraph. First paragraph. First paragraph. First +paragraph. First paragraph. First paragraph. First paragraph. First +paragraph. First paragraph. First paragraph. First +paragraph. <div style="float: left; border: 1px dashed black">Left +float. Left float. Left float. Left float. Left float. Left +float. Left float. Left float. Left float. Left float. Left +float. Left float. Left float. Left float. Left float. Left +float. Left float. Left float. Left float. Left float.</div> First +paragraph. First paragraph. First paragraph. First paragraph. First +paragraph. First paragraph. First paragraph. First paragraph. First +paragraph. First paragraph. First paragraph. First paragraph. First +paragraph. First paragraph. First paragraph. First paragraph. First +paragraph. First paragraph. First paragraph. First paragraph. First +paragraph. First paragraph. First paragraph. First paragraph. First +paragraph. First paragraph. First paragraph. First paragraph. First +paragraph. First paragraph. First paragraph.</div> +<div><div>Second paragraph. Second paragraph. Second paragraph. Second +paragraph. Second paragraph. Second paragraph. Second +paragraph. Second paragraph. Second paragraph. Second +paragraph. Second paragraph. Second paragraph. Second +paragraph. Second paragraph. Second paragraph.<div style="float: +right; border: 1px dashed black">Right float. Right float. Right +float. Right float. Right float. Right float.</div> Second +paragraph. Second paragraph. Second paragraph. Second +paragraph. Second paragraph. Second paragraph. Second +paragraph. Second paragraph. Second paragraph. Second +paragraph. Second paragraph. Second paragraph. Second +paragraph. Second paragraph. Second paragraph. Second +paragraph. Second paragraph. Second paragraph. Second +paragraph. Second paragraph. Second paragraph. Second +paragraph. Second paragraph. Second paragraph. Second +paragraph. Second paragraph. Second paragraph. Second +paragraph. Second paragraph. Second paragraph. Second +paragraph. Second paragraph. Second paragraph. Second +paragraph. Second paragraph. Second paragraph. Second +paragraph. Second paragraph. Second paragraph. Second +paragraph. Second paragraph. Second paragraph. Second +paragraph. Second paragraph. Second paragraph. Second +paragraph. Second paragraph. Second paragraph. Second +paragraph. Second paragraph. Second paragraph.</div></div> diff --git a/test/floats2.html b/test/floats2.html new file mode 100644 index 00000000..b7978706 --- /dev/null +++ b/test/floats2.html @@ -0,0 +1,17 @@ +Sed ut perspiciatis, unde omnis iste natus error sit voluptatem +accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae +ab illo inventore veritatis et quasi architecto beatae vitae dicta +sunt, explicabo. +<div style="float:left; border: 1px dashed black">Some text in a +float.<br /><img src="http://www.dillo.org/dw/html/not-so-simple-container.png" /></div> +nemo enim ipsam voluptatem, quia voluptas sit, +aspernatur aut odit aut fugit, sed quia consequuntur magni dolores +eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est, +qui dolorem ipsum, quia dolor sit, amet, consectetur, adipisci velit, +sed quia non numquam eius modi tempora incidunt, ut labore et dolore +magnam aliquam quaerat voluptatem. ut enim ad minima veniam, quis +nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut +aliquid ex ea commodi consequatur? quis autem vel eum iure +reprehenderit, qui in ea voluptate velit esse, quam nihil molestiae +consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla +pariatur? diff --git a/test/floats3.html b/test/floats3.html new file mode 100644 index 00000000..4d57e453 --- /dev/null +++ b/test/floats3.html @@ -0,0 +1,16 @@ +<p>Sed ut perspiciatis, unde omnis iste natus error sit voluptatem +accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae +ab illo inventore veritatis et quasi architecto beatae vitae dicta +sunt, explicabo.</p> +<div style="float:left; border: 1px dashed black">Some text in a +float.<br /><img src="http://www.dillo.org/dw/html/not-so-simple-container.png" /></div> +<p>nemo enim ipsam voluptatem, quia voluptas sit, aspernatur aut odit +aut fugit, sed quia consequuntur magni dolores eos, qui ratione +voluptatem sequi nesciunt, neque porro quisquam est, qui dolorem +ipsum, quia dolor sit, amet, consectetur, adipisci velit, sed quia non +numquam eius modi tempora incidunt, ut labore et dolore magnam aliquam +quaerat voluptatem. ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex +ea commodi consequatur? quis autem vel eum iure reprehenderit, qui in +ea voluptate velit esse, quam nihil molestiae consequatur, vel illum, +qui dolorem eum fugiat, quo voluptas nulla pariatur?</p> diff --git a/test/floats4.html b/test/floats4.html new file mode 100644 index 00000000..965ed68d --- /dev/null +++ b/test/floats4.html @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> + <head> + <title>Floats 4</title> + <style type="text/css"> + .border { + background-color: #e0e0ff; + padding: 1cm; + } + .float1, .float2 { + margin: 1cm; + padding: 1cm; + border: 1px dashed red; + background-color: #f0fff0; + float: right; + } + .float1 { + float: left; + } + .float2 { + float: right; + } + + .wide { + margin: 1cm 0; + padding: 1cm; + border: 1px dashed red; + background-color: #ffffd0; + width: 40cm; + } + </style> + </head> + <body class="border"> + <div class="float2">Some text in a float.</div> + + <p>Sed ut perspiciatis, unde omnis iste natus error sit voluptatem + accusantium doloremque laudantium, totam rem aperiam eaque ipsa, + quae ab illo inventore veritatis et quasi architecto beatae + vitae dicta sunt, explicabo. nemo enim ipsam voluptatem, quia + voluptas sit, aspernatur aut odit aut fugit, sed quia + consequuntur magni dolores eos, qui ratione voluptatem sequi + nesciunt, neque porro quisquam est, qui dolorem ipsum, quia + dolor sit, amet, consectetur, adipisci velit, sed quia non + numquam eius modi tempora incidunt, ut labore et dolore magnam + aliquam quaerat voluptatem. ut enim ad minima veniam, quis + nostrum exercitationem ullam corporis suscipit laboriosam, nisi + ut aliquid ex ea commodi consequatur? quis autem vel eum iure + reprehenderit, qui in ea voluptate velit esse, quam nihil + molestiae consequatur, vel illum, qui dolorem eum fugiat, quo + voluptas nulla pariatur?</p> + + <table class="wide"><tbody><tr><td>Ἐν ἀρχῇ ἦν ὁ Λόγος, καὶ ὁ Λόγος + ἦν πρὸς τὸν Θεόν, καὶ Θεὸς ἦν ὁ Λόγος. Οὗτος ἦν ἐν ἀρχῇ πρὸς τὸν + Θεόν. πάντα δι' αὐτοῦ ἐγένετο, καὶ χωρὶς αὐτοῦ ἐγένετο οὐδὲ ἕν ὃ + γέγονεν. ἐν αὐτῷ ζωὴ ἦν, καὶ ἡ ζωὴ ἦν τὸ φῶς τῶν ἀνθρώπων. καὶ + τὸ φῶς ἐν τῇ σκοτίᾳ φαίνει, καὶ ἡ σκοτία αὐτὸ οὐ + κατέλαβεν.</tbody></tr></td></table> + <body> +</html> + + + diff --git a/test/floats5.html b/test/floats5.html new file mode 100644 index 00000000..c8c6564a --- /dev/null +++ b/test/floats5.html @@ -0,0 +1,16 @@ +Sed ut perspiciatis, unde omnis iste natus error sit voluptatem +accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae +ab illo inventore veritatis et quasi architecto beatae vitae dicta +sunt, explicabo. +<div style="float:left; border: 1px dashed black"><img src="http://www.dillo.org/Icons/ProgramIcon16.png" /></div> +nemo enim ipsam voluptatem, quia voluptas sit, +aspernatur aut odit aut fugit, sed quia consequuntur magni dolores +eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est, +qui dolorem ipsum, quia dolor sit, amet, consectetur, adipisci velit, +sed quia non numquam eius modi tempora incidunt, ut labore et dolore +magnam aliquam quaerat voluptatem. ut enim ad minima veniam, quis +nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut +aliquid ex ea commodi consequatur? quis autem vel eum iure +reprehenderit, qui in ea voluptate velit esse, quam nihil molestiae +consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla +pariatur? |