aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.hgignore2
-rw-r--r--AUTHORS2
-rw-r--r--ChangeLog32
-rw-r--r--Doxyfile2661
-rw-r--r--README4
-rw-r--r--configure.ac25
-rw-r--r--devdoc/CCCwork.txt (renamed from doc/CCCwork.txt)0
-rw-r--r--devdoc/Cache.txt (renamed from doc/Cache.txt)0
-rw-r--r--devdoc/Dillo.txt (renamed from doc/Dillo.txt)0
-rw-r--r--devdoc/Dpid.txt (renamed from doc/Dpid.txt)0
-rw-r--r--devdoc/HtmlParser.txt (renamed from doc/HtmlParser.txt)0
-rw-r--r--devdoc/IO.txt (renamed from doc/IO.txt)0
-rw-r--r--devdoc/Images.txt (renamed from doc/Images.txt)3
-rw-r--r--devdoc/NC_design.txt (renamed from doc/NC_design.txt)0
-rw-r--r--devdoc/README51
-rw-r--r--devdoc/dw-changes.doc (renamed from doc/dw-changes.doc)0
-rw-r--r--devdoc/dw-example-screenshot.pngbin0 -> 2264 bytes
-rw-r--r--devdoc/dw-floats-01.pngbin0 -> 3410 bytes
-rw-r--r--devdoc/dw-grows.doc202
-rw-r--r--devdoc/dw-images-and-backgrounds.doc (renamed from doc/dw-images-and-backgrounds.doc)0
-rw-r--r--devdoc/dw-layout-views.doc (renamed from doc/dw-layout-views.doc)0
-rw-r--r--devdoc/dw-layout-widgets.doc (renamed from doc/dw-layout-widgets.doc)0
-rw-r--r--devdoc/dw-line-breaking.doc (renamed from doc/dw-line-breaking.doc)4
-rw-r--r--devdoc/dw-map.doc (renamed from doc/dw-map.doc)0
-rw-r--r--devdoc/dw-out-of-flow-2.doc69
-rw-r--r--devdoc/dw-out-of-flow-floats.doc121
-rw-r--r--devdoc/dw-out-of-flow.doc214
-rw-r--r--devdoc/dw-overview.doc (renamed from doc/dw-overview.doc)0
-rw-r--r--devdoc/dw-size-of-widget.pngbin0 -> 1749 bytes
-rw-r--r--devdoc/dw-style-box-model.pngbin0 -> 3889 bytes
-rw-r--r--devdoc/dw-style-length-absolute.pngbin0 -> 575 bytes
-rw-r--r--devdoc/dw-style-length-percentage.pngbin0 -> 890 bytes
-rw-r--r--devdoc/dw-style-length-relative.pngbin0 -> 868 bytes
-rw-r--r--devdoc/dw-textblock-collapsing-spaces-1-1.png (renamed from doc/dw-textblock-collapsing-spaces-1-1.png)bin641 -> 641 bytes
-rw-r--r--devdoc/dw-textblock-collapsing-spaces-1-2.png (renamed from doc/dw-textblock-collapsing-spaces-1-2.png)bin521 -> 521 bytes
-rw-r--r--devdoc/dw-textblock-collapsing-spaces-2-1.png (renamed from doc/dw-textblock-collapsing-spaces-2-1.png)bin802 -> 802 bytes
-rw-r--r--devdoc/dw-textblock-collapsing-spaces-2-2.png (renamed from doc/dw-textblock-collapsing-spaces-2-2.png)bin586 -> 586 bytes
-rw-r--r--devdoc/dw-usage.doc (renamed from doc/dw-usage.doc)0
-rw-r--r--devdoc/dw-viewport-with-scrollbar.png (renamed from doc/dw-viewport-with-scrollbar.png)bin755 -> 755 bytes
-rw-r--r--devdoc/dw-viewport-without-scrollbar.png (renamed from doc/dw-viewport-without-scrollbar.png)bin542 -> 542 bytes
-rw-r--r--devdoc/dw-widget-sizes.doc277
-rw-r--r--devdoc/fltk-problems.doc (renamed from doc/fltk-problems.doc)0
-rw-r--r--devdoc/index.doc (renamed from doc/index.doc)0
-rw-r--r--devdoc/lout.doc (renamed from doc/lout.doc)0
-rw-r--r--devdoc/not-so-simple-container.pngbin0 -> 19319 bytes
-rw-r--r--devdoc/not-so-simple-container.svg785
-rw-r--r--devdoc/rounding-errors.doc (renamed from doc/rounding-errors.doc)0
-rw-r--r--devdoc/uml-legend.doc (renamed from doc/uml-legend.doc)0
-rw-r--r--dillorc25
-rw-r--r--doc/Dw.txt11
-rw-r--r--doc/Imgbuf.txt177
-rw-r--r--doc/Makefile.am43
-rw-r--r--doc/README54
-rw-r--r--doc/Selection.txt149
-rw-r--r--doc/dillo.1.in6
-rw-r--r--doc/dw-example-screenshot.pngbin3808 -> 0 bytes
-rw-r--r--doc/dw-size-of-widget.pngbin2825 -> 0 bytes
-rw-r--r--doc/dw-style-box-model.pngbin5116 -> 0 bytes
-rw-r--r--doc/dw-style-length-absolute.pngbin756 -> 0 bytes
-rw-r--r--doc/dw-style-length-percentage.pngbin1105 -> 0 bytes
-rw-r--r--doc/dw-style-length-relative.pngbin1083 -> 0 bytes
-rw-r--r--doc/dw-widget-sizes.doc186
-rw-r--r--doc/not-so-simple-container.pngbin13812 -> 0 bytes
-rw-r--r--dpi/cookies.c38
-rw-r--r--dpi/downloads.cc2
-rw-r--r--dpid/dpid_common.h1
-rw-r--r--dw/Makefile.am13
-rw-r--r--dw/alignedtablecell.cc205
-rw-r--r--dw/alignedtablecell.hh47
-rw-r--r--dw/bullet.cc20
-rw-r--r--dw/bullet.hh3
-rw-r--r--dw/findtext.cc6
-rw-r--r--dw/fltkimgbuf.cc38
-rw-r--r--dw/fltkplatform.hh2
-rw-r--r--dw/fltkui.cc76
-rw-r--r--dw/fltkui.hh4
-rw-r--r--dw/hyphenator.cc2
-rw-r--r--dw/image.cc171
-rw-r--r--dw/image.hh7
-rw-r--r--dw/imgrenderer.cc19
-rw-r--r--dw/iterator.cc146
-rw-r--r--dw/iterator.hh15
-rw-r--r--dw/layout.cc200
-rw-r--r--dw/layout.hh43
-rw-r--r--dw/listitem.cc7
-rw-r--r--dw/listitem.hh2
-rw-r--r--dw/outofflowmgr.cc2293
-rw-r--r--dw/outofflowmgr.hh435
-rw-r--r--dw/regardingborder.cc39
-rw-r--r--dw/regardingborder.hh27
-rw-r--r--dw/ruler.cc41
-rw-r--r--dw/ruler.hh21
-rw-r--r--dw/selection.hh2
-rw-r--r--dw/simpletablecell.cc129
-rw-r--r--dw/simpletablecell.hh38
-rw-r--r--dw/style.cc77
-rw-r--r--dw/style.hh64
-rw-r--r--dw/table.cc1653
-rw-r--r--dw/table.hh140
-rw-r--r--dw/table_iterator.cc134
-rw-r--r--dw/tablecell.cc146
-rw-r--r--dw/tablecell.hh37
-rw-r--r--dw/textblock.cc1528
-rw-r--r--dw/textblock.hh429
-rw-r--r--dw/textblock_iterator.cc360
-rw-r--r--dw/textblock_linebreaking.cc1750
-rw-r--r--dw/types.cc85
-rw-r--r--dw/types.hh32
-rw-r--r--dw/ui.cc195
-rw-r--r--dw/ui.hh62
-rw-r--r--dw/widget.cc1202
-rw-r--r--dw/widget.hh252
-rw-r--r--lout/Makefile.am3
-rw-r--r--lout/container.cc156
-rw-r--r--lout/container.hh70
-rw-r--r--lout/debug.hh249
-rw-r--r--lout/identity.cc23
-rw-r--r--lout/identity.hh6
-rw-r--r--lout/misc.cc6
-rw-r--r--lout/misc.hh67
-rw-r--r--lout/msg.h1
-rw-r--r--lout/object.cc61
-rw-r--r--lout/object.hh57
-rw-r--r--lout/signal.hh6
-rw-r--r--lout/unicode.cc20
-rw-r--r--src/IO/IO.c34
-rw-r--r--src/IO/Makefile.am7
-rw-r--r--src/IO/Url.h5
-rw-r--r--src/IO/about.c2
-rw-r--r--src/IO/http.c670
-rw-r--r--src/IO/tls.c1258
-rw-r--r--src/IO/tls.h49
-rw-r--r--src/Makefile.am9
-rw-r--r--src/cache.c140
-rw-r--r--src/cache.h3
-rw-r--r--src/capi.c225
-rw-r--r--src/colors.c2
-rw-r--r--src/cookies.h3
-rw-r--r--src/css.cc2
-rw-r--r--src/css.hh2
-rw-r--r--src/cssparser.cc68
-rw-r--r--src/decode.c26
-rw-r--r--src/decode.h16
-rw-r--r--src/dialog.cc31
-rw-r--r--src/dillo.cc26
-rw-r--r--src/form.cc4
-rw-r--r--src/gif.c4
-rw-r--r--src/hsts.c364
-rw-r--r--src/hsts.h19
-rwxr-xr-xsrc/hsts_preload2037
-rw-r--r--src/html.cc529
-rw-r--r--src/html_charrefs.h2138
-rw-r--r--src/klist.c2
-rw-r--r--src/menu.cc35
-rw-r--r--src/nav.c1
-rw-r--r--src/paths.hh1
-rw-r--r--src/png.c2
-rw-r--r--src/prefs.c4
-rw-r--r--src/prefs.h4
-rw-r--r--src/prefsparser.cc5
-rw-r--r--src/styleengine.cc46
-rw-r--r--src/styleengine.hh1
-rw-r--r--src/table.cc5
-rw-r--r--src/tipwin.cc2
-rw-r--r--src/uicmd.cc2
-rw-r--r--src/url.c84
-rw-r--r--src/url.h21
-rw-r--r--src/web.cc2
-rw-r--r--test/Makefile.am25
-rw-r--r--test/containers.cc51
-rw-r--r--test/cookies.c23
-rw-r--r--test/dw_float_test.cc145
-rw-r--r--test/dw_simple_container.cc244
-rw-r--r--test/dw_simple_container.hh56
-rw-r--r--test/dw_simple_container_test.cc114
-rw-r--r--test/dw_table.cc1
-rw-r--r--test/dw_table_aligned.cc6
-rw-r--r--test/floats-and-absolute.html51
-rw-r--r--test/floats-and-margins.html40
-rw-r--r--test/floats-table.html24
-rw-r--r--test/floats-worm.html42
-rw-r--r--test/floats1.html46
-rw-r--r--test/floats2.html17
-rw-r--r--test/floats3.html16
-rw-r--r--test/floats4.html63
-rw-r--r--test/floats5.html16
186 files changed, 22496 insertions, 4440 deletions
diff --git a/.hgignore b/.hgignore
index 9ab76864..f3e9fd41 100644
--- a/.hgignore
+++ b/.hgignore
@@ -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$
diff --git a/AUTHORS b/AUTHORS
index e8f0b466..f92ad655 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -87,3 +87,5 @@ Non-Dillo code:
* src/md5.[ch] contain code by L. Peter Deutsch whose copyright is held by
Aladdin Enterprises.
* src/tipwin.cc contains code by Greg Ercolano.
+* src/IO/tls.c contains code from wget whose copyright is held by the
+ Free Software Foundation.
diff --git a/ChangeLog b/ChangeLog
index 2b41f89b..007a724f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -6,6 +6,38 @@ 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
++- HTML5 character references.
+ - Give images lower priority when requesting resources (responsiveness).
+ - Reuse of connections for HTTP (enable w/ http_persistent_conns in dillorc).
+ - Abort failed queries.
+ Patches: corvid
++- Doxygen fixes.
+ Patch: Jeremy Henty
++- Move HTTPS from dpi into the browser, enable SNI, add certificate hostname
+ checking from wget, check more locations for CA bundles and add
+ --with-ca-certs-file and --with-ca-certs-dir to configure, some improvement
+ to security warning popups, etc.
+ Patch: corvid, Benjamin Johnson
+
+-----------------------------------------------------------------------------
+
dillo-3.0.5 [June 30, 2015]
+- Image buffer/cache improvements.
diff --git a/Doxyfile b/Doxyfile
index 971cf7be..68ffcea9 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -1,1517 +1,2368 @@
-# Doxyfile 1.5.8
+# Doxyfile 1.8.9.1
# This file describes the settings to be used by the documentation system
-# doxygen (www.doxygen.org) for a project
+# doxygen (www.doxygen.org) for a project.
#
-# All text after a hash (#) is considered a comment and will be ignored
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
# The format is:
-# TAG = value [value, ...]
-# For lists items can also be appended using:
-# TAG += value [value, ...]
-# Values that contain spaces should be placed between quotes (" ")
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
#---------------------------------------------------------------------------
# Project related configuration options
#---------------------------------------------------------------------------
-# This tag specifies the encoding used for all characters in the config file
-# that follow. The default is UTF-8 which is also the encoding used for all
-# text before the first occurrence of this tag. Doxygen uses libiconv (or the
-# iconv built into libc) for the transcoding. See
-# http://www.gnu.org/software/libiconv for the list of possible encodings.
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all text
+# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
+# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
+# for the list of possible encodings.
+# The default value is: UTF-8.
DOXYFILE_ENCODING = UTF-8
-# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
-# by quotes) that should identify the project.
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
PROJECT_NAME = Dillo
-# The PROJECT_NUMBER tag can be used to enter a project or revision number.
-# This could be handy for archiving the generated documentation or
-# if some version control system is used.
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF =
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
-PROJECT_NUMBER =
+PROJECT_LOGO =
-# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
-# base path where the generated documentation will be put.
-# If a relative path is entered, it will be relative to the location
-# where doxygen was started. If left blank the current directory will be used.
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
-OUTPUT_DIRECTORY =
+OUTPUT_DIRECTORY =
-# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
-# 4096 sub-directories (in 2 levels) under the output directory of each output
-# format and will distribute the generated files over these directories.
-# Enabling this option can be useful when feeding doxygen a huge amount of
-# source files, where putting all generated files in the same directory would
-# otherwise cause performance problems for the file system.
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
CREATE_SUBDIRS = NO
-# The OUTPUT_LANGUAGE tag is used to specify the language in which all
-# documentation generated by doxygen is written. Doxygen will use this
-# information to generate all constant output in the proper language.
-# The default language is English, other supported languages are:
-# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
-# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek,
-# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages),
-# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish,
-# Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, Slovene,
-# Spanish, Swedish, and Ukrainian.
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
OUTPUT_LANGUAGE = English
-# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
-# include brief member descriptions after the members that are listed in
-# the file and class documentation (similar to JavaDoc).
-# Set to NO to disable this.
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
BRIEF_MEMBER_DESC = YES
-# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
-# the brief description of a member or function before the detailed description.
-# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
# brief descriptions will be completely suppressed.
+# The default value is: YES.
REPEAT_BRIEF = YES
-# This tag implements a quasi-intelligent brief description abbreviator
-# that is used to form the text in various listings. Each string
-# in this list, if found as the leading text of the brief description, will be
-# stripped from the text and the result after processing the whole list, is
-# used as the annotated text. Otherwise, the brief description is used as-is.
-# If left blank, the following values are used ("$name" is automatically
-# replaced with the name of the entity): "The $name class" "The $name widget"
-# "The $name file" "is" "provides" "specifies" "contains"
-# "represents" "a" "an" "the"
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
-ABBREVIATE_BRIEF =
+ABBREVIATE_BRIEF =
-# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
-# Doxygen will generate a detailed section even if there is only a brief
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
# description.
+# The default value is: NO.
ALWAYS_DETAILED_SEC = NO
-# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
-# inherited members of a class in the documentation of that class as if those
-# members were ordinary class members. Constructors, destructors and assignment
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
# operators of the base classes will not be shown.
+# The default value is: NO.
INLINE_INHERITED_MEMB = NO
-# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
-# path before files name in the file list and in the header files. If set
-# to NO the shortest path that makes the file name unique will be used.
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
FULL_PATH_NAMES = YES
-# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
-# can be used to strip a user-defined part of the path. Stripping is
-# only done if one of the specified strings matches the left-hand part of
-# the path. The tag can be used to show relative paths in the file list.
-# If left blank the directory from which doxygen is run is used as the
-# path to strip.
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
-STRIP_FROM_PATH =
+STRIP_FROM_PATH =
-# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
-# the path mentioned in the documentation of a class, which tells
-# the reader which header file to include in order to use a class.
-# If left blank only the name of the header file containing the class
-# definition is used. Otherwise one should specify the include paths that
-# are normally passed to the compiler using the -I flag.
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
-STRIP_FROM_INC_PATH =
+STRIP_FROM_INC_PATH =
-# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
-# (but less readable) file names. This can be useful is your file systems
-# doesn't support long names like on DOS, Mac, or CD-ROM.
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
SHORT_NAMES = NO
-# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
-# will interpret the first line (until the first dot) of a JavaDoc-style
-# comment as the brief description. If set to NO, the JavaDoc
-# comments will behave just like regular Qt-style comments
-# (thus requiring an explicit @brief command for a brief description.)
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
JAVADOC_AUTOBRIEF = NO
-# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
-# interpret the first line (until the first dot) of a Qt-style
-# comment as the brief description. If set to NO, the comments
-# will behave just like regular Qt-style comments (thus requiring
-# an explicit \brief command for a brief description.)
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
QT_AUTOBRIEF = NO
-# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
-# treat a multi-line C++ special comment block (i.e. a block of //! or ///
-# comments) as a brief description. This used to be the default behaviour.
-# The new default is to treat a multi-line C++ comment block as a detailed
-# description. Set this tag to YES if you prefer the old behaviour instead.
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
MULTILINE_CPP_IS_BRIEF = NO
-# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
-# member inherits the documentation from any documented member that it
-# re-implements.
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
INHERIT_DOCS = YES
-# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
-# a new page for each member. If set to NO, the documentation of a member will
-# be part of the file/class/namespace that contains it.
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
SEPARATE_MEMBER_PAGES = NO
-# The TAB_SIZE tag can be used to set the number of spaces in a tab.
-# Doxygen uses this value to replace tabs by spaces in code fragments.
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
TAB_SIZE = 8
-# This tag can be used to specify a number of aliases that acts
-# as commands in the documentation. An alias has the form "name=value".
-# For example adding "sideeffect=\par Side Effects:\n" will allow you to
-# put the command \sideeffect (or @sideeffect) in the documentation, which
-# will result in a user-defined paragraph with heading "Side Effects:".
-# You can put \n's in the value part of an alias to insert newlines.
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines.
+
+ALIASES =
-ALIASES =
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
-# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
-# sources only. Doxygen will then generate output that is more tailored for C.
-# For instance, some of the names that are used will be different. The list
-# of all members will be omitted, etc.
+TCL_SUBST =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
OPTIMIZE_OUTPUT_FOR_C = NO
-# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
-# sources only. Doxygen will then generate output that is more tailored for
-# Java. For instance, namespaces will be presented as packages, qualified
-# scopes will look different, etc.
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
OPTIMIZE_OUTPUT_JAVA = NO
-# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
-# sources only. Doxygen will then generate output that is more tailored for
-# Fortran.
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
OPTIMIZE_FOR_FORTRAN = NO
-# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
-# sources. Doxygen will then generate output that is tailored for
-# VHDL.
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
OPTIMIZE_OUTPUT_VHDL = NO
-# Doxygen selects the parser to use depending on the extension of the files it parses.
-# With this tag you can assign which parser to use for a given extension.
-# Doxygen has a built-in mapping, but you can override or extend it using this tag.
-# The format is ext=language, where ext is a file extension, and language is one of
-# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP,
-# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat
-# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran),
-# use: inc=Fortran f=C
-
-EXTENSION_MAPPING =
-
-# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
-# to include (a tag file for) the STL sources as input, then you should
-# set this tag to YES in order to let doxygen match functions declarations and
-# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
-# func(std::string) {}). This also make the inheritance and collaboration
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
+# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
+# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
+# Fortran. In the later case the parser tries to guess whether the code is fixed
+# or free formatted code, this is the default for Fortran type files), VHDL. For
+# instance to make doxygen treat .inc files as Fortran files (default is PHP),
+# and .f files as C (default is Fortran), use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
+
+EXTENSION_MAPPING =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT = YES
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
BUILTIN_STL_SUPPORT = NO
-# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
# enable parsing support.
+# The default value is: NO.
CPP_CLI_SUPPORT = NO
-# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
-# Doxygen will parse them like normal C++ but will assume all classes use public
-# instead of private inheritance when no explicit protection keyword is present.
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
SIP_SUPPORT = NO
-# For Microsoft's IDL there are propget and propput attributes to indicate getter
-# and setter methods for a property. Setting this option to YES (the default)
-# will make doxygen to replace the get and set methods by a property in the
-# documentation. This will only work if the methods are indeed getting or
-# setting a simple type. If this is not the case, or you want to show the
-# methods anyway, you should set this option to NO.
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
IDL_PROPERTY_SUPPORT = YES
-# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
-# tag is set to YES, then doxygen will reuse the documentation of the first
-# member in the group (if any) for the other members of the group. By default
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
# all members of a group must be documented explicitly.
+# The default value is: NO.
DISTRIBUTE_GROUP_DOC = NO
-# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
-# the same type (for instance a group of public functions) to be put as a
-# subgroup of that type (e.g. under the Public Functions section). Set it to
-# NO to prevent subgrouping. Alternatively, this can be done per class using
-# the \nosubgrouping command.
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
SUBGROUPING = YES
-# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
-# is documented as struct, union, or enum with the name of the typedef. So
-# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
-# with name TypeT. When disabled the typedef will appear as a member of a file,
-# namespace, or class. And the struct will be named TypeS. This can typically
-# be useful for C code in case the coding convention dictates that all compound
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
TYPEDEF_HIDES_STRUCT = NO
-# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
-# determine which symbols to keep in memory and which to flush to disk.
-# When the cache is full, less often used symbols will be written to disk.
-# For small to medium size projects (<1000 input files) the default value is
-# probably good enough. For larger projects a too small cache size can cause
-# doxygen to be busy swapping symbols to and from disk most of the time
-# causing a significant performance penality.
-# If the system has enough physical memory increasing the cache will improve the
-# performance by keeping more symbols in memory. Note that the value works on
-# a logarithmic scale so increasing the size by one will rougly double the
-# memory usage. The cache size is given by this formula:
-# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
-# corresponding to a cache size of 2^16 = 65536 symbols
-
-SYMBOL_CACHE_SIZE = 0
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE = 0
#---------------------------------------------------------------------------
# Build related configuration options
#---------------------------------------------------------------------------
-# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
-# documentation are documented, even if no documentation was available.
-# Private class members and static file members will be hidden unless
-# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
EXTRACT_ALL = YES
-# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
-# will be included in the documentation.
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
EXTRACT_PRIVATE = YES
-# If the EXTRACT_STATIC tag is set to YES all static members of a file
-# will be included in the documentation.
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE = NO
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
EXTRACT_STATIC = YES
-# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
-# defined locally in source files will be included in the documentation.
-# If set to NO only classes defined in header files are included.
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
EXTRACT_LOCAL_CLASSES = NO
-# This flag is only useful for Objective-C code. When set to YES local
-# methods, which are defined in the implementation section but not in
-# the interface are included in the documentation.
-# If set to NO (the default) only methods in the interface are included.
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
EXTRACT_LOCAL_METHODS = NO
-# If this flag is set to YES, the members of anonymous namespaces will be
-# extracted and appear in the documentation as a namespace called
-# 'anonymous_namespace{file}', where file will be replaced with the base
-# name of the file that contains the anonymous namespace. By default
-# anonymous namespace are hidden.
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
EXTRACT_ANON_NSPACES = NO
-# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
-# undocumented members of documented classes, files or namespaces.
-# If set to NO (the default) these members will be included in the
-# various overviews, but no documentation section is generated.
-# This option has no effect if EXTRACT_ALL is enabled.
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
HIDE_UNDOC_MEMBERS = NO
-# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
-# undocumented classes that are normally visible in the class hierarchy.
-# If set to NO (the default) these classes will be included in the various
-# overviews. This option has no effect if EXTRACT_ALL is enabled.
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
HIDE_UNDOC_CLASSES = NO
-# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
-# friend (class|struct|union) declarations.
-# If set to NO (the default) these declarations will be included in the
-# documentation.
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# (class|struct|union) declarations. If set to NO, these declarations will be
+# included in the documentation.
+# The default value is: NO.
HIDE_FRIEND_COMPOUNDS = NO
-# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
-# documentation blocks found inside the body of a function.
-# If set to NO (the default) these blocks will be appended to the
-# function's detailed documentation block.
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
HIDE_IN_BODY_DOCS = NO
-# The INTERNAL_DOCS tag determines if documentation
-# that is typed after a \internal command is included. If the tag is set
-# to NO (the default) then the documentation will be excluded.
-# Set it to YES to include the internal documentation.
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
INTERNAL_DOCS = NO
-# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
-# file names in lower-case letters. If set to YES upper-case letters are also
-# allowed. This is useful if you have classes or files whose names only differ
-# in case and if your file system supports case sensitive file names. Windows
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES, upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
# and Mac users are advised to set this option to NO.
+# The default value is: system dependent.
CASE_SENSE_NAMES = YES
-# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
-# will show members with their full class and namespace scopes in the
-# documentation. If set to YES the scope will be hidden.
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
HIDE_SCOPE_NAMES = NO
-# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
-# will put a list of the files that are included by a file in the documentation
-# of that file.
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
SHOW_INCLUDE_FILES = YES
-# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
-# is inserted in the documentation for inline members.
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
INLINE_INFO = YES
-# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
-# will sort the (detailed) documentation of file and class members
-# alphabetically by member name. If set to NO the members will appear in
-# declaration order.
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
SORT_MEMBER_DOCS = YES
-# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
-# brief documentation of file, namespace and class members alphabetically
-# by member name. If set to NO (the default) the members will appear in
-# declaration order.
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
SORT_BRIEF_DOCS = NO
-# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
-# hierarchy of group names into alphabetical order. If set to NO (the default)
-# the group names will appear in their defined order.
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
SORT_GROUP_NAMES = NO
-# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
-# sorted by fully-qualified names, including namespaces. If set to
-# NO (the default), the class list will be sorted only by class name,
-# not including the namespace part.
-# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
-# Note: This option applies only to the class list, not to the
-# alphabetical list.
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
SORT_BY_SCOPE_NAME = NO
-# The GENERATE_TODOLIST tag can be used to enable (YES) or
-# disable (NO) the todo list. This list is created by putting \todo
-# commands in the documentation.
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
GENERATE_TODOLIST = YES
-# The GENERATE_TESTLIST tag can be used to enable (YES) or
-# disable (NO) the test list. This list is created by putting \test
-# commands in the documentation.
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
GENERATE_TESTLIST = YES
-# The GENERATE_BUGLIST tag can be used to enable (YES) or
-# disable (NO) the bug list. This list is created by putting \bug
-# commands in the documentation.
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
GENERATE_BUGLIST = YES
-# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
-# disable (NO) the deprecated list. This list is created by putting
-# \deprecated commands in the documentation.
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
GENERATE_DEPRECATEDLIST= YES
-# The ENABLED_SECTIONS tag can be used to enable conditional
-# documentation sections, marked by \if sectionname ... \endif.
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
-ENABLED_SECTIONS =
+ENABLED_SECTIONS =
-# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
-# the initial value of a variable or define consists of for it to appear in
-# the documentation. If the initializer consists of more lines than specified
-# here it will be hidden. Use a value of 0 to hide initializers completely.
-# The appearance of the initializer of individual variables and defines in the
-# documentation can be controlled using \showinitializer or \hideinitializer
-# command in the documentation regardless of this setting.
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
MAX_INITIALIZER_LINES = 30
-# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
-# at the bottom of the documentation of classes and structs. If set to YES the
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
SHOW_USED_FILES = YES
-# If the sources in your project are distributed over multiple directories
-# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
-# in the documentation. The default is NO.
-
-SHOW_DIRECTORIES = NO
-
-# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
-# This will remove the Files entry from the Quick Index and from the
-# Folder Tree View (if specified). The default is YES.
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
SHOW_FILES = YES
-# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
-# Namespaces page.
-# This will remove the Namespaces entry from the Quick Index
-# and from the Folder Tree View (if specified). The default is YES.
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
SHOW_NAMESPACES = YES
-# The FILE_VERSION_FILTER tag can be used to specify a program or script that
-# doxygen should invoke to get the current version for each file (typically from
-# the version control system). Doxygen will invoke the program by executing (via
-# popen()) the command <command> <input-file>, where <command> is the value of
-# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
-# provided by doxygen. Whatever the program writes to standard output
-# is used as the file version. See the manual for examples.
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
-FILE_VERSION_FILTER =
+LAYOUT_FILE =
-# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by
-# doxygen. The layout file controls the global structure of the generated output files
-# in an output format independent way. The create the layout file that represents
-# doxygen's defaults, run doxygen with the -l option. You can optionally specify a
-# file name after the option, if omitted DoxygenLayout.xml will be used as the name
-# of the layout file.
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
-LAYOUT_FILE =
+CITE_BIB_FILES =
#---------------------------------------------------------------------------
-# configuration options related to warning and progress messages
+# Configuration options related to warning and progress messages
#---------------------------------------------------------------------------
-# The QUIET tag can be used to turn on/off the messages that are generated
-# by doxygen. Possible values are YES and NO. If left blank NO is used.
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
QUIET = NO
-# The WARNINGS tag can be used to turn on/off the warning messages that are
-# generated by doxygen. Possible values are YES and NO. If left blank
-# NO is used.
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
WARNINGS = YES
-# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
-# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
-# automatically be disabled.
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
WARN_IF_UNDOCUMENTED = YES
-# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
-# potential errors in the documentation, such as not documenting some
-# parameters in a documented function, or documenting parameters that
-# don't exist or using markup commands wrongly.
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
WARN_IF_DOC_ERROR = YES
-# This WARN_NO_PARAMDOC option can be abled to get warnings for
-# functions that are documented, but have no documentation for their parameters
-# or return value. If set to NO (the default) doxygen will only warn about
-# wrong or incomplete parameter documentation, but not about the absence of
-# documentation.
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong or incomplete
+# parameter documentation, but not about the absence of documentation.
+# The default value is: NO.
WARN_NO_PARAMDOC = NO
-# The WARN_FORMAT tag determines the format of the warning messages that
-# doxygen can produce. The string should contain the $file, $line, and $text
-# tags, which will be replaced by the file and line number from which the
-# warning originated and the warning text. Optionally the format may contain
-# $version, which will be replaced by the version of the file (if it could
-# be obtained via FILE_VERSION_FILTER)
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
WARN_FORMAT = "$file:$line: $text"
-# The WARN_LOGFILE tag can be used to specify a file to which warning
-# and error messages should be written. If left blank the output is written
-# to stderr.
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
-WARN_LOGFILE =
+WARN_LOGFILE =
#---------------------------------------------------------------------------
-# configuration options related to the input files
+# Configuration options related to the input files
#---------------------------------------------------------------------------
-# The INPUT tag can be used to specify the files and/or directories that contain
-# documented source files. You may enter file names like "myfile.cpp" or
-# directories like "/usr/src/myproject". Separate the files or directories
-# with spaces.
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces.
+# Note: If this tag is empty the current directory is searched.
-INPUT =
+INPUT =
-# This tag can be used to specify the character encoding of the source files
-# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
-# also the default input encoding. Doxygen uses libiconv (or the iconv built
-# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
-# the list of possible encodings.
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: http://www.gnu.org/software/libiconv) for the list of
+# possible encodings.
+# The default value is: UTF-8.
INPUT_ENCODING = UTF-8
-# If the value of the INPUT tag contains directories, you can use the
-# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
-# and *.h) to filter out the source-files in the directories. If left
-# blank the following patterns are tested:
-# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
-# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank the
+# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii,
+# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp,
+# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown,
+# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf,
+# *.qsf, *.as and *.js.
FILE_PATTERNS = *.cc \
*.hh \
*.doc
-# The RECURSIVE tag can be used to turn specify whether or not subdirectories
-# should be searched for input files as well. Possible values are YES and NO.
-# If left blank NO is used.
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
RECURSIVE = YES
-# The EXCLUDE tag can be used to specify files and/or directories that should
-# excluded from the INPUT source files. This way you can easily exclude a
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
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
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
# from the input.
+# The default value is: NO.
EXCLUDE_SYMLINKS = NO
-# If the value of the INPUT tag contains directories, you can use the
-# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
-# certain files from those directories. Note that the wildcards are matched
-# against the file with absolute path, so to exclude all test directories
-# for example use the pattern */test/*
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
-EXCLUDE_PATTERNS =
+EXCLUDE_PATTERNS =
-# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
-# (namespaces, classes, functions, etc.) that should be excluded from the
-# output. The symbol name can be a fully qualified name, a word, or if the
-# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
-EXCLUDE_SYMBOLS =
+EXCLUDE_SYMBOLS =
-# The EXAMPLE_PATH tag can be used to specify one or more files or
-# directories that contain example code fragments that are included (see
-# the \include command).
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
-EXAMPLE_PATH =
+EXAMPLE_PATH =
-# If the value of the EXAMPLE_PATH tag contains directories, you can use the
-# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
-# and *.h) to filter out the source-files in the directories. If left
-# blank all files are included.
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
-EXAMPLE_PATTERNS =
+EXAMPLE_PATTERNS =
-# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
-# searched for input files to be used with the \include or \dontinclude
-# commands irrespective of the value of the RECURSIVE tag.
-# Possible values are YES and NO. If left blank NO is used.
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
EXAMPLE_RECURSIVE = NO
-# The IMAGE_PATH tag can be used to specify one or more files or
-# directories that contain image that are included in the documentation (see
-# the \image command).
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
-IMAGE_PATH = doc
+IMAGE_PATH = devdoc
-# The INPUT_FILTER tag can be used to specify a program that doxygen should
-# invoke to filter for each input file. Doxygen will invoke the filter program
-# by executing (via popen()) the command <filter> <input-file>, where <filter>
-# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
-# input file. Doxygen will then use the output that the filter program writes
-# to standard output.
-# If FILTER_PATTERNS is specified, this tag will be
-# ignored.
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
-INPUT_FILTER =
+INPUT_FILTER =
-# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
-# basis.
-# Doxygen will compare the file name with each pattern and apply the
-# filter if there is a match.
-# The filters are a list of the form:
-# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
-# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
-# is applied to all files.
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
-FILTER_PATTERNS =
+FILTER_PATTERNS =
-# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
-# INPUT_FILTER) will be used to filter the input files when producing source
-# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
FILTER_SOURCE_FILES = NO
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
#---------------------------------------------------------------------------
-# configuration options related to source browsing
+# Configuration options related to source browsing
#---------------------------------------------------------------------------
-# If the SOURCE_BROWSER tag is set to YES then a list of source files will
-# be generated. Documented entities will be cross-referenced with these sources.
-# Note: To get rid of all source code in the generated output, make sure also
-# VERBATIM_HEADERS is set to NO.
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
SOURCE_BROWSER = NO
-# Setting the INLINE_SOURCES tag to YES will include the body
-# of functions and classes directly in the documentation.
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
INLINE_SOURCES = NO
-# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
-# doxygen to hide any special comment blocks from generated source code
-# fragments. Normal C and C++ comments will always remain visible.
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
STRIP_CODE_COMMENTS = YES
-# If the REFERENCED_BY_RELATION tag is set to YES
-# then for each documented function all documented
-# functions referencing it will be listed.
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# function all documented functions referencing it will be listed.
+# The default value is: NO.
REFERENCED_BY_RELATION = YES
-# If the REFERENCES_RELATION tag is set to YES
-# then for each documented function all documented entities
-# called/used by that function will be listed.
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
REFERENCES_RELATION = YES
-# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
-# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
-# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
-# link to the source code.
-# Otherwise they will link to the documentation.
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
REFERENCES_LINK_SOURCE = YES
-# If the USE_HTAGS tag is set to YES then the references to source code
-# will point to the HTML generated by the htags(1) tool instead of doxygen
-# built-in source browser. The htags tool is part of GNU's global source
-# tagging system (see http://www.gnu.org/software/global/global.html). You
-# will need version 4.8.6 or higher.
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see http://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
USE_HTAGS = NO
-# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
-# will generate a verbatim copy of the header file for each class for
-# which an include is specified. Set to NO to disable this.
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
VERBATIM_HEADERS = YES
#---------------------------------------------------------------------------
-# configuration options related to the alphabetical class index
+# Configuration options related to the alphabetical class index
#---------------------------------------------------------------------------
-# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
-# of all compounds will be generated. Enable this if the project
-# contains a lot of classes, structs, unions or interfaces.
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
ALPHABETICAL_INDEX = YES
-# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
-# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
-# in which this list will be split (can be a number in the range [1..20])
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
COLS_IN_ALPHA_INDEX = 3
-# In case all classes in a project start with a common prefix, all
-# classes will be put under the same header in the alphabetical index.
-# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
-# should be ignored while generating the index headers.
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
-IGNORE_PREFIX =
+IGNORE_PREFIX =
#---------------------------------------------------------------------------
-# configuration options related to the HTML output
+# Configuration options related to the HTML output
#---------------------------------------------------------------------------
-# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
-# generate HTML output.
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
GENERATE_HTML = YES
-# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `html' will be used as the default path.
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_OUTPUT = html
-# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
-# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
-# doxygen will generate files with .html extension.
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_FILE_EXTENSION = .html
-# The HTML_HEADER tag can be used to specify a personal HTML header for
-# each generated HTML page. If it is left blank doxygen will generate a
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
# standard header.
-
-HTML_HEADER =
-
-# The HTML_FOOTER tag can be used to specify a personal HTML footer for
-# each generated HTML page. If it is left blank doxygen will generate a
-# standard footer.
-
-HTML_FOOTER =
-
-# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
-# style sheet that is used by each HTML page. It can be used to
-# fine-tune the look of the HTML output. If the tag is left blank doxygen
-# will generate a default style sheet. Note that doxygen will try to copy
-# the style sheet file to the HTML output directory, so don't put your own
-# stylesheet in the HTML output directory as well, or it will be erased!
-
-HTML_STYLESHEET =
-
-# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
-# files or namespaces will be aligned in HTML using tables. If set to
-# NO a bullet list will be used.
-
-HTML_ALIGN_MEMBERS = YES
-
-# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
-# documentation will contain sections that can be hidden and shown after the
-# page has loaded. For this to work a browser that supports
-# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
-# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_DYNAMIC_SECTIONS = NO
-# If the GENERATE_DOCSET tag is set to YES, additional index files
-# will be generated that can be used as input for Apple's Xcode 3
-# integrated development environment, introduced with OSX 10.5 (Leopard).
-# To create a documentation set, doxygen will generate a Makefile in the
-# HTML output directory. Running make will produce the docset in that
-# directory and running "make install" will install the docset in
-# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
-# it at startup.
-# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information.
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: http://developer.apple.com/tools/xcode/), introduced with
+# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
GENERATE_DOCSET = NO
-# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
-# feed. A documentation feed provides an umbrella under which multiple
-# documentation sets from a single provider (such as a company or product suite)
-# can be grouped.
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
DOCSET_FEEDNAME = "Doxygen generated docs"
-# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
-# should uniquely identify the documentation set bundle. This should be a
-# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
-# will append .docset to the name.
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
DOCSET_BUNDLE_ID = org.doxygen.Project
-# If the GENERATE_HTMLHELP tag is set to YES, additional index files
-# will be generated that can be used as input for tools like the
-# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
-# of the generated HTML documentation.
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
GENERATE_HTMLHELP = NO
-# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
-# be used to specify the file name of the resulting .chm file. You
-# can add a path in front of the file if the result should not be
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-CHM_FILE =
+CHM_FILE =
-# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
-# be used to specify the location (absolute path including file name) of
-# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
-# the HTML help compiler on the generated index.hhp.
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-HHC_LOCATION =
+HHC_LOCATION =
-# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
-# controls if a separate .chi index file is generated (YES) or that
-# it should be included in the master .chm file (NO).
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the master .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
GENERATE_CHI = NO
-# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
-# is used to encode HtmlHelp index (hhk), content (hhc) and project file
-# content.
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-CHM_INDEX_ENCODING =
+CHM_INDEX_ENCODING =
-# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
-# controls whether a binary table of contents is generated (YES) or a
-# normal table of contents (NO) in the .chm file.
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
BINARY_TOC = NO
-# The TOC_EXPAND flag can be set to YES to add extra items for group members
-# to the contents of the HTML help documentation and to the tree view.
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
TOC_EXPAND = NO
-# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER
-# are set, an additional index file will be generated that can be used as input for
-# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated
-# HTML documentation.
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
GENERATE_QHP = NO
-# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
-# be used to specify the file name of the resulting .qch file.
-# The path specified is relative to the HTML output folder.
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE =
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
+# folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER = devdoc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS =
+
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
-QCH_FILE =
+DISABLE_INDEX = NO
-# The QHP_NAMESPACE tag specifies the namespace to use when generating
-# Qt Help Project output. For more information please see
-# http://doc.trolltech.com/qthelpproject.html#namespace
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
-QHP_NAMESPACE =
+GENERATE_TREEVIEW = NO
-# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
-# Qt Help Project output. For more information please see
-# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
-QHP_VIRTUAL_FOLDER = doc
+ENUM_VALUES_PER_LINE = 4
-# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add.
-# For more information please see
-# http://doc.trolltech.com/qthelpproject.html#custom-filters
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
-QHP_CUST_FILTER_NAME =
+TREEVIEW_WIDTH = 250
-# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see
-# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">Qt Help Project / Custom Filters</a>.
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
-QHP_CUST_FILTER_ATTRS =
+EXT_LINKS_IN_WINDOW = NO
-# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's
-# filter section matches.
-# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">Qt Help Project / Filter Attributes</a>.
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
-QHP_SECT_FILTER_ATTRS =
+FORMULA_FONTSIZE = 10
-# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
-# be used to specify the location of Qt's qhelpgenerator.
-# If non-empty doxygen will try to run qhelpgenerator on the generated
-# .qhp file.
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# http://www.mathjax.org) which uses client side Javascript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX = NO
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from http://www.mathjax.org before deployment.
+# The default value is: http://cdn.mathjax.org/mathjax/latest.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
-QHG_LOCATION =
+SEARCHENGINE = NO
-# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
-# top of each HTML page. The value NO (the default) enables the index and
-# the value YES disables it.
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using Javascript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
-DISABLE_INDEX = NO
+EXTERNAL_SEARCH = NO
-# This tag can be used to set the number of enum values (range [1..20])
-# that doxygen will group on one line in the generated HTML documentation.
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
-ENUM_VALUES_PER_LINE = 4
+SEARCHENGINE_URL =
-# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
-# structure should be generated to display hierarchical information.
-# If the tag value is set to FRAME, a side panel will be generated
-# containing a tree-like index structure (just like the one that
-# is generated for HTML Help). For this to work a browser that supports
-# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+,
-# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are
-# probably better off using the HTML help feature. Other possible values
-# for this tag are: HIERARCHIES, which will generate the Groups, Directories,
-# and Class Hierarchy pages using a tree view instead of an ordered list;
-# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which
-# disables this behavior completely. For backwards compatibility with previous
-# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE
-# respectively.
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
-GENERATE_TREEVIEW = NO
+SEARCHDATA_FILE = searchdata.xml
-# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
-# used to set the initial width (in pixels) of the frame in which the tree
-# is shown.
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
-TREEVIEW_WIDTH = 250
+EXTERNAL_SEARCH_ID =
-# Use this tag to change the font size of Latex formulas included
-# as images in the HTML documentation. The default is 10. Note that
-# when you change the font size after a successful doxygen run you need
-# to manually remove any form_*.png images from the HTML output directory
-# to force them to be regenerated.
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
-FORMULA_FONTSIZE = 10
+EXTRA_SEARCH_MAPPINGS =
#---------------------------------------------------------------------------
-# configuration options related to the LaTeX output
+# Configuration options related to the LaTeX output
#---------------------------------------------------------------------------
-# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
-# generate Latex output.
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
GENERATE_LATEX = NO
-# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `latex' will be used as the default path.
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_OUTPUT = latex
-# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
-# invoked. If left blank `latex' will be used as the default command name.
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when enabling USE_PDFLATEX this option is only used for generating
+# bitmaps for formulas in the HTML output, but not in the Makefile that is
+# written to the output directory.
+# The default file is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_CMD_NAME = latex
-# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
-# generate index for LaTeX. If left blank `makeindex' will be used as the
-# default command name.
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
MAKEINDEX_CMD_NAME = makeindex
-# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
-# LaTeX documents. This may be useful for small projects and may help to
-# save some trees in general.
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
COMPACT_LATEX = NO
-# The PAPER_TYPE tag can be used to set the paper type that is used
-# by the printer. Possible values are: a4, a4wide, letter, legal and
-# executive. If left blank a4wide will be used.
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
PAPER_TYPE = a4wide
-# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
-# packages that should be included in the LaTeX output.
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. To get the times font for
+# instance you can specify
+# EXTRA_PACKAGES=times
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
-EXTRA_PACKAGES =
+EXTRA_PACKAGES =
-# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
-# the generated latex document. The header should contain everything until
-# the first chapter. If it is left blank doxygen will generate a
-# standard header. Notice: only use this tag if you know what you are doing!
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
+# string, for the replacement values of the other commands the user is referred
+# to HTML_HEADER.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
-LATEX_HEADER =
+LATEX_FOOTER =
-# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
-# is prepared for conversion to pdf (using ps2pdf). The pdf file will
-# contain links (just like the HTML output) instead of page references
-# This makes the output suitable for online browsing using a pdf viewer.
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
PDF_HYPERLINKS = NO
-# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
-# plain latex in the generated Makefile. Set this option to YES to get a
+# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES, to get a
# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
USE_PDFLATEX = NO
-# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
-# command to the generated LaTeX files. This will instruct LaTeX to keep
-# running if errors occur, instead of asking the user for help.
-# This option is also used when generating formulas in HTML.
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_BATCHMODE = NO
-# If LATEX_HIDE_INDICES is set to YES then doxygen will not
-# include the index chapters (such as File Index, Compound Index, etc.)
-# in the output.
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_HIDE_INDICES = NO
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_SOURCE_CODE = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE = plain
+
#---------------------------------------------------------------------------
-# configuration options related to the RTF output
+# Configuration options related to the RTF output
#---------------------------------------------------------------------------
-# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
-# The RTF output is optimized for Word 97 and may not look very pretty with
-# other RTF readers or editors.
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
GENERATE_RTF = NO
-# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `rtf' will be used as the default path.
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
RTF_OUTPUT = rtf
-# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
-# RTF documents. This may be useful for small projects and may help to
-# save some trees in general.
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
COMPACT_RTF = NO
-# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
-# will contain hyperlink fields. The RTF file will
-# contain links (just like the HTML output) instead of page references.
-# This makes the output suitable for online browsing using WORD or other
-# programs which support those fields.
-# Note: wordpad (write) and others do not support links.
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
RTF_HYPERLINKS = NO
-# Load stylesheet definitions from file. Syntax is similar to doxygen's
-# config file, i.e. a series of assignments. You only have to provide
-# replacements, missing definitions are set to their default value.
+# Load stylesheet definitions from file. Syntax is similar to doxygen's config
+# file, i.e. a series of assignments. You only have to provide replacements,
+# missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
-RTF_STYLESHEET_FILE =
+RTF_STYLESHEET_FILE =
-# Set optional variables used in the generation of an rtf document.
-# Syntax is similar to doxygen's config file.
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's config file. A template extensions file can be generated
+# using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
-RTF_EXTENSIONS_FILE =
+RTF_EXTENSIONS_FILE =
+
+# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
+# with syntax highlighting in the RTF output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_SOURCE_CODE = NO
#---------------------------------------------------------------------------
-# configuration options related to the man page output
+# Configuration options related to the man page output
#---------------------------------------------------------------------------
-# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
-# generate man pages
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
GENERATE_MAN = NO
-# The MAN_OUTPUT tag is used to specify where the man pages will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `man' will be used as the default path.
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
MAN_OUTPUT = man
-# The MAN_EXTENSION tag determines the extension that is added to
-# the generated man pages (default is the subroutine's section .3)
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
MAN_EXTENSION = .3
-# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
-# then it will generate one additional man file for each entity
-# documented in the real man page(s). These additional files
-# only source the real man page, but without them the man command
-# would be unable to find the correct page. The default is NO.
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
MAN_LINKS = NO
#---------------------------------------------------------------------------
-# configuration options related to the XML output
+# Configuration options related to the XML output
#---------------------------------------------------------------------------
-# If the GENERATE_XML tag is set to YES Doxygen will
-# generate an XML file that captures the structure of
-# the code including all documentation.
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
GENERATE_XML = NO
-# The XML_OUTPUT tag is used to specify where the XML pages will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `xml' will be used as the default path.
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
XML_OUTPUT = xml
-# The XML_SCHEMA tag can be used to specify an XML schema,
-# which can be used by a validating XML parser to check the
-# syntax of the XML files.
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
-XML_SCHEMA =
+XML_PROGRAMLISTING = YES
-# The XML_DTD tag can be used to specify an XML DTD,
-# which can be used by a validating XML parser to check the
-# syntax of the XML files.
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
-XML_DTD =
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
-# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
-# dump the program listings (including syntax highlighting
-# and cross-referencing information) to the XML output. Note that
-# enabling this will significantly increase the size of the XML output.
+GENERATE_DOCBOOK = NO
-XML_PROGRAMLISTING = YES
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT = docbook
+
+# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
+# program listings (including syntax highlighting and cross-referencing
+# information) to the DOCBOOK output. Note that enabling this will significantly
+# increase the size of the DOCBOOK output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_PROGRAMLISTING = NO
#---------------------------------------------------------------------------
-# configuration options for the AutoGen Definitions output
+# Configuration options for the AutoGen Definitions output
#---------------------------------------------------------------------------
-# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
-# generate an AutoGen Definitions (see autogen.sf.net) file
-# that captures the structure of the code including all
-# documentation. Note that this feature is still experimental
-# and incomplete at the moment.
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://autogen.sf.net) file that captures the
+# structure of the code including all documentation. Note that this feature is
+# still experimental and incomplete at the moment.
+# The default value is: NO.
GENERATE_AUTOGEN_DEF = NO
#---------------------------------------------------------------------------
-# configuration options related to the Perl module output
+# Configuration options related to the Perl module output
#---------------------------------------------------------------------------
-# If the GENERATE_PERLMOD tag is set to YES Doxygen will
-# generate a Perl module file that captures the structure of
-# the code including all documentation. Note that this
-# feature is still experimental and incomplete at the
-# moment.
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
GENERATE_PERLMOD = NO
-# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
-# the necessary Makefile rules, Perl scripts and LaTeX code to be able
-# to generate PDF and DVI output from the Perl module output.
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
PERLMOD_LATEX = NO
-# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
-# nicely formatted so it can be parsed by a human reader.
-# This is useful
-# if you want to understand what is going on.
-# On the other hand, if this
-# tag is set to NO the size of the Perl module output will be much smaller
-# and Perl will parse it just the same.
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
PERLMOD_PRETTY = YES
-# The names of the make variables in the generated doxyrules.make file
-# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
-# This is useful so different doxyrules.make files included by the same
-# Makefile don't overwrite each other's variables.
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
-PERLMOD_MAKEVAR_PREFIX =
+PERLMOD_MAKEVAR_PREFIX =
#---------------------------------------------------------------------------
-# Configuration options related to the preprocessor
+# Configuration options related to the preprocessor
#---------------------------------------------------------------------------
-# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
-# evaluate all C-preprocessor directives found in the sources and include
-# files.
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
ENABLE_PREPROCESSING = YES
-# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
-# names in the source code. If set to NO (the default) only conditional
-# compilation will be performed. Macro expansion can be done in a controlled
-# way by setting EXPAND_ONLY_PREDEF to YES.
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
MACRO_EXPANSION = NO
-# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
-# then the macro expansion is limited to the macros specified with the
-# PREDEFINED and EXPAND_AS_DEFINED tags.
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
EXPAND_ONLY_PREDEF = NO
-# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
-# in the INCLUDE_PATH (see below) will be search if a #include is found.
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
SEARCH_INCLUDES = YES
-# The INCLUDE_PATH tag can be used to specify one or more directories that
-# contain include files that are not input files but should be processed by
-# the preprocessor.
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
-INCLUDE_PATH =
+INCLUDE_PATH =
-# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
-# patterns (like *.h and *.hpp) to filter out the header-files in the
-# directories. If left blank, the patterns specified with FILE_PATTERNS will
-# be used.
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-INCLUDE_FILE_PATTERNS =
+INCLUDE_FILE_PATTERNS =
-# The PREDEFINED tag can be used to specify one or more macro names that
-# are defined before the preprocessor is started (similar to the -D option of
-# gcc). The argument of the tag is a list of macros of the form: name
-# or name=definition (no spaces). If the definition and the = are
-# omitted =1 is assumed. To prevent a macro definition from being
-# undefined via #undef or recursively expanded use the := operator
-# instead of the = operator.
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-PREDEFINED =
+PREDEFINED =
-# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
-# this tag can be used to specify a list of macro names that should be expanded.
-# The macro definition that is found in the sources will be used.
-# Use the PREDEFINED tag if you want to use a different macro definition.
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-EXPAND_AS_DEFINED =
+EXPAND_AS_DEFINED =
-# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
-# doxygen's preprocessor will remove all function-like macros that are alone
-# on a line, have an all uppercase name, and do not end with a semicolon. Such
-# function macros are typically used for boiler-plate code, and will confuse
-# the parser if not removed.
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
SKIP_FUNCTION_MACROS = YES
#---------------------------------------------------------------------------
-# Configuration::additions related to external references
+# Configuration options related to external references
#---------------------------------------------------------------------------
-# The TAGFILES option can be used to specify one or more tagfiles.
-# Optionally an initial location of the external documentation
-# can be added for each tagfile. The format of a tag file without
-# this location is as follows:
-#
-# TAGFILES = file1 file2 ...
-# Adding location for the tag files is done as follows:
-#
-# TAGFILES = file1=loc1 "file2 = loc2" ...
-# where "loc1" and "loc2" can be relative or absolute paths or
-# URLs. If a location is present for each tag, the installdox tool
-# does not have to be run to correct the links.
-# Note that each tag file must have a unique name
-# (where the name does NOT include the path)
-# If a tag file is not located in the directory in which doxygen
-# is run, you must also specify the path to the tagfile here.
-
-TAGFILES =
-
-# When a file name is specified after GENERATE_TAGFILE, doxygen will create
-# a tag file that is based on the input files it reads.
-
-GENERATE_TAGFILE =
-
-# If the ALLEXTERNALS tag is set to YES all external classes will be listed
-# in the class index. If set to NO only the inherited external classes
-# will be listed.
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
+# The default value is: NO.
ALLEXTERNALS = NO
-# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
-# in the modules index. If set to NO, only the current project's groups will
-# be listed.
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
EXTERNAL_GROUPS = YES
-# The PERL_PATH should be the absolute path and name of the perl script
-# interpreter (i.e. the result of `which perl').
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of 'which perl').
+# The default file (with absolute path) is: /usr/bin/perl.
PERL_PATH = /usr/bin/perl
#---------------------------------------------------------------------------
-# Configuration options related to the dot tool
+# Configuration options related to the dot tool
#---------------------------------------------------------------------------
-# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
-# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
-# or super classes. Setting the tag to NO turns the diagrams off. Note that
-# this option is superseded by the HAVE_DOT option below. This is only a
-# fallback. It is recommended to install and use dot, since it yields more
+# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
# powerful graphs.
+# The default value is: YES.
CLASS_DIAGRAMS = YES
-# You can define message sequence charts within doxygen comments using the \msc
-# command. Doxygen will then run the mscgen tool (see
-# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
-# documentation. The MSCGEN_PATH tag allows you to specify the directory where
-# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see:
+# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
# default search path.
-MSCGEN_PATH =
+MSCGEN_PATH =
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
-# If set to YES, the inheritance and collaboration graphs will hide
-# inheritance and usage relations if the target is undocumented
-# or is not a class.
+DIA_PATH =
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
HIDE_UNDOC_RELATIONS = YES
-# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
-# available from the path. This tool is part of Graphviz, a graph visualization
-# toolkit from AT&T and Lucent Bell Labs. The other options in this section
-# have no effect if this option is set to NO (the default)
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: NO.
HAVE_DOT = YES
-# By default doxygen will write a font called FreeSans.ttf to the output
-# directory and reference it in all dot files that doxygen generates. This
-# font does not include all possible unicode characters however, so when you need
-# these (or just want a differently looking font) you can specify the font name
-# using DOT_FONTNAME. You need need to make sure dot is able to find the font,
-# which can be done by putting it in a standard location or by setting the
-# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
-# containing the font.
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTNAME = FreeSans
-# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
-# The default size is 10pt.
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTSIZE = 10
-# By default doxygen will tell dot to use the output directory to look for the
-# FreeSans.ttf font (which doxygen will put there itself). If you specify a
-# different font using DOT_FONTNAME you can set the path where dot
-# can find it using this tag.
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
-DOT_FONTPATH =
+DOT_FONTPATH =
-# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for each documented class showing the direct and
-# indirect inheritance relations. Setting this tag to YES will force the
-# the CLASS_DIAGRAMS tag to NO.
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
CLASS_GRAPH = NO
-# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for each documented class showing the direct and
-# indirect implementation dependencies (inheritance, containment, and
-# class references variables) of the class with other documented classes.
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
COLLABORATION_GRAPH = NO
-# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for groups, showing the direct groups dependencies
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
GROUP_GRAPHS = YES
-# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
-# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
UML_LOOK = YES
-# If set to YES, the inheritance and collaboration graphs will show the
-# relations between templates and their instances.
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LIMIT_NUM_FIELDS = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
TEMPLATE_RELATIONS = NO
-# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
-# tags are set to YES then doxygen will generate a graph for each documented
-# file showing the direct and indirect include dependencies of the file with
-# other documented files.
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
INCLUDE_GRAPH = YES
-# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
-# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
-# documented header file showing the documented files that directly or
-# indirectly include this file.
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
INCLUDED_BY_GRAPH = YES
-# If the CALL_GRAPH and HAVE_DOT options are set to YES then
-# doxygen will generate a call dependency graph for every global function
-# or class method. Note that enabling this option will significantly increase
-# the time of a run. So in most cases it will be better to enable call graphs
-# for selected functions only using the \callgraph command.
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
CALL_GRAPH = NO
-# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
-# doxygen will generate a caller dependency graph for every global function
-# or class method. Note that enabling this option will significantly increase
-# the time of a run. So in most cases it will be better to enable caller
-# graphs for selected functions only using the \callergraph command.
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
CALLER_GRAPH = NO
-# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
-# will graphical hierarchy of all classes instead of a textual one.
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
GRAPHICAL_HIERARCHY = NO
-# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
-# then doxygen will show the dependencies a directory has on other directories
-# in a graphical way. The dependency relations are determined by the #include
-# relations between the files in the directories.
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
DIRECTORY_GRAPH = YES
-# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
-# generated by dot. Possible values are png, jpg, or gif
-# If left blank png will be used.
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot.
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, gif and svg.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
DOT_IMAGE_FORMAT = png
-# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS =
-DOT_PATH =
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
-# The DOTFILE_DIRS tag can be used to specify one or more directories that
-# contain dot files that are included in the documentation (see the
-# \dotfile command).
+PLANTUML_JAR_PATH =
-DOTFILE_DIRS =
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
-# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
-# nodes that will be shown in the graph. If the number of nodes in a graph
-# becomes larger than this value, doxygen will truncate the graph, which is
-# visualized by representing a node as a red box. Note that doxygen if the
-# number of direct children of the root node in a graph is already larger than
-# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
-# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+PLANTUML_INCLUDE_PATH =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
DOT_GRAPH_MAX_NODES = 50
-# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
-# graphs generated by dot. A depth value of 3 means that only nodes reachable
-# from the root by following a path via at most 3 edges will be shown. Nodes
-# that lay further from the root node will be omitted. Note that setting this
-# option to 1 or 2 may greatly reduce the computation time needed for large
-# code bases. Also note that the size of a graph can be further restricted by
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
MAX_DOT_GRAPH_DEPTH = 0
-# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
-# background. This is disabled by default, because dot on Windows does not
-# seem to support this out of the box. Warning: Depending on the platform used,
-# enabling this option may lead to badly anti-aliased labels on the edges of
-# a graph (i.e. they become hard to read).
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
DOT_TRANSPARENT = NO
-# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
-# files in one run (i.e. multiple -o and -T options on the command line). This
-# makes dot run faster, but since only newer versions of dot (>1.8.10)
-# support this, this feature is disabled by default.
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
DOT_MULTI_TARGETS = NO
-# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
-# generate a legend page explaining the meaning of the various boxes and
-# arrows in the dot generated graphs.
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
GENERATE_LEGEND = YES
-# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
-# remove the intermediate dot files that are used to generate
-# the various graphs.
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
DOT_CLEANUP = YES
-
-#---------------------------------------------------------------------------
-# Options related to the search engine
-#---------------------------------------------------------------------------
-
-# The SEARCHENGINE tag specifies whether or not a search engine should be
-# used. If set to NO the values of all tags below this one will be ignored.
-
-SEARCHENGINE = NO
diff --git a/README b/README
index d019680f..ea6dd41d 100644
--- a/README
+++ b/README
@@ -27,8 +27,8 @@ dillorc.
Here's a list of some old well-known problems of dillo:
* no FRAMES rendering
- * no https (there's a barebones prototype, enable it with:
- ./configure --enable-ssl ).
+ * https code not yet fully trusted
+ (enable it with: ./configure --enable-ssl ).
--------
diff --git a/configure.ac b/configure.ac
index f9edfbf0..be9c9c42 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,6 +1,6 @@
dnl Process this file with aclocal, autoconf and automake.
-AC_INIT([dillo], [3.0.5])
+AC_INIT([dillo], [3.1-dev])
dnl Detect the canonical target build environment
AC_CANONICAL_TARGET
@@ -22,8 +22,10 @@ AC_ARG_ENABLE(gprof, [ --enable-gprof Try to compile and run with pro
, enable_gprof=no)
AC_ARG_ENABLE(insure, [ --enable-insure Try to compile and run with Insure++],
, enable_insure=no)
-AC_ARG_ENABLE(ssl, [ --enable-ssl Enable ssl, https (ALPHA CODE)],
+AC_ARG_ENABLE(ssl, [ --enable-ssl Enable SSL/HTTPS/TLS (EXPERIMENTAL CODE)],
, enable_ssl=no)
+AC_ARG_WITH(ca-certs-file, [ --with-ca-certs-file=FILE Specify where to find a bundle of trusted CA certificates for TLS], CA_CERTS_FILE=$withval)
+AC_ARG_WITH(ca-certs-dir, [ --with-ca-certs-dir=DIR Specify where to find a directory containing trusted CA certificates for TLS], CA_CERTS_DIR=$withval)
AC_ARG_ENABLE(ipv6, [ --enable-ipv6 Build with support for IPv6], , )
AC_ARG_ENABLE(cookies,[ --disable-cookies Don't compile support for cookies],
, enable_cookies=yes)
@@ -56,6 +58,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)
@@ -278,7 +288,7 @@ if test "x$enable_gif" = "xyes"; then
fi
dnl --------------------------
-dnl Test for support for SSL
+dnl Test for support for SSL/TLS
dnl --------------------------
dnl
if test "x$enable_ssl" = "xyes"; then
@@ -292,14 +302,14 @@ if test "x$enable_ssl" = "xyes"; then
if test "x$ssl_ok" = "xyes"; then
LIBSSL_LIBS="-lcrypto -lssl"
- AC_MSG_WARN([*** Enabling ssl support. THIS IS ALPHA CODE!***])
+ AC_MSG_WARN([*** Enabling SSL/HTTPS/TLS support. THIS IS EXPERIMENTAL CODE ***])
else
- AC_MSG_WARN([*** No libssl found. Disabling ssl support.***])
+ AC_MSG_WARN([*** No libssl found. Disabling SSL/HTTPS/TLS support. ***])
fi
fi
if test "x$ssl_ok" = "xyes"; then
- AC_DEFINE([ENABLE_SSL], [1], [Enable SSL support])
+ AC_DEFINE([ENABLE_SSL], [1], [Enable SSL/HTTPS/TLS support])
fi
dnl --------------------------------------------------------------
@@ -486,6 +496,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)
@@ -500,6 +511,8 @@ AC_SUBST(LIBFLTK_CFLAGS)
AC_SUBST(LIBFLTK_LIBS)
AC_SUBST(LIBICONV_LIBS)
AC_SUBST(LIBX11_LIBS)
+AC_SUBST(CA_CERTS_FILE)
+AC_SUBST(CA_CERTS_DIR)
AC_SUBST(datadir)
AC_CONFIG_FILES([
diff --git a/doc/CCCwork.txt b/devdoc/CCCwork.txt
index 1ea5d20e..1ea5d20e 100644
--- a/doc/CCCwork.txt
+++ b/devdoc/CCCwork.txt
diff --git a/doc/Cache.txt b/devdoc/Cache.txt
index 4e885df2..4e885df2 100644
--- a/doc/Cache.txt
+++ b/devdoc/Cache.txt
diff --git a/doc/Dillo.txt b/devdoc/Dillo.txt
index a63c9588..a63c9588 100644
--- a/doc/Dillo.txt
+++ b/devdoc/Dillo.txt
diff --git a/doc/Dpid.txt b/devdoc/Dpid.txt
index 6c418f57..6c418f57 100644
--- a/doc/Dpid.txt
+++ b/devdoc/Dpid.txt
diff --git a/doc/HtmlParser.txt b/devdoc/HtmlParser.txt
index 2eb8be63..2eb8be63 100644
--- a/doc/HtmlParser.txt
+++ b/devdoc/HtmlParser.txt
diff --git a/doc/IO.txt b/devdoc/IO.txt
index cd62a4f5..cd62a4f5 100644
--- a/doc/IO.txt
+++ b/devdoc/IO.txt
diff --git a/doc/Images.txt b/devdoc/Images.txt
index 6a36e6f5..62082e48 100644
--- a/doc/Images.txt
+++ b/devdoc/Images.txt
@@ -1,5 +1,8 @@
January 2009, --Jcid
+Update June 2015: See also doc/dw-images-and-backgrounds.doc, or
+../html/dw-images-and-backgrounds.html (generated by doxygen).
+
------
IMAGES
------
diff --git a/doc/NC_design.txt b/devdoc/NC_design.txt
index 380787f6..380787f6 100644
--- a/doc/NC_design.txt
+++ b/devdoc/NC_design.txt
diff --git a/devdoc/README b/devdoc/README
new file mode 100644
index 00000000..9736a32b
--- /dev/null
+++ b/devdoc/README
@@ -0,0 +1,51 @@
+README: Last update Jul 2009
+
+These documents cover dillo's internals.
+For user help, see http://www.dillo.org/dillo3-help.html
+
+--------------------------------------------------------------------------
+
+These documents need a review.
+*.txt were current with Dillo1, but many have since become more or
+ less out-of-date.
+*.doc are doxygen source for the Dillo Widget (dw) component, and
+ were written for Dillo2.
+
+They will give you an overview of what's going on, but take them
+with a pinch of salt.
+
+ Of course I'd like to have *.txt as doxygen files too!
+If somebody wants to make this conversion, please let me know
+to assign higher priority to updating these docs.
+
+--
+Jorge.-
+
+ --------------------------------------------------------------------------
+ FILE DESCRIPTION STATE
+ --------------------------------------------------------------------------
+ NC_design.txt Naming&Coding design (Be sure to Current
+ read it before any other doc)
+ Dillo.txt General overview of the program Current
+ IO.txt Extensive introduction Current
+ Cache.txt Informative description Current
+ Images.txt Image handling and processing Current
+ HtmlParser.txt A versatile parser Current
+ Dw.txt The New Dillo Widget (Overview) Current
+ Imgbuf.txt Image buffers Pending
+ Selection.txt Selections, and link activation Current (?)
+ Cookies.txt Explains how to enable cookies Current
+ Dpid.txt Dillo plugin daemon Current
+ --------------------------------------------------------------------------
+
+
+ * BTW, there's a small program (srch) within the src/ dir. It searches
+ tokens within the whole code (*.[ch]). It has proven very useful.
+ Ex: ./srch a_Image_write
+ ./srch todo:
+
+ * Please submit your patches with 'hg diff'.
+
+
+ Happy coding!
+ --Jcid
diff --git a/doc/dw-changes.doc b/devdoc/dw-changes.doc
index 7050df9a..7050df9a 100644
--- a/doc/dw-changes.doc
+++ b/devdoc/dw-changes.doc
diff --git a/devdoc/dw-example-screenshot.png b/devdoc/dw-example-screenshot.png
new file mode 100644
index 00000000..94f272ab
--- /dev/null
+++ b/devdoc/dw-example-screenshot.png
Binary files differ
diff --git a/devdoc/dw-floats-01.png b/devdoc/dw-floats-01.png
new file mode 100644
index 00000000..116d36b3
--- /dev/null
+++ b/devdoc/dw-floats-01.png
Binary files differ
diff --git a/devdoc/dw-grows.doc b/devdoc/dw-grows.doc
new file mode 100644
index 00000000..a0304ef9
--- /dev/null
+++ b/devdoc/dw-grows.doc
@@ -0,0 +1,202 @@
+/** \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*;
+ - &lt;button&gt;.
+
+
+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.&nbsp;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>
+
+Another **extension of extremes: *adjustmentWidth*.** This is used as
+minimum for the width, when "adjust_min_width" (or,
+"adjust_table_min_width", respectively) is set.
+
+The rules for the calculation:
+
+1. If a widget has no children, it can choose a suitable value,
+ typically based on dw::core::Extremes::minWidth and
+ dw::core::Extremes::minWidthIntrinsic.
+2. A widget must calculate *adjustmentWidth* from *adjustmentWidth* of
+ its children.
+
+*Note:* An implementation of dw::core::Widget::getExtremesImpl may set
+this value *after* calling dw::core::Widget::correctExtremesOfChild,
+so that it cannot be used for the correction of extremes. In this case
+*useAdjustmentWidth = false* should be passed to
+dw::core::Widget::correctExtremesOfChild. On the other hand, if known
+before, *useAdjustmentWidth* should be set to *true*.
+
+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.&nbsp;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-images-and-backgrounds.doc b/devdoc/dw-images-and-backgrounds.doc
index 8f07766a..8f07766a 100644
--- a/doc/dw-images-and-backgrounds.doc
+++ b/devdoc/dw-images-and-backgrounds.doc
diff --git a/doc/dw-layout-views.doc b/devdoc/dw-layout-views.doc
index d1118489..d1118489 100644
--- a/doc/dw-layout-views.doc
+++ b/devdoc/dw-layout-views.doc
diff --git a/doc/dw-layout-widgets.doc b/devdoc/dw-layout-widgets.doc
index e0215562..e0215562 100644
--- a/doc/dw-layout-widgets.doc
+++ b/devdoc/dw-layout-widgets.doc
diff --git a/doc/dw-line-breaking.doc b/devdoc/dw-line-breaking.doc
index ac4f61d6..14ab97c4 100644
--- a/doc/dw-line-breaking.doc
+++ b/devdoc/dw-line-breaking.doc
@@ -264,7 +264,7 @@ the stretchability by changing the preference value
(Comparison to T<sub>E</sub>X: Knuth and Plass describe a method for
ragged borders, which is effectively the same as described here (Knuth
1999, pp.&nbsp;93--94). The value for the stretchability of the line
-is slightly less, 1&nsbp;em (ibid., see also p.&nsbp;72 for the
+is slightly less, 1&nbsp;em (ibid., see also p.&nbsp;72 for the
definition of the units). However, this article suggests a value for
the hyphenation penalty, which is ten times larger than the value for
justified text; this would suggest a larger value for
@@ -420,7 +420,7 @@ Notice that Liang's algorithm may behave different regarding hyphens:
and "Stadt", but "Nordrhein-Westfalen" is divided into "Nord",
"rhein-West", "fa", "len": the part containing the hyphen
("rhein-West") is untouched. (Sorry for the German words; if you have
-got English examples, send them me.)</div>
+got English examples, send them me.)
**Solution for both:** This has been implemented in
dw::Textblock::addText, in a similar way to soft hyphens. Liang's
diff --git a/doc/dw-map.doc b/devdoc/dw-map.doc
index aebeb7da..aebeb7da 100644
--- a/doc/dw-map.doc
+++ b/devdoc/dw-map.doc
diff --git a/devdoc/dw-out-of-flow-2.doc b/devdoc/dw-out-of-flow-2.doc
new file mode 100644
index 00000000..d9d70565
--- /dev/null
+++ b/devdoc/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/devdoc/dw-out-of-flow-floats.doc b/devdoc/dw-out-of-flow-floats.doc
new file mode 100644
index 00000000..53c6b220
--- /dev/null
+++ b/devdoc/dw-out-of-flow-floats.doc
@@ -0,0 +1,121 @@
+/** \page dw-out-of-flow-floats Handling Elements Out Of Flow: Floats
+
+(Note: Bases on work at <http://flpsed.org/hgweb/dillo_grows>, I plan
+to split the documentation on elements out of flow into different
+parts: general part, floats, positioned elements. In this document,
+informations about floats are collected.)
+
+
+GB lists and CB lists
+=====================
+
+Floats generated by a block which is not yet allocated are initially
+put into a list related to the *generator*:
+
+- dw::OutOfFlowMgr::TBInfo::leftFloatsGB or
+- dw::OutOfFlowMgr::TBInfo::rightFloatsGB.
+
+These lists are also called GB lists.
+
+Floats of allocated generators are put into lists related to the
+*container* (called CB lists):
+
+- dw::OutOfFlowMgr::leftFloatsCB or
+- dw::OutOfFlowMgr::rightFloatsCB.
+
+As soon as the container is allocated, all floats are moved from the
+GB lists to the CB lists (dw::OutOfFlowMgr::sizeAllocateStart →
+dw::OutOfFlowMgr::moveFromGBToCB).
+
+Here, it is important to preserve the *generation order* (for reasons,
+see below: *Sorting floats*), i. e. the order in which floats have
+been added (dw::OutOfFlowMgr::addWidgetOOF). This may become a bit
+more complicated in a case like this:
+
+ <head>
+ <style>
+ \#fl-1, \#fl-2, \#fl-3 { float: right }
+ </style>
+ </head>
+ <body>
+ <div id="bl-1">
+ <div id="fl-1">float 1</div>
+ <div id="bl-2">
+ <div id="fl-2">float 2</div>
+ </div>
+ <div id="fl-3">float 3</div>
+ </div>
+ </body>
+
+The floats are generated in this order:
+
+- \#fl-1 (generated by \#bl-1),
+- \#fl-2 (generated by \#bl-2),
+- \#fl-3 (generated by \#bl-1).
+
+Since the floats must be moved into the CB list in this order, it
+becomes clear that the floats from one GB list cannot be moved at
+once. For this reason, each float is assigned a "mark", which is
+different from the last one as soon as the generator is *before* the
+generator of the float added before. In the example above, there are
+three generators: body, \#bl-1, and \#bl-2 (in this order), and floats
+are assigned these marks:
+
+- \#fl-1: 0,
+- \#fl-2: also 0,
+- \#fl-3 is assigned 1, since its generator (\#bl-1) lies before the
+ last generator (\#bl-2).
+
+dw::OutOfFlowMgr::moveFromGBToCB will then iterate over all marks, so
+that the generation order is preserved.
+
+
+Sorting floats
+==============
+
+Floats are sorted, to make binary search possible, in these lists:
+
+- for each generator: dw::OutOfFlowMgr::TBInfo::leftFloatsGB and
+ dw::OutOfFlowMgr::TBInfo::rightFloatsGB;
+- for the container: dw::OutOfFlowMgr::leftFloatsCB and
+ dw::OutOfFlowMgr::rightFloatsCB.
+
+The other two lists, dw::OutOfFlowMgr::leftFloatsAll and
+dw::OutOfFlowMgr::rightFloatsAll are not sorted at all.
+
+New floats are always added to the end of either list; this order is
+called *generation order*. See also above: *GB lists and CB lists*.
+
+On the other hand, there are different sorting criteria, implemented
+by different comparators, so that different kinds of keys may be used
+for searching. These sorting criteria are equivalent to the generation
+order.
+
+dw::OutOfFlowMgr::Float::CompareSideSpanningIndex compares
+*sideSpanningIndex* (used to compare floats to those on the respective
+other side); if you look at the definition
+(dw::OutOfFlowMgr::addWidgetOOF) it becomes clear that this order is
+equivalent to the generation order.
+
+dw::OutOfFlowMgr::Float::CompareGBAndExtIndex compares *externalIndex*
+for floats with same generators, otherwise: (i) if one generator (T1)
+is a direct anchestor of the other generator (T2), the child of T1,
+which is an anchestor of, or identical to, T2 is compared to the float
+generated by T1, using *externalIndex*, as in this example:
+
+ T1 -+-> child --> ... -> T2 -> Float
+ `-> Float
+
+Otherwise, the two blocks are compared, according to their position in
+dw::OutOfFlowMgr::tbInfos:
+
+ common anchestor -+-> ... --> T1 -> Float
+ `-> ... --> T2 -> Float
+
+This is equivalent to the generation order, as long it is ensured that
+*externalIndex* reflects the generation order within a generating
+block, for both floats and child blocks.
+
+dw::OutOfFlowMgr::Float::ComparePosition ...
+
+*/
diff --git a/devdoc/dw-out-of-flow.doc b/devdoc/dw-out-of-flow.doc
new file mode 100644
index 00000000..ea4a52bc
--- /dev/null
+++ b/devdoc/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&nbsp;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-overview.doc b/devdoc/dw-overview.doc
index 0c4ffb53..0c4ffb53 100644
--- a/doc/dw-overview.doc
+++ b/devdoc/dw-overview.doc
diff --git a/devdoc/dw-size-of-widget.png b/devdoc/dw-size-of-widget.png
new file mode 100644
index 00000000..dbdbe0c4
--- /dev/null
+++ b/devdoc/dw-size-of-widget.png
Binary files differ
diff --git a/devdoc/dw-style-box-model.png b/devdoc/dw-style-box-model.png
new file mode 100644
index 00000000..bf2fb1f1
--- /dev/null
+++ b/devdoc/dw-style-box-model.png
Binary files differ
diff --git a/devdoc/dw-style-length-absolute.png b/devdoc/dw-style-length-absolute.png
new file mode 100644
index 00000000..9ea28cad
--- /dev/null
+++ b/devdoc/dw-style-length-absolute.png
Binary files differ
diff --git a/devdoc/dw-style-length-percentage.png b/devdoc/dw-style-length-percentage.png
new file mode 100644
index 00000000..b1ad79c9
--- /dev/null
+++ b/devdoc/dw-style-length-percentage.png
Binary files differ
diff --git a/devdoc/dw-style-length-relative.png b/devdoc/dw-style-length-relative.png
new file mode 100644
index 00000000..ee79b1a9
--- /dev/null
+++ b/devdoc/dw-style-length-relative.png
Binary files differ
diff --git a/doc/dw-textblock-collapsing-spaces-1-1.png b/devdoc/dw-textblock-collapsing-spaces-1-1.png
index d528dfb2..d528dfb2 100644
--- a/doc/dw-textblock-collapsing-spaces-1-1.png
+++ b/devdoc/dw-textblock-collapsing-spaces-1-1.png
Binary files differ
diff --git a/doc/dw-textblock-collapsing-spaces-1-2.png b/devdoc/dw-textblock-collapsing-spaces-1-2.png
index 483e79d1..483e79d1 100644
--- a/doc/dw-textblock-collapsing-spaces-1-2.png
+++ b/devdoc/dw-textblock-collapsing-spaces-1-2.png
Binary files differ
diff --git a/doc/dw-textblock-collapsing-spaces-2-1.png b/devdoc/dw-textblock-collapsing-spaces-2-1.png
index 0a03ea80..0a03ea80 100644
--- a/doc/dw-textblock-collapsing-spaces-2-1.png
+++ b/devdoc/dw-textblock-collapsing-spaces-2-1.png
Binary files differ
diff --git a/doc/dw-textblock-collapsing-spaces-2-2.png b/devdoc/dw-textblock-collapsing-spaces-2-2.png
index b89c6254..b89c6254 100644
--- a/doc/dw-textblock-collapsing-spaces-2-2.png
+++ b/devdoc/dw-textblock-collapsing-spaces-2-2.png
Binary files differ
diff --git a/doc/dw-usage.doc b/devdoc/dw-usage.doc
index a23920b8..a23920b8 100644
--- a/doc/dw-usage.doc
+++ b/devdoc/dw-usage.doc
diff --git a/doc/dw-viewport-with-scrollbar.png b/devdoc/dw-viewport-with-scrollbar.png
index 7ac62de3..7ac62de3 100644
--- a/doc/dw-viewport-with-scrollbar.png
+++ b/devdoc/dw-viewport-with-scrollbar.png
Binary files differ
diff --git a/doc/dw-viewport-without-scrollbar.png b/devdoc/dw-viewport-without-scrollbar.png
index 8aa20fec..8aa20fec 100644
--- a/doc/dw-viewport-without-scrollbar.png
+++ b/devdoc/dw-viewport-without-scrollbar.png
Binary files differ
diff --git a/devdoc/dw-widget-sizes.doc b/devdoc/dw-widget-sizes.doc
new file mode 100644
index 00000000..a82d3b99
--- /dev/null
+++ b/devdoc/dw-widget-sizes.doc
@@ -0,0 +1,277 @@
+/** \page dw-widget-sizes Sizes of Dillo Widgets
+
+<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
+
+- 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.
+
+- 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
+divided into an ascender (which height is called \em ascent), and a
+descender (which height is called \em descent). The total height is so
+the sum of \em ascent and \em descent.
+
+Sizes of zero are allowed. The upper limit for the size of a widget is
+defined by the limits of the C++ type \em int.
+
+\image html dw-size-of-widget.png Allocation of a Widget
+
+In the example in the image, the widget has the following allocation:
+
+- \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
+calling dw::core::Widget::sizeAllocate. This is a concrete method,
+which will call dw::core::Widget::sizeAllocateImpl (see code of
+dw::core::Widget::sizeAllocate for details).
+
+For trivial widgets (like dw::Bullet),
+dw::core::Widget::sizeAllocateImpl does not need to be
+implemented. For more complex widgets, the implementation should call
+dw::core::Widget::sizeAllocate (not
+dw::core::Widget::sizeAllocateImpl) on all child widgets, with
+appropriate child allocations. dw::core::Widget::allocation should not
+be changed here, this is already done in
+dw::core::Widget::sizeAllocate.
+
+
+Requisitions
+============
+
+A widget may prefer a given size for the allocation. This size, the
+\em requisition, should be returned by the method
+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:
+
+- 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 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.
+
+From the outside, dw::Image::sizeRequest should be called, which does
+a bit of optimization. Notice that in dw::Image::sizeRequestImpl, no
+optimization like lazy evaluation is necessary, this is already done
+in dw::Image::sizeRequest.
+
+A widget, which has children, will likely call dw::Image::sizeRequest
+on its children, to calculate the total requisition.
+
+The caller (this is either the dw::core::Layout, or the parent
+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.)
+
+
+Size Hints
+==========
+
+<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>
+
+
+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:
+
+- 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:
+
+- 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 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.
+
+- 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.
+
+
+Resizing
+========
+
+When the widget changes its size (requisition), it should call
+dw::core::Widget::queueResize. The next call of
+dw::core::Widget::sizeRequestImpl should then return the new
+size. See dw::Image::setBuffer as an example.
+
+Interna are described in the code of dw::core::Widget::queueResize.
+
+<h3>Incremental Resizing</h3>
+
+A widget may calculate its size based on size calculations already
+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:
+
+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:
+ 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.&nbsp;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/fltk-problems.doc b/devdoc/fltk-problems.doc
index df4f1f14..df4f1f14 100644
--- a/doc/fltk-problems.doc
+++ b/devdoc/fltk-problems.doc
diff --git a/doc/index.doc b/devdoc/index.doc
index 59de8cd8..59de8cd8 100644
--- a/doc/index.doc
+++ b/devdoc/index.doc
diff --git a/doc/lout.doc b/devdoc/lout.doc
index 4e1503c6..4e1503c6 100644
--- a/doc/lout.doc
+++ b/devdoc/lout.doc
diff --git a/devdoc/not-so-simple-container.png b/devdoc/not-so-simple-container.png
new file mode 100644
index 00000000..f3e2c039
--- /dev/null
+++ b/devdoc/not-so-simple-container.png
Binary files differ
diff --git a/devdoc/not-so-simple-container.svg b/devdoc/not-so-simple-container.svg
new file mode 100644
index 00000000..ce00510e
--- /dev/null
+++ b/devdoc/not-so-simple-container.svg
@@ -0,0 +1,785 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="210mm"
+ height="297mm"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.3.1 r9886"
+ sodipodi:docname="not-so-simple-container.svg"
+ inkscape:export-filename="/home/sg/dev/dillo/dillo/doc/not-so-simple-container.png"
+ inkscape:export-xdpi="69"
+ inkscape:export-ydpi="69">
+ <defs
+ id="defs4">
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Mend"
+ style="overflow:visible;">
+ <path
+ id="path3998"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;"
+ transform="scale(0.4) rotate(180) translate(10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Send"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Send"
+ style="overflow:visible;">
+ <path
+ id="path4004"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;"
+ transform="scale(0.2) rotate(180) translate(6,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Lend"
+ style="overflow:visible;">
+ <path
+ id="path3992"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;"
+ transform="scale(0.8) rotate(180) translate(12.5,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Mend-5"
+ style="overflow:visible">
+ <path
+ inkscape:connector-curvature="0"
+ id="path3998-5"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ transform="matrix(-0.4,0,0,-0.4,-4,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Mend-3"
+ style="overflow:visible">
+ <path
+ inkscape:connector-curvature="0"
+ id="path3998-2"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ transform="matrix(-0.4,0,0,-0.4,-4,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Mend-2"
+ style="overflow:visible">
+ <path
+ inkscape:connector-curvature="0"
+ id="path3998-22"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ transform="matrix(-0.4,0,0,-0.4,-4,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker4917"
+ style="overflow:visible">
+ <path
+ inkscape:connector-curvature="0"
+ id="path4919"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ transform="matrix(-0.4,0,0,-0.4,-4,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker4921"
+ style="overflow:visible">
+ <path
+ inkscape:connector-curvature="0"
+ id="path4923"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ transform="matrix(-0.4,0,0,-0.4,-4,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Mend-9"
+ style="overflow:visible">
+ <path
+ inkscape:connector-curvature="0"
+ id="path3998-7"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ transform="matrix(-0.4,0,0,-0.4,-4,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Mend-6"
+ style="overflow:visible">
+ <path
+ inkscape:connector-curvature="0"
+ id="path3998-6"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ transform="matrix(-0.4,0,0,-0.4,-4,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Mend-51"
+ style="overflow:visible">
+ <path
+ inkscape:connector-curvature="0"
+ id="path3998-3"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ transform="matrix(-0.4,0,0,-0.4,-4,0)" />
+ </marker>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1"
+ inkscape:cx="226.09436"
+ inkscape:cy="741.31258"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:window-width="1598"
+ inkscape:window-height="876"
+ inkscape:window-x="0"
+ inkscape:window-y="22"
+ inkscape:window-maximized="1">
+ <inkscape:grid
+ type="xygrid"
+ id="grid2985" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Ebene 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <rect
+ style="fill:#e0e0e0;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3010"
+ width="35.433071"
+ height="17.716536"
+ x="442.91339"
+ y="25.611128" />
+ <text
+ xml:space="preserve"
+ style="font-size:20px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ x="496.06299"
+ y="38.581394"
+ id="text3012"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3014"
+ x="496.06299"
+ y="38.581394"
+ style="font-size:16px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Nimbus Sans L;-inkscape-font-specification:Nimbus Sans L">unaffected (in main array)</tspan></text>
+ <flowRoot
+ xml:space="preserve"
+ id="flowRoot3800"
+ style="fill:black;stroke:none;stroke-opacity:1;stroke-width:1px;stroke-linejoin:miter;stroke-linecap:butt;fill-opacity:1;font-family:Nimbus Mono L;font-style:normal;font-weight:normal;font-size:20px;line-height:125%;letter-spacing:0px;word-spacing:0px;-inkscape-font-specification:Nimbus Mono L;font-stretch:normal;font-variant:normal"><flowRegion
+ id="flowRegion3802"><rect
+ id="rect3804"
+ width="276.7818"
+ height="34.345188"
+ x="424.26407"
+ y="20.996433" /></flowRegion><flowPara
+ id="flowPara3806" /></flowRoot> <rect
+ style="fill:#e0e0e0;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3812"
+ width="70.866142"
+ height="17.716536"
+ x="17.716558"
+ y="60.236198" />
+ <rect
+ style="fill:#b0b0ff;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3814"
+ width="35.433071"
+ height="17.716536"
+ x="442.91339"
+ y="61.480198" />
+ <rect
+ style="fill:#80ff00;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3816"
+ width="35.433071"
+ height="17.716536"
+ x="442.91339"
+ y="96.913269" />
+ <rect
+ style="fill:#ffff40;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3818"
+ width="35.433071"
+ height="17.716536"
+ x="442.91339"
+ y="131.10234" />
+ <rect
+ style="fill:#b0b0ff;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3816-2"
+ width="88.58268"
+ height="17.716536"
+ x="124.01577"
+ y="60.236198" />
+ <rect
+ style="fill:#e0e0e0;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3812-5"
+ width="88.582687"
+ height="17.716536"
+ x="301.18112"
+ y="60.236198" />
+ <rect
+ style="fill:#80ff00;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3816-4"
+ width="35.433071"
+ height="17.716536"
+ x="442.91339"
+ y="96.913269" />
+ <rect
+ style="fill:#80ff00;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3816-4-2"
+ width="35.433071"
+ height="17.716536"
+ x="88.582703"
+ y="95.669266" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 17.716558,60.236198 70.866142,0 0,17.716536 -70.866142,0"
+ id="path3939"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 389.7638,60.236198 -88.58268,0 0,17.716536 88.58268,0"
+ id="path3941"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccc" />
+ <rect
+ style="fill:#e0e0e0;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3812-3"
+ width="70.866142"
+ height="17.716536"
+ x="17.71656"
+ y="148.81888" />
+ <rect
+ style="fill:#e0e0e0;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3812-5-7"
+ width="88.582687"
+ height="17.716537"
+ x="301.18112"
+ y="148.81888" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 17.716558,148.81888 70.86614,0 0,17.71653 -70.86614,0"
+ id="path3939-4"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 389.7638,148.81887 -88.58268,1e-5 0,17.71653 88.58268,0"
+ id="path3941-8"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccc" />
+ <rect
+ style="fill:#80ff00;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3816-4-2-6"
+ width="35.433071"
+ height="17.716536"
+ x="124.01577"
+ y="148.81888" />
+ <rect
+ style="fill:#b0b0ff;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3816-2-5"
+ width="53.149605"
+ height="17.716537"
+ x="159.44885"
+ y="148.81888" />
+ <rect
+ style="fill:#b0b0ff;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3816-2-50"
+ width="35.433067"
+ height="17.716537"
+ x="212.59845"
+ y="184.25195" />
+ <rect
+ style="fill:#ffff40;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3818-4"
+ width="53.149609"
+ height="17.716534"
+ x="248.03152"
+ y="184.25195" />
+ <path
+ style="fill:none;stroke:#808080;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 88.5827,148.81887 35.43307,0"
+ id="path4078-6"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#808080;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 88.5827,166.53541 35.43307,0"
+ id="path4078-4-7"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#808080;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 212.59845,60.236198 88.58267,0"
+ id="path4078-66"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#808080;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 212.59845,77.952734 88.58267,0"
+ id="path4078-4-0"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#8080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0;stroke-dasharray:2, 4;stroke-dashoffset:0"
+ d="m 88.5827,77.952734 0,17.716535"
+ id="path4156"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#808000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 88.5827,77.952734 0,17.716535"
+ id="path4176"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#808000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 124.01577,95.669269 0,-17.716535"
+ id="path4178"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#808000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 212.59845,166.53541 0,17.71654"
+ id="path4180"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#808000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 301.18112,184.25195 0,-17.71654"
+ id="path4182"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#8080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 88.5827,77.952734 0,17.716535"
+ id="path4186"
+ inkscape:connector-curvature="0"
+ inkscape:export-filename="/home/sg/dev/dillo/dillo/doc/path4188.png"
+ inkscape:export-xdpi="69"
+ inkscape:export-ydpi="69" />
+ <path
+ style="fill:none;stroke:#8080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 124.01577,95.669269 0,-17.716535"
+ id="path4188"
+ inkscape:connector-curvature="0"
+ inkscape:export-xdpi="69"
+ inkscape:export-ydpi="69" />
+ <path
+ style="fill:none;stroke:#8080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 212.59845,166.53541 0,17.71654"
+ id="path4190"
+ inkscape:connector-curvature="0"
+ inkscape:export-filename="/home/sg/dev/dillo/dillo/doc/path4188.png"
+ inkscape:export-xdpi="69"
+ inkscape:export-ydpi="69" />
+ <path
+ style="fill:none;stroke:#8080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 301.18112,184.25195 0,-17.71654"
+ id="path4192"
+ inkscape:connector-curvature="0"
+ inkscape:export-filename="/home/sg/dev/dillo/dillo/doc/path4188.png"
+ inkscape:export-xdpi="69"
+ inkscape:export-ydpi="69" />
+ <path
+ style="fill:none;stroke:#808080;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 88.5827,77.952734 0,17.716535"
+ id="path4078-6-0"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#808080;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 124.01577,77.952734 0,17.716535"
+ id="path4078-6-0-9"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#808080;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 212.59845,166.53541 0,17.71654"
+ id="path4078-6-0-0"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#808080;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 301.18112,166.53541 0,17.71654"
+ id="path4078-6-0-6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:none;marker-end:url(#Arrow1Mend)"
+ d="m 106.29923,116.92911 c 35.43307,28.34646 0,0 35.43307,28.34646"
+ id="path3803"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:none;marker-end:url(#Arrow1Mend)"
+ d="m 148.81892,81.496041 c 35.43307,63.779529 0,0 35.43307,63.779529"
+ id="path3803-7"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 177.16537,60.236198 0,17.716535"
+ id="path4078-6-0-9-5"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:none;marker-end:url(#Arrow1Mend)"
+ d="m 194.88191,81.496038 c 35.43307,99.212602 0,3e-6 35.43307,99.212602"
+ id="path3803-7-0"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-size:20px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ x="496.06299"
+ y="74.426468"
+ id="text3012-5"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3014-0"
+ x="496.06299"
+ y="74.426468"
+ style="font-size:16px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Nimbus Sans L;-inkscape-font-specification:Nimbus Sans L">main array (moved)</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:20px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Nimbus Sans L;-inkscape-font-specification:Nimbus Sans L"
+ x="496.06299"
+ y="109.85954"
+ id="text4897"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan4899"
+ x="496.06299"
+ y="109.85954"
+ style="font-size:16px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Nimbus Sans L;-inkscape-font-specification:Nimbus Sans L">original extra array</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:20px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ x="496.06299"
+ y="145.60861"
+ id="text4901"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan4903"
+ x="496.06299"
+ y="145.60861"
+ style="font-size:16px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Nimbus Sans L;-inkscape-font-specification:Nimbus Sans L">new inserted area</tspan></text>
+ <rect
+ style="fill:#e0e0e0;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3812-37"
+ width="53.149609"
+ height="17.716536"
+ x="17.716558"
+ y="272.83463" />
+ <rect
+ style="fill:#b0b0ff;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3816-2-1"
+ width="35.433067"
+ height="17.716537"
+ x="159.44884"
+ y="272.83463" />
+ <rect
+ style="fill:#e0e0e0;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3812-5-5"
+ width="53.149582"
+ height="17.716537"
+ x="336.61423"
+ y="272.83463" />
+ <rect
+ style="fill:#80ff00;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3816-4-2-3"
+ width="88.582664"
+ height="17.716534"
+ x="70.866173"
+ y="308.2677" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 17.716558,272.83462 53.149615,0 0,17.71654 -53.149615,0"
+ id="path3939-43"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 389.7638,272.83462 -53.14961,0 0,17.71654 53.14961,0"
+ id="path3941-6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccc" />
+ <rect
+ style="fill:#e0e0e0;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3812-3-3"
+ width="53.149624"
+ height="17.716515"
+ x="17.716558"
+ y="361.4173" />
+ <rect
+ style="fill:#e0e0e0;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3812-5-7-9"
+ width="53.149582"
+ height="17.716543"
+ x="336.61423"
+ y="361.4173" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 17.716559,361.4173 53.149604,0 0,17.71653 -53.149604,1e-5"
+ id="path3939-4-2"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 389.7638,361.4173 -53.14961,0 0,17.71653 53.14961,1e-5"
+ id="path3941-8-1"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccc" />
+ <rect
+ style="fill:#80ff00;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3816-4-2-6-7"
+ width="35.433071"
+ height="17.716536"
+ x="159.44884"
+ y="361.4173" />
+ <rect
+ style="fill:#b0b0ff;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3816-2-50-6"
+ width="35.433067"
+ height="17.716537"
+ x="248.03151"
+ y="396.85037" />
+ <rect
+ style="fill:#ffff40;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3818-4-9"
+ width="53.149609"
+ height="17.716534"
+ x="283.4646"
+ y="396.85037" />
+ <path
+ style="fill:none;stroke:#808080;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 70.866173,361.41729 88.582667,1e-5"
+ id="path4078-6-8"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#808080;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 70.866173,379.13383 88.582667,1e-5"
+ id="path4078-4-7-4"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#808080;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 194.88192,272.83462 141.73227,0"
+ id="path4078-66-9"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#808080;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 194.88192,290.55116 141.73227,0"
+ id="path4078-4-0-6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#8080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0;stroke-dasharray:2, 4;stroke-dashoffset:0"
+ d="m 70.866173,290.55116 0,17.71653"
+ id="path4156-9"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#808000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 70.866173,290.55116 0,17.71653"
+ id="path4176-4"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#808000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 106.29924,308.26769 0,-17.71653"
+ id="path4178-9"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#808000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 194.88192,379.13383 0,17.71654"
+ id="path4180-3"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#808000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 336.61419,396.85037 0,-17.71654"
+ id="path4182-8"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#8080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 70.866173,290.55116 0,17.71653"
+ id="path4186-1"
+ inkscape:connector-curvature="0"
+ inkscape:export-filename="/home/sg/dev/dillo/dillo/doc/path4188.png"
+ inkscape:export-xdpi="69"
+ inkscape:export-ydpi="69"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#8080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 106.29924,308.26769 0,-17.71653"
+ id="path4188-2"
+ inkscape:connector-curvature="0"
+ inkscape:export-xdpi="69"
+ inkscape:export-ydpi="69" />
+ <path
+ style="fill:none;stroke:#8080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 194.88192,379.13383 0,17.71654"
+ id="path4190-8"
+ inkscape:connector-curvature="0"
+ inkscape:export-filename="/home/sg/dev/dillo/dillo/doc/path4188.png"
+ inkscape:export-xdpi="69"
+ inkscape:export-ydpi="69" />
+ <path
+ style="fill:none;stroke:#8080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 336.61419,396.85037 0,-17.71654"
+ id="path4192-4"
+ inkscape:connector-curvature="0"
+ inkscape:export-filename="/home/sg/dev/dillo/dillo/doc/path4188.png"
+ inkscape:export-xdpi="69"
+ inkscape:export-ydpi="69" />
+ <path
+ style="fill:none;stroke:#808080;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 70.866173,290.55116 0,17.71653"
+ id="path4078-6-0-4"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#808080;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 159.44883,290.55116 0,17.71653"
+ id="path4078-6-0-9-0"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#808080;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 194.88192,379.13383 0,17.71654"
+ id="path4078-6-0-0-7"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#808080;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 336.61419,379.13383 0,17.71654"
+ id="path4078-6-0-6-1"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <rect
+ style="fill:#80ff00;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect3816-4-2-6-7-8"
+ width="53.149616"
+ height="17.716539"
+ x="194.88191"
+ y="396.85037" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:4, 2;stroke-dashoffset:0"
+ d="m 106.29924,308.26769 0,17.71654"
+ id="path4078-6-0-9-5-1"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:none;marker-end:url(#Arrow1Mend)"
+ d="m 88.5827,329.52754 c 88.58267,28.34645 0,0 88.58267,28.34645"
+ id="path3803-4"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:none;marker-end:url(#Arrow1Mend)"
+ d="m 131.10238,329.52754 c 70.86614,24.80315 53.14961,3.5433 88.58268,63.77952"
+ id="path3803-4-0"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:none;marker-end:url(#Arrow1Mend)"
+ d="m 177.16537,294.09447 c 88.58268,99.21259 0,0 88.58268,99.21259"
+ id="path3803-4-01"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-size:20px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ x="17.716536"
+ y="42.519665"
+ id="text3012-5-7"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3014-0-2"
+ x="17.716536"
+ y="42.519665"
+ style="font-size:16px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Nimbus Sans L;-inkscape-font-specification:Nimbus Sans L">Example 1:</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:20px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ x="528"
+ y="389.36218"
+ id="text5205"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan5207"
+ x="528"
+ y="389.36218" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:20px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ x="17.716536"
+ y="255.11809"
+ id="text3012-5-7-9"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ x="17.716536"
+ y="255.11809"
+ style="font-size:16px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Nimbus Sans L;-inkscape-font-specification:Nimbus Sans L"
+ id="tspan5230">Example 2:</tspan></text>
+ </g>
+</svg>
diff --git a/doc/rounding-errors.doc b/devdoc/rounding-errors.doc
index a442033e..a442033e 100644
--- a/doc/rounding-errors.doc
+++ b/devdoc/rounding-errors.doc
diff --git a/doc/uml-legend.doc b/devdoc/uml-legend.doc
index 54004ccd..54004ccd 100644
--- a/doc/uml-legend.doc
+++ b/devdoc/uml-legend.doc
diff --git a/dillorc b/dillorc
index e3d859c9..fb37a86f 100644
--- a/dillorc
+++ b/dillorc
@@ -79,6 +79,15 @@
# 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=YES
+
+# 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" so that it is able to mimic Firefox, which
+# differenciates between tables and, say, textblocks (in some cases).
+#adjust_table_min_width=YES
#-------------------------------------------------------------------------
# PENALTIES
@@ -175,7 +184,21 @@ 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
-# Set the proxy information for http.
+# 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.
+#http_persistent_conns=NO
+
+# This mechanism allows servers to specify that they are only to be contacted
+# through HTTPS and not HTTP.
+#
+# On the whole, this is a valuable security measure against TLS stripping
+# attacks, etc., but in principle a site could contrive to use this as a
+# tracking mechanism. The term is "HSTS super cookie", although note that these
+* HSTS directives are not saved between browser sessions.
+#http_strict_transport_security=YES
+
+# Set the proxy information for http/https.
# Note that the http_proxy environment variable overrides this setting.
# WARNING: FTP and downloads plugins use wget. To use a proxy with them,
# you will need to configure wget accordingly. See
diff --git a/doc/Dw.txt b/doc/Dw.txt
deleted file mode 100644
index f6909380..00000000
--- a/doc/Dw.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-Last update: Oct 2008
-
-================
-Dw: Dillo Widget
-================
-
-Dw is the internal widget library for rendering HTML. It has excellent
-documentation.
-
- Just run "doxygen" and browse the html/ directory!
-
diff --git a/doc/Imgbuf.txt b/doc/Imgbuf.txt
deleted file mode 100644
index f4a56660..00000000
--- a/doc/Imgbuf.txt
+++ /dev/null
@@ -1,177 +0,0 @@
-Aug 2004, S.Geerken@ping.de
-
-=============
-Image Buffers
-=============
-
-General
-=======
-
-Image buffers depend on the platform (see DwRender.txt), but have a
-general, platform independant interface, which is described in this
-section. The next section describes the Gdk version of Imgbuf.
-
-The structure ImgBuf will become part of the image processing, between
-image data decoding and the widget DwImage. Its purposes are
-
- 1. storing the image data,
- 2. handling scaled versions of this buffer, and
- 3. drawing.
-
-The latter must be done independently from the window.
-
-Storing Image Data
-------------------
-Imgbuf supports five image types, which are listed in the table
-below. The representation defines, how the colors are stored within
-the data, which is passed to a_Imgbuf_copy_row().
-
- | bytes per |
- type | pixel | representation
- ---------------+-----------+-------------------------
- RGB | 3 | red, green, blue
- RGBA | 4 | red, green, blue, alpha
- gray | 1 | gray value
- indexed | 1 | index to colormap
- indexed alpha | 1 | index to colormap
-
-The last two types need a colormap, which is set by
-a_Imgbuf_set_cmap(), which must be called before
-a_Imgbuf_copy_row(). This function expects the colors as 32 bit
-unsigned integers, which have the format 0xrrbbgg (for indexed
-images), or 0xaarrggbb (for indexed alpha), respectively.
-
-Scaling
--------
-The buffer with the original size, which was created by
-a_Imgbuf_new(), is called root buffer. Imgbuf provides the ability to
-scale buffers. Generally, both root buffers, as well as scaled
-buffers, may be shared, memory management is done by reference
-counters.
-
-Via a_Imgbuf_get_scaled_buf(), you can retrieve a scaled buffer. The
-way, how this function works in detail, is described in the code, but
-generally, something like this works always, in an efficient way:
-
- old_buf = cur_buf;
- cur_buf = a_Imgbuf_get_scaled_buf(old_buf, with, height);
- a_Imgbuf_unref (old_buf);
-
-Old_buf may both be a root buffer, or a scaled buffer.
-
-(As an exception, there should always be a reference on the root
-buffer, since scaled buffers cannot exist without the root buffer, but
-on the other side, do not hold references on it. So, if in the example
-above, old_buf would be a root buffer, and there would, at the
-beginning, only be one reference on it, new_buf would also be
-destroyed, along with old_buf. Therefore, an external reference must
-be added to the root buffer, which is in dillo done within the dicache
-module.)
-
-The root buffer keeps a list of all children, and all operations
-operating on the image data (a_Imgbuf_copy_row() and
-a_Imgbuf_set_cmap()) are delegated to the scaled buffers, when
-processed, and inherited, when a new scaled buffer is created. This
-means, that they must only be performed for the root buffer.
-
-Drawing
--------
-There are two situations, when drawing is necessary:
-
- 1. To react on expose events, the function a_Imgbuf_draw() can be
- used. Notice that the exact signature of this function is
- platform dependant.
-
- 2. When a row has been copied, it has to be drawn. To determine the
- area, which has to be drawn, the function
- a_Imgbuf_get_row_area() should be used. In dillo, the dicache
- module will first call a_Img_copy_row(), and then call
- a_Dw_image_draw_row() for the images connected to this image
- buffer. a_Dw_image_draw_row() will then call
- p_Dw_widget_queue_draw(), with an area determined by
- a_Imgbuf_get_row_area().
-
-
-The Gdk Implementation
-======================
-
-The Gdk implementation is used by the Gtk+ platform. [... todo]
-
-
-Global Scalers
-==============
-
-In some cases, there is a context, where images have to be scaled
-often, by a relatively constant factor. For example, the preview
-window (GtkDwPreview) draws images via the Imgbuf draw functions, but
-uses scaled buffers. Scaling such a buffer each time it is needed,
-causes huge performance losses. On the other hand, if the preview
-window would keep these scaled buffers (e.g. by lazy mapping of the
-original buffer to the scaled buffer), the scaled buffers get never
-freed, since the view is not told about, when the original buffer is
-not needed anymore. (n.b., that currently, the scaled buffers are
-destroyed, when the original buffer is destroyed, but this may change,
-and even this would leave "zombies" in this mapping structure, where
-the values refer to dead pointers).
-
-It is sufficient, that references on the scaled buffers are referred
-somehow, so that they do not get destroyed between different
-usages. The caller (in this case the preview) simply requests a scaled
-buffer, but the Imgbuf returns this from the list of already scaled
-buffers.
-
-These references are hold by special structures, which are called
-"scalers". There are two types of scalers, local scalers, which are
-bound to image buffers, and global scalers, which refer to multiple
-scalers.
-
-What happens in different situations:
-
- - The caller (e.g. the preview) requests a scaled buffer. For this,
- it uses a special method, which also passes the global image
- scaler, which was created before (for the preview, there is a 1-1
- association). The Imgbuf uses this global image scaler, to
- identify the caller, and keeps a list of them. If this global
- scaler is not yet in the list, it is added, and a local scaler is
- created.
-
-
-
- -
-
-There are three images in the page, i1a, i1b, and i2. I1a and i1b
-refer to the same image recource, represented by the root image buffer
-iba, which original size is 200 x 200. I1a is displayed in original
-size, while i1b is displayed at 100 x 100. I2 refers to an other
-recource, ibb, which has the size 300 x 300. I2 is shown in original
-size.
-
-
- :DwRenderLayout ------------------- :DwPage ----------.
- / \ |
- ,----' `----. ,------ i1a:DwImage --+
- / \ | |
- view1:GtkDwViewport view2:GtkDwPreview | ,---- i1b:DwImage --|
- | | | |
- ,------------------------------' | | ,-- i2: DwImage --'
- | | | |
- | ,-------------------------------------' | |
- | | ,--------------------------------' |
- | | | ,----'
- | | | |
- | V | V
- | iba:Imgbuf | ibb:Imgbuf -- 30x30
- | | | V | ^
- | | +- 100x100 ,- 20x20 ,- 10x10 | |
- | | | | ^ | ^ | |
- | | `----------+----|---' | `--. ,--'
- | | ,--------------' | | |
- | | | ,------------------' | |
- | | | | | |
- | lca:ImgbufLSc lcb:ImgbufLSc
- | (factor 1/10) (factor 1/10)
- | \ /
- | `-----------. ,-------------------'
- | \ /
- `------------------> scl:ImgbufGSc
- (factor 1/10)
diff --git a/doc/Makefile.am b/doc/Makefile.am
index d48e3e73..7b40d0a2 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -1,47 +1,10 @@
dist_doc_DATA = user_help.html
man_MANS = dillo.1
EXTRA_DIST = \
- index.doc \
- lout.doc \
- dw-map.doc \
- dw-overview.doc \
- dw-usage.doc \
- dw-layout-views.doc \
- dw-layout-widgets.doc \
- dw-widget-sizes.doc \
- dw-changes.doc \
- dw-images-and-backgrounds.doc \
- fltk-problems.doc \
- rounding-errors.doc \
- uml-legend.doc \
- dw-line-breaking.doc \
- dw-example-screenshot.png \
- dw-viewport-without-scrollbar.png \
- dw-viewport-with-scrollbar.png \
- dw-size-of-widget.png \
- dw-style-box-model.png \
- dw-style-length-absolute.png \
- dw-style-length-percentage.png \
- dw-style-length-relative.png \
- dw-textblock-collapsing-spaces-1-1.png \
- dw-textblock-collapsing-spaces-1-2.png \
- dw-textblock-collapsing-spaces-2-1.png \
- dw-textblock-collapsing-spaces-2-2.png \
- not-so-simple-container.png \
- Cache.txt \
- Cookies.txt \
- Dillo.txt \
- Dw.txt \
- HtmlParser.txt \
- IO.txt \
- Images.txt \
- Imgbuf.txt \
- NC_design.txt \
- Selection.txt \
- Dpid.txt \
- CCCwork.txt \
README \
- dillo.1.in
+ Cookies.txt \
+ dillo.1.in \
+ user_help.html
dillo.1: $(srcdir)/dillo.1.in Makefile
sed 's%/usr/local%${prefix}%g' < $(srcdir)/dillo.1.in > dillo.1
diff --git a/doc/README b/doc/README
index 9736a32b..41c11d87 100644
--- a/doc/README
+++ b/doc/README
@@ -1,51 +1,5 @@
-README: Last update Jul 2009
+Last update: June 2015
-These documents cover dillo's internals.
-For user help, see http://www.dillo.org/dillo3-help.html
-
---------------------------------------------------------------------------
-
-These documents need a review.
-*.txt were current with Dillo1, but many have since become more or
- less out-of-date.
-*.doc are doxygen source for the Dillo Widget (dw) component, and
- were written for Dillo2.
-
-They will give you an overview of what's going on, but take them
-with a pinch of salt.
-
- Of course I'd like to have *.txt as doxygen files too!
-If somebody wants to make this conversion, please let me know
-to assign higher priority to updating these docs.
-
---
-Jorge.-
-
- --------------------------------------------------------------------------
- FILE DESCRIPTION STATE
- --------------------------------------------------------------------------
- NC_design.txt Naming&Coding design (Be sure to Current
- read it before any other doc)
- Dillo.txt General overview of the program Current
- IO.txt Extensive introduction Current
- Cache.txt Informative description Current
- Images.txt Image handling and processing Current
- HtmlParser.txt A versatile parser Current
- Dw.txt The New Dillo Widget (Overview) Current
- Imgbuf.txt Image buffers Pending
- Selection.txt Selections, and link activation Current (?)
- Cookies.txt Explains how to enable cookies Current
- Dpid.txt Dillo plugin daemon Current
- --------------------------------------------------------------------------
-
-
- * BTW, there's a small program (srch) within the src/ dir. It searches
- tokens within the whole code (*.[ch]). It has proven very useful.
- Ex: ./srch a_Image_write
- ./srch todo:
-
- * Please submit your patches with 'hg diff'.
-
-
- Happy coding!
- --Jcid
+This directory contains user documentation. Developer documentation is
+only stored in the Hg repository at <http://hg.dillo.org/dillo/>, in
+the directory "devdoc", but not part of the tarball.
diff --git a/doc/Selection.txt b/doc/Selection.txt
deleted file mode 100644
index 7904bd94..00000000
--- a/doc/Selection.txt
+++ /dev/null
@@ -1,149 +0,0 @@
-Apr 2003, S.Geerken@ping.de
-Last update: Dec 2004
-
-=========
-Selection
-=========
-
-The selection module (selection.[ch]) handles selections, as well as
-activation of links, which is closely related.
-
-
-General Overview
-================
-
-The selection module defines a structure "Selection", which is
-associated to GtkDwViewport, and so to a widget tree. The selection
-state is controlled by "abstract events", which are sent by single
-widgets by calling one of the following functions:
-
- a_Selection_button_press for button press events,
- a_Selection_button_release for button release events, and
- a_Selection_button_motion for motion events (with pressed mouse
- button).
-
-The widget must construct simple iterators (DwIterator), which will be
-transferred to extended iterators (DwExtIterator), see below for more
-details. All event handling functions have the same signature, the
-arguments in detail are:
-
- - DwIterator *it the iterator pointing on the item under
- the mouse pointer,
- - gint char_pos the exact (character) position within
- the iterator,
- - gint link if this item is associated with a link,
- its number (see DwImage, section
- "signals" for the meaning), otherwise
- -1,
- - GdkEventButton *event the event itself; only the button is
- used,
- - gboolean within_content TRUE, if there is some selectable
- content unter the mouse cursor; if set
- to FALSE, the "full screen" feature is
- used on double click.
-
-In some cases, char_pos would be difficult to determine. E.g., when
-the DwPage widget decides that the user is pointing on a position
-_at_the_end_ of an image (DwImage), it constructs a simple iterator
-pointing on this image widget. In a simple iterator, that fact that
-the pointer is at the end, would be represented by char_pos == 1. But
-when transferring this simple iterator into an extended iterator, this
-simple iterator is discarded and instead the stack has an iterator
-pointing to text at the top. As a result, only the first letter of the
-ALT text would be copied.
-
-To avoid this problem, widgets should in this case pass SELECTION_EOW
-(end of word) as char_pos, which is then automatically reduced to the
-actual length of the extended(!) iterator.
-
-The return value is the same as in DwWidget event handling methods.
-I.e., in most cases, they should simply return it. The events
-"link_pressed", "link_released" and "link_clicked" (but not
-"link_entered") are emitted by these functions, so that widgets which
-let the selection module handle links, should only emit "link_entered"
-for themselves. (See DwImage.txt for a description of this.)
-
-
-Selection State
-===============
-
-Selection interferes with handling the activation of links, so the
-latter is also handled by the selection module. Details are based on
-following guidelines:
-
- 1. It should be simple to select links and to start selection in
- links. The rule to distinguish between link activation and
- selection is that the selection starts as soon as the user leaves
- the link. (This is, IMO, a useful feature. Even after drag and
- drop has been implemented in dillo, this should be somehow
- preserved.)
-
- 2. The selection should stay as long as possible, i.e., the old
- selection is only cleared when a new selection is started.
-
-The latter leads to a model with two states: the selection state and
-the link handling state.
-
-The general selection works, for events not pointing on links, like
-this (numbers in parantheses after the event denote the button, "n"
-means arbitrary button):
-
- motion(1)
- ,-----.
- | |
- press(1) on non-link V |
- NONE -----------------------> SELECTING <----------------.
- ^ | |
- | | release(1) |
- | | | press(1)
- | no V yes |
- `----------------------- Anything selected? --------> SELECTED
-
-The selected region is represented by two DwExtIterators.
-
-Links are handled by a different state machine:
-
- ,-----------------------------.
- | |
- | Switch to selection
- | (SELECTING) for n == 1.
- | ^
- | | no
- | | yes
- | Still the same link? --.
- | ^ |
- | | |
- | | motion(n) |
- V press(n) on links | |
- NONE ---------------------> PRESSED(n) <-----'
- ^ |
- | | release(n)
- | |
- | V yes
- | Still the same link? -----------------.
- | | |
- | | no V
- | V Send "clicked" signal.
- | Switch to selection |
- | (SELECTED) for n == 1. |
- | | |
- |`----------------------------' |
- | |
- `----------------------------------------------------------'
-
-Switching to selection simply means that the selection state will
-eventually be SELECTED/SELECTING, with the original and the actual
-position making up the selection region. This happens for button 1,
-events with buttons other than 1 do not affect selection at all.
-
-
-TODO
-====
-
-* a_Selection_button_motion currently always assumes that button 1 has
- been pressed (since otherwise it would not do anything). This should
- be made a bit cleaner.
-
-* The selection should be cleared, when the user selects something
- somewhere else (perhaps switched into "non-active" mode, as some
- Gtk+ widgets do).
diff --git a/doc/dillo.1.in b/doc/dillo.1.in
index 3bb5fe03..0853e6eb 100644
--- a/doc/dillo.1.in
+++ b/doc/dillo.1.in
@@ -1,4 +1,4 @@
-.TH dillo 1 "December 20, 2014" "" "USER COMMANDS"
+.TH dillo 1 "May 28, 2015" "" "USER COMMANDS"
.SH NAME
dillo \- web browser
.SH SYNOPSIS
@@ -11,7 +11,7 @@ dillo \- web browser
Dillo is a lightweight graphical web browser that aims to be secure.
It handles HTTP internally, and FILE, FTP, and
DATA URIs are handled through a plugin system (dpi). In addition,
-.I INSECURE
+.I EXPERIMENTAL
HTTPS support can be enabled. Both FTP and Dillo's download manager use the
.BR wget (1)
downloader.
@@ -68,7 +68,7 @@ Error in command line arguments.
User's home directory.
.TP
.B http_proxy
-URL of proxy to send HTTP traffic through.
+URL of proxy to send HTTP/HTTPS traffic through.
.SH FILES
.TP
.I dpid
diff --git a/doc/dw-example-screenshot.png b/doc/dw-example-screenshot.png
deleted file mode 100644
index a4d37903..00000000
--- a/doc/dw-example-screenshot.png
+++ /dev/null
Binary files differ
diff --git a/doc/dw-size-of-widget.png b/doc/dw-size-of-widget.png
deleted file mode 100644
index eda93ee1..00000000
--- a/doc/dw-size-of-widget.png
+++ /dev/null
Binary files differ
diff --git a/doc/dw-style-box-model.png b/doc/dw-style-box-model.png
deleted file mode 100644
index aa65ecb7..00000000
--- a/doc/dw-style-box-model.png
+++ /dev/null
Binary files differ
diff --git a/doc/dw-style-length-absolute.png b/doc/dw-style-length-absolute.png
deleted file mode 100644
index 6b4d8389..00000000
--- a/doc/dw-style-length-absolute.png
+++ /dev/null
Binary files differ
diff --git a/doc/dw-style-length-percentage.png b/doc/dw-style-length-percentage.png
deleted file mode 100644
index 15b36958..00000000
--- a/doc/dw-style-length-percentage.png
+++ /dev/null
Binary files differ
diff --git a/doc/dw-style-length-relative.png b/doc/dw-style-length-relative.png
deleted file mode 100644
index d54d99b5..00000000
--- a/doc/dw-style-length-relative.png
+++ /dev/null
Binary files differ
diff --git a/doc/dw-widget-sizes.doc b/doc/dw-widget-sizes.doc
deleted file mode 100644
index 419a4a73..00000000
--- a/doc/dw-widget-sizes.doc
+++ /dev/null
@@ -1,186 +0,0 @@
-/** \page dw-widget-sizes Sizes of Dillo Widgets
-
-<h2>Allocation</h2>
-
-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 \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.
-
-<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 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
-divided into an ascender (which height is called \em ascent), and a
-descender (which height is called \em descent). The total height is so
-the sum of \em ascent and \em descent.
-
-Sizes of zero are allowed. The upper limit for the size of a widget is
-defined by the limits of the C++ type \em int.
-
-\image html dw-size-of-widget.png Allocation of a Widget
-
-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>
-
-The current allocation of a widget is hold in
-dw::core::Widget::allocation. It can be set from outside by
-calling dw::core::Widget::sizeAllocate. This is a concrete method,
-which will call dw::core::Widget::sizeAllocateImpl (see code of
-dw::core::Widget::sizeAllocate for details).
-
-For trivial widgets (like dw::Bullet),
-dw::core::Widget::sizeAllocateImpl does not need to be
-implemented. For more complex widgets, the implementation should call
-dw::core::Widget::sizeAllocate (not
-dw::core::Widget::sizeAllocateImpl) on all child widgets, with
-appropriate child allocations. dw::core::Widget::allocation should not
-be changed here, this is already done in
-dw::core::Widget::sizeAllocate.
-
-<h2>Requisitions</h2>
-
-A widget may prefer a given size for the allocation. This size, the
-\em requisition, should be returned by the method
-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.
-
-<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>
-
-This is a bit simplified, dw::Image::sizeRequestImpl should also deal
-with margins, borders and paddings, see dw::core::style.
-
-From the outside, dw::Image::sizeRequest should be called, which does
-a bit of optimization. Notice that in dw::Image::sizeRequestImpl, no
-optimization like lazy evaluation is necessary, this is already done
-in dw::Image::sizeRequest.
-
-A widget, which has children, will likely call dw::Image::sizeRequest
-on its children, to calculate the total requisition.
-
-The caller (this is either the dw::core::Layout, or the parent
-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.
-
-A widget, which depends on size hints, should call
-dw::core::Widget::queueResize, when apropriate.
-
-\todo There should be a definition of "available space".
-
-<h2>Width Extremes</h2>
-
-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>
-
-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).
-
-<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.
-
-<li> dw::Table is an example, where the width extremes are calculated
- from the width extremes of the children.
-</ul>
-
-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>
-
-When the widget changes its size (requisition), it should call
-dw::core::Widget::queueResize. The next call of
-dw::core::Widget::sizeRequestImpl should then return the new
-size. See dw::Image::setBuffer as an example.
-
-Interna are described in the code of dw::core::Widget::queueResize.
-
-<h3>Incremental Resizing</h3>
-
-A widget may calculate its size based on size calculations already
-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
- 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>
-
-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.
-
-*/
diff --git a/doc/not-so-simple-container.png b/doc/not-so-simple-container.png
deleted file mode 100644
index 886b72a8..00000000
--- a/doc/not-so-simple-container.png
+++ /dev/null
Binary files differ
diff --git a/dpi/cookies.c b/dpi/cookies.c
index b858bd53..46be18fc 100644
--- a/dpi/cookies.c
+++ b/dpi/cookies.c
@@ -45,6 +45,7 @@ int main(void)
#include <stdio.h>
#include <time.h> /* for time() and time_t */
#include <ctype.h>
+#include <limits.h>
#include <netdb.h>
#include <signal.h>
#include "dpiutil.h"
@@ -607,7 +608,7 @@ static void Cookies_too_many(DomainNode *node)
{
CookieData_t *lru = Cookies_get_LRU(node ? node->cookies : all_cookies);
- MSG("Too many cookies!\n"
+ MSG("Too many cookies! "
"Removing LRU cookie for \'%s\': \'%s=%s\'\n", lru->domain,
lru->name, lru->value);
if (!node)
@@ -835,11 +836,20 @@ static CookieData_t *Cookies_parse(char *cookie_str, const char *server_date)
} else if (dStrAsciiCasecmp(attr, "Max-Age") == 0) {
value = Cookies_parse_value(&str);
if (isdigit(*value) || *value == '-') {
+ long age;
time_t now = time(NULL);
- long age = strtol(value, NULL, 10);
struct tm *tm = gmtime(&now);
- tm->tm_sec += age;
+ errno = 0;
+ age = (*value == '-') ? 0 : strtol(value, NULL, 10);
+
+ if (errno == ERANGE ||
+ (age > 0 && (age > INT_MAX - tm->tm_sec))) {
+ /* let's not overflow */
+ tm->tm_sec = INT_MAX;
+ } else {
+ tm->tm_sec += age;
+ }
cookie->expires_at = mktime(tm);
if (age > 0 && cookie->expires_at == (time_t) -1) {
cookie->expires_at = cookies_future_time;
@@ -1142,14 +1152,14 @@ static int Cookies_set(char *cookie_string, char *url_host,
* Compare the cookie with the supplied data to see whether it matches
*/
static bool_t Cookies_match(CookieData_t *cookie, const char *url_path,
- bool_t host_only_val, bool_t is_ssl)
+ bool_t host_only_val, bool_t is_tls)
{
if (cookie->host_only != host_only_val)
return FALSE;
/* Insecure cookies match both secure and insecure urls, secure
cookies match only secure urls */
- if (cookie->secure && !is_ssl)
+ if (cookie->secure && !is_tls)
return FALSE;
if (!Cookies_path_matches(url_path, cookie->path))
@@ -1163,7 +1173,7 @@ static void Cookies_add_matching_cookies(const char *domain,
const char *url_path,
bool_t host_only_val,
Dlist *matching_cookies,
- bool_t is_ssl)
+ bool_t is_tls)
{
DomainNode *node = dList_find_sorted(domains, domain,
Domain_node_by_domain_cmp);
@@ -1183,7 +1193,7 @@ static void Cookies_add_matching_cookies(const char *domain,
--i; continue;
}
/* Check if the cookie matches the requesting URL */
- if (Cookies_match(cookie, url_path, host_only_val, is_ssl)) {
+ if (Cookies_match(cookie, url_path, host_only_val, is_tls)) {
int j;
CookieData_t *curr;
uint_t path_length = strlen(cookie->path);
@@ -1213,7 +1223,7 @@ static char *Cookies_get(char *url_host, char *url_path,
char *domain_str, *str;
CookieData_t *cookie;
Dlist *matching_cookies;
- bool_t is_ssl, is_ip_addr, host_only_val;
+ bool_t is_tls, is_ip_addr, host_only_val;
Dstr *cookie_dstring;
int i;
@@ -1224,7 +1234,7 @@ static char *Cookies_get(char *url_host, char *url_path,
matching_cookies = dList_new(8);
/* Check if the protocol is secure or not */
- is_ssl = (!dStrAsciiCasecmp(url_scheme, "https"));
+ is_tls = (!dStrAsciiCasecmp(url_scheme, "https"));
is_ip_addr = Cookies_domain_is_ip(url_host);
@@ -1240,17 +1250,17 @@ static char *Cookies_get(char *url_host, char *url_path,
/* e.g., sub.example.com set a cookie with domain ".sub.example.com". */
domain_str = dStrconcat(".", url_host, NULL);
Cookies_add_matching_cookies(domain_str, url_path, host_only_val,
- matching_cookies, is_ssl);
+ matching_cookies, is_tls);
dFree(domain_str);
}
host_only_val = TRUE;
/* e.g., sub.example.com set a cookie with no domain attribute. */
Cookies_add_matching_cookies(url_host, url_path, host_only_val,
- matching_cookies, is_ssl);
+ matching_cookies, is_tls);
host_only_val = FALSE;
/* e.g., sub.example.com set a cookie with domain "sub.example.com". */
Cookies_add_matching_cookies(url_host, url_path, host_only_val,
- matching_cookies, is_ssl);
+ matching_cookies, is_tls);
if (!is_ip_addr) {
for (domain_str = strchr(url_host+1, '.');
@@ -1258,12 +1268,12 @@ static char *Cookies_get(char *url_host, char *url_path,
domain_str = strchr(domain_str+1, '.')) {
/* e.g., sub.example.com set a cookie with domain ".example.com". */
Cookies_add_matching_cookies(domain_str, url_path, host_only_val,
- matching_cookies, is_ssl);
+ matching_cookies, is_tls);
if (domain_str[1]) {
domain_str++;
/* e.g., sub.example.com set a cookie with domain "example.com".*/
Cookies_add_matching_cookies(domain_str, url_path, host_only_val,
- matching_cookies, is_ssl);
+ matching_cookies, is_tls);
}
}
}
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(); };
diff --git a/dpid/dpid_common.h b/dpid/dpid_common.h
index cc7505a9..6df55ae1 100644
--- a/dpid/dpid_common.h
+++ b/dpid/dpid_common.h
@@ -18,7 +18,6 @@
*/
#define _MSG(...)
#define MSG(...) printf("[dpid]: " __VA_ARGS__)
-#define _MSG_ERR(...)
#define MSG_ERR(...) fprintf(stderr, "[dpid]: " __VA_ARGS__)
#define dotDILLO_DPI ".dillo/dpi"
diff --git a/dw/Makefile.am b/dw/Makefile.am
index d0d56d2a..68410805 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,16 @@ libDw_widgets_a_SOURCES = \
image.hh \
listitem.cc \
listitem.hh \
+ outofflowmgr.cc \
+ outofflowmgr.hh \
+ regardingborder.cc \
+ regardingborder.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..e65bbf2c
--- /dev/null
+++ b/dw/alignedtablecell.cc
@@ -0,0 +1,205 @@
+/*
+ * 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 ();
+}
+
+bool AlignedTableCell::mustBeWidenedToAvailWidth ()
+{
+ return tablecell::mustBeWidenedToAvailWidth ();
+}
+
+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,
+ bool useAdjustmentWidth)
+{
+ 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,
+ useAdjustmentWidth);
+ tablecell::correctCorrectedExtremesOfChild (this, child, extremes,
+ useAdjustmentWidth);
+
+ 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..b4203047
--- /dev/null
+++ b/dw/alignedtablecell.hh
@@ -0,0 +1,47 @@
+#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 useAdjustmentWidth);
+
+ 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 ();
+
+ bool mustBeWidenedToAvailWidth ();
+};
+
+} // namespace dw
+
+#endif // __DW_ALIGNEDTABLECELL_HH__
diff --git a/dw/bullet.cc b/dw/bullet.cc
index af7f5451..40c197e6 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,20 @@ void Bullet::sizeRequestImpl (core::Requisition *requisition)
requisition->descent = 0;
}
+void Bullet::getExtremesImpl (core::Extremes *extremes)
+{
+ extremes->minWidth = extremes->maxWidth = extremes->adjustmentWidth =
+ extremes->minWidthIntrinsic = extremes->maxWidthIntrinsic =
+ lout::misc::max (getStyle()->font->xHeight * 4 / 5, 1);
+}
+
+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 01bce455..df387dfb 100644
--- a/dw/fltkimgbuf.cc
+++ b/dw/fltkimgbuf.cc
@@ -145,6 +145,12 @@ void FltkImgbuf::init (Type type, int width, int height, double gamma,
deleteOnUnref = true;
copiedRows = new lout::misc::BitSet (height);
+ DBG_IF_RTFL {
+ lout::misc::StringBuffer sb;
+ copiedRows->intoStringBuffer (&sb);
+ DBG_OBJ_SET_SYM ("copiedRows", sb.getChars ());
+ }
+
// The list is only used for root buffers.
if (isRoot())
scaledBuffers = new lout::container::typed::List <FltkImgbuf> (true);
@@ -219,6 +225,13 @@ inline void FltkImgbuf::scaleRowSimple (int row, const core::byte *data)
if (copiedRows->get(sr)) continue;
copiedRows->set (sr, true);
+
+ DBG_IF_RTFL {
+ lout::misc::StringBuffer sb;
+ copiedRows->intoStringBuffer (&sb);
+ DBG_OBJ_SET_SYM ("copiedRows", sb.getChars ());
+ }
+
if (sr == sr1) {
for (int px = 0; px < root->width; px++) {
int px1 = px * width / root->width;
@@ -249,6 +262,12 @@ inline void FltkImgbuf::scaleRowBeautiful (int row, const core::byte *data)
// Mark scaled rows done
for (int sr = sr1; sr < sr2 || sr == sr1; sr++)
copiedRows->set (sr, true);
+
+ DBG_IF_RTFL {
+ lout::misc::StringBuffer sb;
+ copiedRows->intoStringBuffer (&sb);
+ DBG_OBJ_SET_SYM ("copiedRows", sb.getChars ());
+ }
} else {
assert (sr1 == sr2 || sr1 + 1 == sr2);
int row1 = backscaledY(sr1), row2 = backscaledY(sr1 + 1);
@@ -263,6 +282,12 @@ inline void FltkImgbuf::scaleRowBeautiful (int row, const core::byte *data)
bpp, gamma);
// Mark scaled row done
copiedRows->set (sr1, true);
+
+ DBG_IF_RTFL {
+ lout::misc::StringBuffer sb;
+ copiedRows->intoStringBuffer (&sb);
+ DBG_OBJ_SET_SYM ("copiedRows", sb.getChars ());
+ }
}
}
}
@@ -333,6 +358,13 @@ void FltkImgbuf::copyRow (int row, const core::byte *data)
if (row < height) {
// Flag the row done and copy its data.
copiedRows->set (row, true);
+
+ DBG_IF_RTFL {
+ lout::misc::StringBuffer sb;
+ copiedRows->intoStringBuffer (&sb);
+ DBG_OBJ_SET_SYM ("copiedRows", sb.getChars ());
+ }
+
memcpy(rawdata + row * width * bpp, data, width * bpp);
// Update all the scaled buffers of this root image.
@@ -350,6 +382,12 @@ void FltkImgbuf::newScan ()
for (Iterator<FltkImgbuf> it = scaledBuffers->iterator(); it.hasNext();){
FltkImgbuf *sb = it.getNext ();
sb->copiedRows->clear();
+
+ DBG_IF_RTFL {
+ lout::misc::StringBuffer sb;
+ copiedRows->intoStringBuffer (&sb);
+ DBG_OBJ_SET_SYM ("copiedRows", sb.getChars ());
+ }
}
}
}
diff --git a/dw/fltkplatform.hh b/dw/fltkplatform.hh
index 8f644227..60dca7f2 100644
--- a/dw/fltkplatform.hh
+++ b/dw/fltkplatform.hh
@@ -151,7 +151,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 e22e4619..51523b95 100644
--- a/dw/fltkui.cc
+++ b/dw/fltkui.cc
@@ -398,6 +398,8 @@ using namespace lout::container::typed;
FltkResource::FltkResource (FltkPlatform *platform)
{
+ DBG_OBJ_CREATE ("dw::fltk::ui::FltkResource");
+
this->platform = platform;
allocation.x = 0;
@@ -433,6 +435,8 @@ FltkResource::~FltkResource ()
}
if (style)
style->unref ();
+
+ DBG_OBJ_DELETE ();
}
void FltkResource::attachView (FltkView *view)
@@ -462,8 +466,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)
@@ -546,6 +556,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)
{
@@ -620,6 +644,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);
@@ -633,6 +659,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 ();
}
/*
@@ -766,6 +796,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 (),
@@ -892,6 +933,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);
@@ -908,6 +951,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)
@@ -915,6 +962,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 */
@@ -1035,6 +1087,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);
@@ -1053,6 +1107,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 ()
@@ -1114,6 +1172,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);
@@ -1127,6 +1187,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 ();
}
@@ -1366,6 +1430,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);
@@ -1380,6 +1446,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 ()
@@ -1655,6 +1725,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();
@@ -1675,6 +1747,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 3615714a..09cdc978 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 e71c8f2f..fd959695 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,116 @@ 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, false);
+
+ extremes->adjustmentWidth =
+ misc::min (extremes->minWidthIntrinsic, extremes->minWidth);
}
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)
@@ -429,15 +490,21 @@ void Image::setBuffer (core::Imgbuf *buffer, bool resize)
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.
- 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..b94f647d 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);
@@ -150,6 +153,10 @@ public:
Image(const char *altText);
~Image();
+ // For images, the minimal width is not well defined, and
+ // correction of the size makes not much sense.
+ virtual bool getAdjustMinWidth () { return false; }
+
core::Iterator *iterator (core::Content::Type mask, bool atEnd);
inline core::Imgbuf *getBuffer () { return buffer; }
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..8bd2a93a 100644
--- a/dw/listitem.cc
+++ b/dw/listitem.cc
@@ -40,6 +40,11 @@ ListItem::~ListItem()
DBG_OBJ_DELETE ();
}
+bool ListItem::mustBeWidenedToAvailWidth ()
+{
+ return true;
+}
+
void ListItem::initWithWidget (core::Widget *widget,
core::style::Style *style)
{
@@ -69,7 +74,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/listitem.hh b/dw/listitem.hh
index 2e303d5d..20fa6e9d 100644
--- a/dw/listitem.hh
+++ b/dw/listitem.hh
@@ -18,6 +18,8 @@ public:
ListItem(ListItem *ref, bool limitTextWidth);
~ListItem();
+ bool mustBeWidenedToAvailWidth ();
+
void initWithWidget (core::Widget *widget, core::style::Style *style);
void initWithText (const char *text, core::style::Style *style);
};
diff --git a/dw/outofflowmgr.cc b/dw/outofflowmgr.cc
new file mode 100644
index 00000000..5081a2cb
--- /dev/null
+++ b/dw/outofflowmgr.cc
@@ -0,0 +1,2293 @@
+/*
+ * 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,
+ getGBWidthForAllocation (vloat));
+ 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);
+
+ Allocation childAllocation;
+ childAllocation.x = cba->x + calcFloatX (vloat, side, gba->x - cba->x,
+ getGBWidthForAllocation (vloat));
+ 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 ();
+}
+
+// Used as argument "gbWidth" for calcFloatX(), in the context of allocation.
+int OutOfFlowMgr::getGBWidthForAllocation (Float *vloat)
+{
+ // See comments in getFloatsSize() for a detailed rationale ...
+ if (containingBlock->mustBeWidenedToAvailWidth ())
+ return vloat->generatingBlock->getLineBreakWidth ();
+ else
+ // ... but notice this difference: not GB width + float width is
+ // used, but only GB width, since the float width has already
+ // been included in getFloatsSize().
+ return min (getAllocation(vloat->generatingBlock)->width,
+ vloat->generatingBlock->getLineBreakWidth ());
+}
+
+/**
+ * \brief ...
+ *
+ * gbX is given relative to the CB, as is the return value.
+ */
+int OutOfFlowMgr::calcFloatX (Float *vloat, Side side, int gbX, int gbWidth)
+{
+ DBG_OBJ_ENTER ("resize.common", 0, "calcFloatX", "%p, %s, %d, %d",
+ vloat->getWidget (), side == LEFT ? "LEFT" : "RIGHT", gbX,
+ gbWidth);
+ 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).
+
+ x = max (gbX + gbWidth - 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, gbWidth, 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, getGBWidthForAllocation (vloat));
+
+ // 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;
+
+ int effWidth;
+ if (containingBlock->mustBeWidenedToAvailWidth ())
+ // For most textblocks, the line break width is used for
+ // calculating the x position. (This changed for GROWS,
+ // where the width of a textblock is often smaller that
+ // the line break.)
+ effWidth = vloat->generatingBlock->getLineBreakWidth ();
+ else
+ // For some textblocks, like inline blocks, the line break
+ // width would be too large for right floats in some
+ // cases.
+ //
+ // (i) Consider a small inline block with only a few words
+ // in one line, narrower that line break width minus
+ // float width. In this case, the sum should be used.
+ //
+ // (ii) If there is more than one line, the line break
+ // will already be exceeded, and so be smaller that
+ // GB width + float width.
+ effWidth = min (cbReq->width + vloat->size.width,
+ vloat->generatingBlock->getLineBreakWidth ());
+
+ if (vloat->generatingBlock == containingBlock) {
+ x = calcFloatX (vloat, side, 0, effWidth);
+ y = vloat->yReal;
+ } else {
+ Allocation *gba = getAllocation(vloat->generatingBlock);
+ x = calcFloatX (vloat, side, gba->x - containingBlockAllocation.x,
+ effWidth);
+ 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);
+
+ 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)) {
+ Extremes extr;
+ vloat->getWidget()->getExtremes (&extr);
+
+ // The calculation of extremes must be kept consistent with
+ // getFloatsSize(). Especially this means for the *minimal* width:
+ //
+ // - The right border (difference between float and
+ // container) does not have to be considered (see
+ // getFloatsSize().
+ //
+ // - This is also the case for the left border, as seen in
+ // calcFloatX() ("... but when the float exceeds the line
+ // break width" ...).
+
+ *minWidth = max (*minWidth, extr.minWidth);
+
+ // For the maximal width, borders must be considered.
+
+ if (vloat->generatingBlock == containingBlock)
+ *maxWidth =
+ max (*maxWidth,
+ extr.maxWidth
+ + vloat->generatingBlock->getStyle()->boxDiffWidth());
+ else {
+ Allocation *gba = getAllocation (vloat->generatingBlock);
+ *maxWidth =
+ max (*maxWidth,
+ extr.maxWidth
+ + vloat->generatingBlock->getStyle()->boxDiffWidth()
+ + max (containingBlockAllocation.width - gba->width, 0));
+ }
+
+ DBG_OBJ_MSGF ("resize.oofm", 1, "%d / %d => %d / %d",
+ extr.minWidth, extr.maxWidth, *minWidth, *maxWidth);
+ } else
+ DBG_OBJ_MSG ("resize.oofm", 1, "not allocated");
+ }
+
+ DBG_OBJ_LEAVE ();
+}
+
+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..78146d08
--- /dev/null
+++ b/dw/outofflowmgr.hh
@@ -0,0 +1,435 @@
+#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 getGBWidthForAllocation (Float *vloat);
+ int calcFloatX (Float *vloat, Side side, int gbX, int gbWidth);
+
+ 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);
+
+ 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/regardingborder.cc b/dw/regardingborder.cc
new file mode 100644
index 00000000..2eb0b4bf
--- /dev/null
+++ b/dw/regardingborder.cc
@@ -0,0 +1,39 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2015 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 "regardingborder.hh"
+
+#include <stdio.h>
+
+namespace dw {
+
+int RegardingBorder::CLASS_ID = -1;
+
+RegardingBorder::RegardingBorder ()
+{
+ DBG_OBJ_CREATE ("dw::RegardingBorder");
+ registerName ("dw::RegardingBorder", &CLASS_ID);
+}
+
+RegardingBorder::~RegardingBorder ()
+{
+ DBG_OBJ_DELETE ();
+}
+
+} // namespace dw
diff --git a/dw/regardingborder.hh b/dw/regardingborder.hh
new file mode 100644
index 00000000..4c8951ad
--- /dev/null
+++ b/dw/regardingborder.hh
@@ -0,0 +1,27 @@
+#ifndef __DW_REGARDINGBORDER_HH__
+#define __DW_REGARDINGBORDER_HH__
+
+#include "core.hh"
+
+namespace dw {
+
+/**
+ * \brief Base class (rather a tag interface) for those widgets
+ * regarding borders defined by floats, and so allocated on the
+ * full width.
+ *
+ * Will, when integrated to the "dillo_grows" repository, become a sub
+ * class of OOFAwareWidget.
+ */
+class RegardingBorder: public core::Widget
+{
+public:
+ static int CLASS_ID;
+
+ RegardingBorder ();
+ ~RegardingBorder ();
+};
+
+} // namespace dw
+
+#endif // __DW_REGARDINGBORDER_HH__
diff --git a/dw/ruler.cc b/dw/ruler.cc
index 115dfaa5..ccf58baa 100644
--- a/dw/ruler.cc
+++ b/dw/ruler.cc
@@ -26,19 +26,54 @@
namespace dw {
+int Ruler::CLASS_ID = -1;
+
Ruler::Ruler ()
{
- setFlags (BLOCK_LEVEL);
- unsetFlags (HAS_CONTENTS);
+ DBG_OBJ_CREATE ("dw::Ruler");
+ registerName ("dw::Ruler", &CLASS_ID);
+}
+
+Ruler::~Ruler ()
+{
+ DBG_OBJ_DELETE ();
}
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, false);
+ extremes->adjustmentWidth =
+ lout::misc::min (extremes->minWidthIntrinsic, extremes->minWidth);
+}
+
+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..cd4f63f4 100644
--- a/dw/ruler.hh
+++ b/dw/ruler.hh
@@ -1,7 +1,7 @@
#ifndef __RULER_HH__
#define __RULER_HH__
-#include "core.hh"
+#include "regardingborder.hh"
namespace dw {
@@ -10,17 +10,30 @@ namespace dw {
*
* This is really an empty widget, the HTML parser puts a border
* around it, and drawing is done in dw::core::Widget::drawWidgetBox.
- * The only remarkable point is that the HAS_CONTENT flag is
- * cleared.
+ *
+ * Ruler implements RegardingBorder; this way, it is simpler to fit
+ * the ruler exactly within the space between floats. Currently, the
+ * drawn area of the ruler is too large (but most of the superfluous
+ * part is hidden by the floats); this problem will not solved but in
+ * "dillo_grows", where RegardingBorder is a sub class of
+ * OOFAwareWidget.
*/
-class Ruler: public core::Widget
+class Ruler: public RegardingBorder
{
protected:
void sizeRequestImpl (core::Requisition *requisition);
+ void getExtremesImpl (core::Extremes *extremes);
+ void containerSizeChangedForChildren ();
+ bool usesAvailWidth ();
void draw (core::View *view, core::Rectangle *area);
public:
+ static int CLASS_ID;
+
Ruler ();
+ ~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..feec46c8
--- /dev/null
+++ b/dw/simpletablecell.cc
@@ -0,0 +1,129 @@
+/*
+ * 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 ();
+}
+
+bool SimpleTableCell::mustBeWidenedToAvailWidth ()
+{
+ return tablecell::mustBeWidenedToAvailWidth ();
+}
+
+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,
+ bool useAdjustmentWidth)
+{
+ 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, useAdjustmentWidth);
+ tablecell::correctCorrectedExtremesOfChild (this, child, extremes,
+ useAdjustmentWidth);
+
+ 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..60715d90
--- /dev/null
+++ b/dw/simpletablecell.hh
@@ -0,0 +1,38 @@
+#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 useAdjustmentWidth);
+
+ 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 ();
+
+ bool mustBeWidenedToAvailWidth ();
+};
+
+} // 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..d55fd72f 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,48 @@ 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 = extremes->adjustmentWidth =
+ 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 = extremes->adjustmentWidth =
+ (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;
+ extremes->adjustmentWidth += colExtremes->getRef(col)->adjustmentWidth;
+ }
}
- _MSG(" Table::getExtremesImpl, {%d, %d} numCols=%d\n",
- extremes->minWidth, extremes->maxWidth, numCols);
+ correctExtremes (extremes, true);
+
+ 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 +178,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 +202,8 @@ void Table::sizeAllocateImpl (core::Allocation *allocation)
x += colWidths->get (col) + getStyle()->hBorderSpacing;
}
+
+ DBG_OBJ_LEAVE ();
}
void Table::resizeDrawImpl ()
@@ -202,30 +214,143 @@ 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;
+
+ // We do not calculate the column widths at this point, because
+ // this tends to be rather inefficient for tables with many
+ // cells:
+ //
+ // For each of the n cells, some text is added (say, only one word
+ // per cell). Textblock::addText will eventually (via addText0
+ // etc.) call this method, Table::getAvailWidthOfChild. If
+ // calcCellSizes() is called here, this will call
+ // forceCalcCellSizes(), since the last call, sizes have to be
+ // re-calculated (because cells have been added). This will
+ // calculate the extremes for each existing cell, so
+ // Widget::getExtremes is called n * (n + 1) / 2 times. Even if the
+ // extremes are cached (so that getExtremesImpl does not have to be
+ // called in each case), this would make rendering tables with more
+ // than a few hundred cells unacceptably slow.
+ //
+ // Instead, column widths are calculated in Table::sizeRequestImpl.
+ //
+ // An alternative would be incremental resizing for tables; this
+ // approach resembles the behaviour before GROWS.
+
+ // TODO Does it still make sence to return -1 when forceValue is
+ // set?
+ if (forceValue)
+ 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);
+
+ // ActualChild->parentRef contains the position in the children
+ // array (see addCell()), so the column can be easily determined.
+ int col = actualChild->parentRef % numCols;
+ int colspanEff = children->get(actualChild->parentRef)->cell.colspanEff;
+ DBG_OBJ_MSGF ("resize", 1, "calculated from column %d, colspanEff = %d",
+ col, colspanEff);
+
+ int width = (colspanEff - 1) * getStyle()->hBorderSpacing;
+ for (int i = 0; i < 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);
}
+
+ DBG_OBJ_MSGF ("resize", 1, "=> %d", width);
+ DBG_OBJ_LEAVE ();
+ return width;
}
-void Table::setDescent (int descent)
+int Table::applyPerWidth (int containerWidth, core::style::Length perWidth)
{
- if (availDescent != descent) {
- availDescent = descent;
- queueResize (0, false);
+ return core::style::multiplyWithPerLength (containerWidth, perWidth);
+}
+
+int Table::applyPerHeight (int containerHeight, core::style::Length perHeight)
+{
+ 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 +397,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;
@@ -347,6 +475,12 @@ void Table::addCell (Widget *widget, int colspan, int rowspan)
child->cell.rowspan = rowspan;
children->set (curRow * numCols + curCol, child);
+ // The position in the children array is assigned to parentRef,
+ // although incremental resizing is not implemented. Useful, e. g.,
+ // in calcAvailWidthForDescendant(). See also reallocChildren().
+ widget->parentRef = curRow * numCols + curCol;
+ DBG_OBJ_SET_NUM_O (widget, "parentRef", widget->parentRef);
+
curCol += colspanEff;
widget->setParent (this);
@@ -373,6 +507,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 +529,7 @@ void Table::addRow (core::style::Style *style)
rowClosed = false;
}
-TableCell *Table::getCellRef ()
+AlignedTableCell *Table::getCellRef ()
{
core::Widget *child;
@@ -401,14 +537,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);
@@ -474,109 +715,441 @@ void Table::reallocChildren (int newNumCols, int newNumRows)
rowStyle->set (row, NULL);
// Rest is increased, when needed.
+ if (newNumCols > numCols) {
+ // Re-calculate parentRef. See addCell().
+ for (int row = 1; row < newNumRows; row++)
+ for (int col = 0; col < newNumCols; col++) {
+ int n = row * newNumCols + col;
+ Child *child = children->get (n);
+ if (child != NULL && child->type == Child::CELL) {
+ child->cell.widget->parentRef = n;
+ DBG_OBJ_SET_NUM_O (child->cell.widget, "parentRef",
+ child->cell.widget->parentRef);
+ }
+ }
+ }
+
numCols = newNumCols;
numRows = newNumRows;
+
+ // We initiate the column widths with a random value, to have a
+ // defined available width for the children before the column
+ // widths are actually calculated.
+
+ colWidths->setSize (numCols, 100);
+
+ 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));
+ }
+
+ 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 extremesChanget = extremesChanged () || extremesQueued ();
+
+ if (calcHeights ? (extremesChanget || sizeChanged) :
+ (extremesChanget || !colWidthsUpToDateWidthColExtremes))
+ forceCalcCellSizes (calcHeights);
+
+ DBG_OBJ_LEAVE ();
}
-void Table::forceCalcCellSizes ()
+void Table::forceCalcCellSizes (bool calcHeights)
{
- int totalWidth = 0, childHeight, forceTotalWidth = 1;
- core::Extremes extremes;
+ // Since Table::getAvailWidthOfChild does not calculate the column
+ // widths, and so initially a random value (100) is returned, a
+ // correction is necessary. The old values are temporary preserved
+ // ...
+
+ lout::misc::SimpleVector<int> oldColWidths (8);
+ oldColWidths.setSize (colWidths->size ());
+ colWidths->copyTo (&oldColWidths);
+
+ actuallyCalcCellSizes (calcHeights);
+
+ // ... and then compared to the new ones. In case of a difference,
+ // the cell is told about this.
+
+ for (int col = 0; col < colWidths->size (); col++) {
+ if (oldColWidths.get (col) != colWidths->get (col)) {
+ for (int row = 0; row < numRows; row++) {
+ int n = row * numCols + col, col2;
+ Child *child = children->get(n);
+ if (child) {
+ Widget *cell;
+ switch (child->type) {
+ case Child::CELL:
+ cell = child->cell.widget;
+ break;
- // Will also call calcColumnExtremes(), when needed.
- getExtremes (&extremes);
+ case Child::SPAN_SPACE:
+ // TODO Are Child::spanSpace::startRow and
+ // Child::spanSpace::startCol not defined?
+
+ // Search for actual cell. If not found, this means
+ // that a cell is spanning multiple columns *and*
+ // rows; in this case it has been processed before.
+
+ cell = NULL;
+ for (col2 = col - 1; col2 >= 0 && cell == NULL; col2--) {
+ int n2 = row * numCols + col2;
+ Child *child2 = children->get(n2);
+ if (child2 != NULL && child2->type == Child::CELL)
+ cell = child2->cell.widget;
+ }
+ break;
- 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;
+ default:
+ misc::assertNotReached ();
+ cell = NULL;
+ }
+
+ if (cell)
+ cell->containerSizeChanged ();
+ }
+ }
+ }
}
+}
- _MSG(" availWidth = %d\n", availWidth);
- _MSG(" totalWidth1 = %d\n", totalWidth);
-
- if (totalWidth < extremes.minWidth)
- totalWidth = extremes.minWidth;
- totalWidth = totalWidth
- - (numCols + 1) * getStyle()->hBorderSpacing
- - getStyle()->boxDiffWidth ();
+void Table::actuallyCalcCellSizes (bool calcHeights)
+{
+ DBG_OBJ_ENTER0 ("resize", 0, "forceCalcCellSizes");
- _MSG(" totalWidth2 = %d curCol=%d\n", totalWidth,curCol);
+ int childHeight;
+ core::Extremes extremes;
+ // Will also call forceCalcColumnExtremes(), when needed.
+ getExtremes (&extremes);
- colWidths->setSize (numCols, 0);
+ int availWidth = getAvailWidth (true);
+ // When adjust_table_min_width is set, use perhaps the adjustment
+ // width for correction. (TODO: Is this necessary?)
+ int corrWidth =
+ Table::getAdjustTableMinWidth () ? extremes.adjustmentWidth : 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);
+
+ assert (colWidths->size () == numCols); // This is set in addCell.
cumHeight->setSize (numRows + 1, 0);
rowSpanCells->setSize (0);
baseline->setSize (numRows);
- _MSG(" extremes = %d,%d\n", extremes.minWidth, extremes.maxWidth);
- _MSG(" getStyle()->boxDiffWidth() = %d\n", getStyle()->boxDiffWidth());
- _MSG(" getStyle()->hBorderSpacing = %d\n", getStyle()->hBorderSpacing);
+ misc::SimpleVector<int> *oldColWidths = colWidths;
+ colWidths = new misc::SimpleVector <int> (8);
+ colWidths->setSize (numCols);
+ 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;
+ }
- apportion_percentages2 (totalWidth, forceTotalWidth);
- if (!hasColPercent)
- apportion2 (totalWidth, forceTotalWidth);
+ // 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;
+ }
- setCumHeight (0, 0);
- for (int row = 0; row < numRows; row++) {
- /**
- * \bug dw::Table::baseline is not filled.
- */
- int rowHeight = 0;
+ 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");
+
+ // 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).
+
+ 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;
+
+ 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;
+
+ minWidthIntrinsicPer -= minIntr;
+
+ if (colWidth > widthPartPer - minWidthIntrinsicPer)
+ colWidth = widthPartPer - minWidthIntrinsicPer;
+
+ colWidths->set (col, colWidth);
+ widthPartPer -= colWidth;
+
+ DBG_OBJ_MSGF ("resize", 1,
+ "#%d: colWidth = %d ... widthPartPer = %d, "
+ "minWidthIntrinsicPer = %d",
+ col, colWidth, widthPartPer, minWidthIntrinsicPer);
+
+ } else
+ colWidths->set (col,
+ colExtremes->getRef(col)->minWidthIntrinsic);
- 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);
+ }
+ } 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++;
+ }
- 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_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 ();
+
+ 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;
+
+ 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
- setCumHeight (row + 1,
- cumHeight->get (row) + rowHeight + getStyle()->vBorderSpacing);
+ setCumHeight (row + 1,
+ cumHeight->get (row) + rowHeight + getStyle()->vBorderSpacing);
+ } // for row
- }/*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,18 +1203,8 @@ void Table::apportionRowSpan ()
setCumHeight (i+1, cumHeight->get(i) + rowHeight[i]);
}
delete[] rowHeight;
-}
-
-/**
- * \brief Fills dw::Table::colExtremes, only if recalculation is necessary.
- *
- * \bug Some parts are missing.
- */
-void Table::calcColumnExtremes ()
-{
- if (extremesChanged ())
- forceCalcColumnExtremes ();
+ DBG_OBJ_LEAVE ();
}
@@ -650,570 +1213,358 @@ 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;
+ colExtremes->getRef(col)->adjustmentWidth = 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);
+
+ colExtremes->getRef(col)->adjustmentWidth =
+ misc::max (colExtremes->getRef(col)->adjustmentWidth,
+ cellExtremes.adjustmentWidth);
+
+ 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;
+ DBG_OBJ_MSG_END ();
}
- _MSG("cs=%d spanWidth=%d,%d sumCols=%d,%d\n",
- cs,cellMinW,cellMaxW,minSumCols,maxSumCols);
+ // 2. cells with colspan > 1
- if (minSumCols >= cellMinW && maxSumCols >= cellMaxW)
- continue;
+ // TODO: Is this old comment still relevant? "If needed, here we
+ // set proportionally apportioned col maximums."
- // 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;
- }
- }
+ for (int i = 0; i < colSpanCells.size(); i++) {
+ int n = colSpanCells.get (i);
+ int col = n % numCols;
+ int cs = children->get(n)->cell.colspanEff;
- // 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;
- }
-
- // 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;
- }
- }
-
- 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;
+ core::Extremes cellExtremes;
+ children->get(n)->cell.widget->getExtremes (&cellExtremes);
- _MSG("i=%d, wMin=%d wMax=%d cumMaxWold=%d\n",
- i,wMin,wMax,cumMaxWold);
+ calcExtremesSpanMultiCols (col, cs, &cellExtremes, MIN, MAX, NULL);
+ calcExtremesSpanMultiCols (col, cs, &cellExtremes, MIN_INTR, MAX_INTR,
+ NULL);
+ calcAdjustmentWidthSpanMultiCols (col, cs, &cellExtremes);
+ 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);
+ }
}
-#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
}
-}
-/**
- * \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);
- }
- if (colPercents->get(col) == core::style::LENGTH_AUTO) {
- maxAutoWidth += colExtremes->get(col).maxWidth;
- minAutoWidth += colExtremes->get(col).minWidth;
- } else
- availAutoWidth -= colWidths->get(col);
+ numColWidthSpecified = 0;
+ numColWidthSpecified = 0;
+ for (int i = 0; i < colExtremes->size (); i++) {
+ if (colWidthSpecified->get (i))
+ numColWidthSpecified++;
+ if (colWidthPercentage->get (i))
+ numColWidthPercentage++;
}
- if (!maxAutoWidth) // no core::style::LENGTH_AUTO cols!
- return;
-
- colWidths->setSize (colExtremes->size (), 0);
+ 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);
+ }
- if (!forceTotalWidth && maxAutoWidth < availAutoWidth) {
- // Enough space for the maximum table, don't widen past max.
- availAutoWidth = maxAutoWidth;
+ 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);
}
- // 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);
+ colWidthsUpToDateWidthColExtremes = false;
+ DBG_OBJ_SET_BOOL ("colWidthsUpToDateWidthColExtremes",
+ colWidthsUpToDateWidthColExtremes);
- _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
+ DBG_OBJ_LEAVE ();
}
-void Table::apportion_percentages2(int totalWidth, int forceTotalWidth)
+void Table::calcExtremesSpanMultiCols (int col, int cs,
+ core::Extremes *cellExtremes,
+ ExtrMod minExtrMod, ExtrMod maxExtrMod,
+ void *extrData)
{
- int hasTablePercent = core::style::isPerLength (getStyle()->width) ? 1 : 0;
+ DBG_OBJ_ENTER ("resize", 0, "calcExtremesSpanMulteCols",
+ "%d, %d, ..., %s, %s, ...",
+ col, cs, getExtrModName (minExtrMod),
+ getExtrModName (maxExtrMod));
- if (colExtremes->size() == 0 || (!hasTablePercent && !hasColPercent))
- return;
+ int cellMin = getExtreme (cellExtremes, minExtrMod);
+ int cellMax = getExtreme (cellExtremes, maxExtrMod);
- // If there's a table-wide percentage, totalWidth comes already scaled.
- _MSG("APP_P, availWidth=%d, totalWidth=%d, forceTotalWidth=%d\n",
- availWidth, totalWidth, forceTotalWidth);
+ int minSumCols = 0, maxSumCols = 0;
- 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);
- }
- }
-
- // 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 j = 0; j < cs; j++) {
+ minSumCols += getColExtreme (col + j, minExtrMod, extrData);
+ maxSumCols += getColExtreme (col + j, maxExtrMod, extrData);
+ }
- 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);
- }
+ 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)));
}
-
- 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
}
-}
-
-// ----------------------------------------------------------------------
-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::calcAdjustmentWidthSpanMultiCols (int col, int cs,
+ core::Extremes *cellExtremes)
{
- 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;
- content.widget = table->children->get(index)->cell.widget;
+ DBG_OBJ_ENTER ("resize", 0, "calcAdjustmentWidthSpanMultiCols",
+ "%d, %d, ...", col, cs);
+
+ int sumAdjustmentWidth = 0;
+ for (int j = 0; j < cs; j++)
+ sumAdjustmentWidth += colExtremes->getRef(col + j)->adjustmentWidth;
+
+ if (cellExtremes->adjustmentWidth > sumAdjustmentWidth) {
+ misc::SimpleVector<int> newAdjustmentWidth;
+ apportion2 (cellExtremes->adjustmentWidth, col, col + cs - 1, MIN, MAX,
+ NULL, &newAdjustmentWidth, 0);
+ for (int j = 0; j < cs; j++)
+ colExtremes->getRef(col + j)->adjustmentWidth =
+ newAdjustmentWidth.get (j);
}
-}
-object::Object *Table::TableIterator::clone()
-{
- return new TableIterator ((Table*)getWidget(), getMask(), index);
+ DBG_OBJ_LEAVE ();
}
-int Table::TableIterator::compareTo(object::Comparable *other)
-{
- return index - ((TableIterator*)other)->index;
-}
-
-bool Table::TableIterator::next ()
+/**
+ * \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::END)
- return false;
-
- // tables only contain widgets:
- if ((getMask() & core::Content::WIDGET) == 0) {
- content.type = core::Content::END;
- return false;
- }
+ if (lastCol >= firstCol) {
+ dest->setSize (destOffset + lastCol - firstCol + 1, 0);
- do {
- index++;
- if (index >= table->children->size ()) {
- content.type = core::Content::END;
- 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;
-}
-
-bool Table::TableIterator::prev ()
-{
- Table *table = (Table*)getWidget();
-
- if (content.type == core::Content::START)
- return false;
- // 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;
+ 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);
+ }
}
- } 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)
-{
-}
+ }
-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..9b868642 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.
@@ -38,29 +43,26 @@ namespace dw {
* sizeRequestImpl [color="#0000ff", URL="\ref dw::Table::sizeRequestImpl"];
* sizeAllocateImpl [color="#0000ff",
* URL="\ref dw::Table::sizeAllocateImpl"];
+ * getExtremes [color="#0000ff", URL="\ref dw::core::Widget::getExtremes"];
* getExtremesImpl [color="#0000ff", URL="\ref dw::Table::getExtremesImpl"];
*
- * subgraph cluster_sizes {
- * style="dashed"; color="#8080c0";
- * calcCellSizes [URL="\ref dw::Table::calcCellSizes"];
- * forceCalcCellSizes [URL="\ref dw::Table::forceCalcCellSizes"];
- * }
- *
- * subgraph cluster_extremes {
- * style="dashed"; color="#8080c0";
- * calcColumnExtremes [URL="\ref dw::Table::calcColumnExtremes"];
- * forceCalcColumnExtremes[URL="\ref dw::Table::forceCalcColumnExtremes"];
- * }
+ * calcCellSizes [label="calcCellSizes (calcHeights = true)",
+ * URL="\ref dw::Table::calcCellSizes"];
+ * forceCalcCellSizes [label="forceCalcCellSizes (calcHeights = true)",
+ * URL="\ref dw::Table::forceCalcCellSizes"];
+ * actuallyCalcCellSizes[label="actuallyCalcCellSizes (calcHeights = true)",
+ * URL="\ref dw::Table::actuallyCalcCellSizes"];
+ * forceCalcColumnExtremes[URL="\ref dw::Table::forceCalcColumnExtremes"];
*
* sizeRequestImpl -> forceCalcCellSizes [label="[B]"];
* sizeAllocateImpl -> calcCellSizes [label="[A]"];
* getExtremesImpl -> forceCalcColumnExtremes [label="[B]"];
*
- * forceCalcCellSizes -> calcColumnExtremes;
+ * forceCalcCellSizes -> actuallyCalcCellSizes;
+ * actuallyCalcCellSizes-> getExtremes;
+ * getExtremes -> getExtremesImpl [style="dashed", label="[C]"];
*
* calcCellSizes -> forceCalcCellSizes [style="dashed", label="[C]"];
- * calcColumnExtremes -> forceCalcColumnExtremes [style="dashed",
- * label="[C]"];
* }
* \enddot
*
@@ -71,8 +73,14 @@ namespace dw {
* is the case.
*
* [C] Whether this function is called, depends on NEEDS_RESIZE /
- * EXTREMES_CHANGED.
+ * RESIZE_QUEUED / EXTREMES_CHANGED / EXTREMES_QUEUED.
*
+ * **TODO:**
+ *
+ * - Are <tt>*[cC]alcCellSizes (calcHeights = *false*)</tt> not
+ * necessary anymore?
+ * - Calculating available sizes (Table::getAvailWidthOfChild) should
+ * be documented in this diagram, too.
*
* <h4>Apportionment</h4>
*
@@ -191,8 +199,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 +325,6 @@ namespace dw {
class Table: public core::Widget
{
private:
-
struct Child
{
enum {
@@ -355,8 +363,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 +378,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 +414,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 +434,26 @@ 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 actuallyCalcCellSizes (bool calcHeights);
void apportionRowSpan ();
- void calcColumnExtremes ();
void forceCalcColumnExtremes ();
+ void calcExtremesSpanMultiCols (int col, int cs,
+ core::Extremes *cellExtremes,
+ ExtrMod minExtrMod, ExtrMod maxExtrMod,
+ void *extrData);
+ void calcAdjustmentWidthSpanMultiCols (int col, int cs,
+ core::Extremes *cellExtremes);
- 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 +463,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 +490,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..3e143c96 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,105 @@
* 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,
+ bool useAdjustmentWidth)
{
- 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..257f87c7 100644
--- a/dw/tablecell.hh
+++ b/dw/tablecell.hh
@@ -2,27 +2,36 @@
#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);
+inline bool mustBeWidenedToAvailWidth () { return true; }
- int getValue ();
- void setMaxValue (int maxValue, int value);
+bool getAdjustMinWidth ();
+bool isBlockLevel ();
-public:
- static int CLASS_ID;
+int correctAvailWidthOfChild (core::Widget *widget, core::Widget *child,
+ int width, bool forceValue);
+int correctAvailHeightOfChild (core::Widget *widget, core::Widget *child,
+ int height, bool forceValue);
- TableCell(TableCell *ref, bool limitTextWidth);
- ~TableCell();
-};
+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,
+ bool useAdjustmentWidth);
+
+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..21de1991 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,162 @@ 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 = extremes->adjustmentWidth =
+ 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;
+ extremes->adjustmentWidth = 0;
} else {
Paragraph *lastPar = paragraphs->getLastRef ();
extremes->minWidth = lastPar->maxParMin;
+ extremes->minWidthIntrinsic = lastPar->maxParMinIntrinsic;
extremes->maxWidth = lastPar->maxParMax;
+ extremes->maxWidthIntrinsic = lastPar->maxParMaxIntrinsic;
+ extremes->adjustmentWidth = lastPar->maxParAdjustmentWidth;
+
+ 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;
+ extremes->adjustmentWidth += 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, true);
- 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);
+ extremes->adjustmentWidth =
+ misc::max (extremes->adjustmentWidth, 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;
@@ -443,13 +624,32 @@ void Textblock::sizeAllocateImpl (core::Allocation *allocation)
core::Allocation childAllocation;
core::Allocation *oldChildAllocation;
- if (allocation->width != this->allocation.width) {
+ if (allocation->x != this->allocation.x ||
+ allocation->y != this->allocation.y ||
+ 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 +657,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 +679,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 +701,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 +728,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 +863,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 (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);
+ }
- // 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);
+ 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 +1094,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 +1104,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 +1130,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 +1213,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 +1231,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 +1289,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 +1304,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 +1315,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 +1344,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 +1375,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 +1540,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 +1560,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 +1594,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 +1604,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 +1651,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 +1669,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 +1681,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 +1738,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 +1774,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 +1813,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 +1843,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 +1963,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 +1971,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 +2058,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 +2119,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 +2207,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 +2230,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 +2248,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 +2265,8 @@ void Textblock::addText (const char *text, size_t len,
}
}
}
+
+ DBG_OBJ_LEAVE ();
}
void Textblock::calcTextSizes (const char *text, size_t textLen,
@@ -1923,6 +2309,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 +2335,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 +2353,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");
- word->content.type = core::Content::WIDGET;
- word->content.widget = widget;
+ 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;
- //DBG_OBJ_ARRSET_PTR (page, "words.%d.content.widget", words->size() - 1,
- // word->content.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");
+
+ 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 +2413,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 +2426,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 +2448,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 +2456,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 +2470,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 +2537,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 +2568,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 +2594,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 +2608,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 +2627,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 +2652,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 +2661,7 @@ void Textblock::addParbreak (int space, core::style::Style *style)
}
/* Otherwise continue to examine parents. */
}
+
/* Return in any case. */
return;
}
@@ -2195,7 +2675,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 +2685,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 +2697,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 +2716,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 +2760,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 +2774,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 +2793,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 +2820,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 +2836,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 +2880,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 +2898,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 +2923,311 @@ 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 ();
+}
+
+bool Textblock::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;
+}
+
+/**
+ * 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 ();
+}
+
+RegardingBorder *Textblock::getWidgetRegardingBorderForLine (Line *line)
+{
+ return getWidgetRegardingBorderForLine (line->firstWord, line->lastWord);
+}
+
+RegardingBorder *Textblock::getWidgetRegardingBorderForLine (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 getWidgetRegardingBorderForLine (firstWord, lastWord);
+}
+
+RegardingBorder *Textblock::getWidgetRegardingBorderForLine (int firstWord,
+ int lastWord)
+{
+ DBG_OBJ_ENTER ("resize", 0, "getWidgetRegardingBorderForLine", "%d, %d",
+ firstWord, lastWord);
+ DBG_OBJ_MSGF ("resize", 1, "words.size = %d", words->size ());
+
+ RegardingBorder *widgetRegardingBorder = NULL;
+
+ if (firstWord < words->size ()) {
+ // Any instance of a subclass of WidgetRegardingBorder 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 (RegardingBorder::CLASS_ID) &&
+ // Exclude some cases where a widget 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)
+ widgetRegardingBorder = (RegardingBorder*)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", widgetRegardingBorder);
+ DBG_OBJ_LEAVE ();
+ return widgetRegardingBorder;
+}
+
+/**
+ * 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..162088dc 100644
--- a/dw/textblock.hh
+++ b/dw/textblock.hh
@@ -3,14 +3,19 @@
#include <limits.h>
-#include "core.hh"
+#include "regardingborder.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.&nbsp;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.
*
@@ -146,7 +196,7 @@ namespace dw {
* necessary, or otherwise the line from which a rewrap is necessary.
*
*/
-class Textblock: public core::Widget
+class Textblock: public RegardingBorder
{
private:
/**
@@ -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,20 @@ 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 parAdjustmentWidth;
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 maxParAdjustmentWidth;
int maxParMax; /* Maximum of all paragraph maxima (value of
- * "parMax"), including this paragraph. */
+ "parMax""), including this paragraph. */
+ int maxParMaxIntrinsic;
};
struct Line
@@ -308,14 +371,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 +412,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 +510,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 +527,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 +555,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 +582,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 +618,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 +656,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 +677,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 getWidgetRegardingBorderForLine (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 getWidgetRegardingBorderForLine (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 +725,7 @@ protected:
*/
inline int lineYOffsetCanvas (Line *line)
{
- return lineYOffsetCanvasAllocation(line, &allocation);
+ return _lineYOffsetCanvasAllocation (line, &childBaseAllocation);
}
inline int lineYOffsetWidgetI (int lineIndex)
@@ -597,57 +733,99 @@ 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;
+ }
}
+ RegardingBorder *getWidgetRegardingBorderForLine (Line *line);
+ RegardingBorder *getWidgetRegardingBorderForLine (int lineNo);
+ RegardingBorder *getWidgetRegardingBorderForLine (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 +842,7 @@ protected:
core::style::Style *style,
int numBreaks, int *breakPos,
core::Requisition *wordSize);
+ static bool isContainingBlock (Widget *widget);
public:
static int CLASS_ID;
@@ -684,9 +863,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 +878,86 @@ public:
void changeLinkColor (int link, int newColor);
void changeWordStyle (int from, int to, core::style::Style *style,
bool includeFirstSpace, bool includeLastSpace);
+
+ virtual bool mustBeWidenedToAvailWidth ();
+
+ 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..3c181388 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,165 @@ 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);
}
-
+
+ // Until here, lineWidth refers does not include floats on the left
+ // side. To include left floats, so that maxLineWidth, and
+ // eventually the requisition, is correct, line->leftOffset (minus
+ // margin+border+padding) has to be added, which was calculated
+ // just before. The correction in sizeAllocateImpl() is irrelevant
+ // in this regard. Also, right floats are not regarded here, but in
+ // OutOfFlowMgr::getSize(),
+ lineWidth += (line->leftOffset - getStyle()->boxOffsetX ());
+
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 +502,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 +511,68 @@ 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, "");
+
+ int diffWords = wordWrap (wordIndex, false);
- if (wordListChanged) {
+ 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_SET_NUM ("paragraphs.size", paragraphs->size ());
+ 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 +583,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;
+ }
+ }
+
+ DBG_OBJ_MSG_END ();
+ DBG_OBJ_MSGF ("construct.word", 1, "=> %s",
+ possibleLineBreak ? "true" : "false");
- if (possibleLineBreak && word->badnessAndPenalty.lineTooTight ()) {
+ 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 +724,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 +849,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 +1109,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 +1153,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 +1178,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 +1204,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 ||
@@ -789,23 +1231,36 @@ void Textblock::handleWordExtremes (int wordIndex)
->badnessAndPenalty.lineMustBeBroken (1)) {
// Add a new paragraph.
paragraphs->increase ();
+ DBG_OBJ_SET_NUM ("paragraphs.size", paragraphs->size ());
+
Paragraph *prevPar = paragraphs->size() == 1 ?
NULL : paragraphs->getRef(paragraphs->size() - 2);
Paragraph *par = paragraphs->getLastRef();
par->firstWord = par->lastWord = wordIndex;
- par->parMin = par->parMax = 0;
+ par->parMin = par->parMinIntrinsic = par->parMax = par->parMaxIntrinsic =
+ par->parAdjustmentWidth = 0;
if (prevPar) {
par->maxParMin = prevPar->maxParMin;
+ par->maxParMinIntrinsic = prevPar->maxParMinIntrinsic;
par->maxParMax = prevPar->maxParMax;
+ par->maxParMaxIntrinsic = prevPar->maxParMaxIntrinsic;
+ par->maxParAdjustmentWidth = prevPar->maxParAdjustmentWidth;
} else
- par->maxParMin = par->maxParMax = 0;
-
- PRINTF (" new par: %d\n", paragraphs->size() - 1);
+ par->maxParMin = par->maxParMinIntrinsic = par->maxParMax =
+ par->maxParMaxIntrinsic = par->maxParAdjustmentWidth = 0;
+
+ DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1, "maxParMin",
+ par->maxParMin);
+ DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1,
+ "maxParMinIntrinsic", par->maxParMinIntrinsic);
+ DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1, "maxParMax",
+ par->maxParMax);
+ DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1,
+ "maxParMaxIntrinsic", par->maxParMaxIntrinsic);
}
- PRINTF (" last par: %d\n", paragraphs->size() - 1);
Paragraph *lastPar = paragraphs->getLastRef();
int corrDiffMin, corrDiffMax;
@@ -816,31 +1271,71 @@ 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);
-
// 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->parAdjustmentWidth +=
+ wordExtremes.adjustmentWidth + word->hyphenWidth + corrDiffMin;
lastPar->maxParMin = misc::max (lastPar->maxParMin, lastPar->parMin);
+ lastPar->maxParMinIntrinsic =
+ misc::max (lastPar->maxParMinIntrinsic, lastPar->parMinIntrinsic);
+ lastPar->maxParAdjustmentWidth =
+ misc::max (lastPar->maxParAdjustmentWidth, lastPar->parAdjustmentWidth);
+
+ DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1, "parMin",
+ lastPar->parMin);
+ DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1,
+ "parMinIntrinsic", lastPar->parMinIntrinsic);
+ DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1, "maxParMin",
+ lastPar->maxParMin);
+ DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1,
+ "maxParMinIntrinsic", lastPar->maxParMinIntrinsic);
+ DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1,
+ "parAdjustmentWidth", lastPar->parAdjustmentWidth);
+ DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1,
+ "maxParAdjustmentWidth",
+ lastPar->maxParAdjustmentWidth);
+
if (word->badnessAndPenalty.lineCanBeBroken (1) &&
- (word->flags & Word::UNBREAKABLE_FOR_MIN_WIDTH) == 0)
- lastPar->parMin = 0;
+ (word->flags & Word::UNBREAKABLE_FOR_MIN_WIDTH) == 0) {
+ lastPar->parMin = lastPar->parMinIntrinsic = lastPar->parAdjustmentWidth
+ = 0;
+
+ DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1, "parMin",
+ lastPar->parMin);
+ DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1,
+ "parMinIntrinsic", lastPar->parMinIntrinsic);
+ DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1,
+ "parAdjustmentWidth",
+ lastPar->parAdjustmentWidth);
+ }
// 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);
-
- PRINTF (" => parMin = %d (max = %d), parMax = %d (max = %d)\n",
- lastPar->parMin, lastPar->maxParMin, lastPar->parMax,
- lastPar->maxParMax);
-
+ lastPar->maxParMaxIntrinsic =
+ misc::max (lastPar->maxParMaxIntrinsic, lastPar->parMaxIntrinsic);
+
+ DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1, "parMax",
+ lastPar->parMax);
+ DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1,
+ "parMaxIntrinsic", lastPar->parMaxIntrinsic);
+ DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1, "maxParMax",
+ lastPar->maxParMax);
+ DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1,
+ "maxParMaxIntrinsic", lastPar->maxParMaxIntrinsic);
+
lastPar->lastWord = wordIndex;
+ DBG_OBJ_LEAVE ();
}
/**
@@ -852,7 +1347,9 @@ void Textblock::correctLastWordExtremes ()
Word *word = words->getLastRef ();
if (word->badnessAndPenalty.lineCanBeBroken (1) &&
(word->flags & Word::UNBREAKABLE_FOR_MIN_WIDTH) == 0) {
- paragraphs->getLastRef()->parMin = 0;
+ Paragraph *lastPar = paragraphs->getLastRef();
+ lastPar->parMin = lastPar->parMinIntrinsic =
+ lastPar->parAdjustmentWidth = 0;
PRINTF (" => corrected; parMin = %d\n",
paragraphs->getLastRef()->parMin);
}
@@ -860,10 +1357,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 +1376,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 +1412,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 +1434,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 +1464,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 +1588,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 +1605,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 +1702,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 +1730,123 @@ 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;
+ for (int i = line->firstWord; i <= line->lastWord; i++)
+ words->getRef(i)->effSpace = words->getRef(i)->origSpace;
+
+ // We are not interested in the alignment of floats etc.
+ int firstWordNotOofRef = line->firstWord;
+ while (firstWordNotOofRef <= line->lastWord &&
+ words->getRef(firstWordNotOofRef)->content.type
+ == core::Content::WIDGET_OOF_REF)
+ firstWordNotOofRef++;
+
+ if (firstWordNotOofRef <= line->lastWord) {
+ Word *firstWord = words->getRef (firstWordNotOofRef);
+
+ if (firstWord->content.type != core::Content::BREAK) {
+ Word *lastWord = words->getRef (line->lastWord);
+ int lineBreakWidth =
+ this->lineBreakWidth - (line->leftOffset + line->rightOffset);
+
+ 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 (or only OOF references).
+ 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;
+
+ switch (line->alignment) {
+ case Line::LEFT:
+ line->textOffset = line->leftOffset;
+ DBG_OBJ_MSGF ("construct.line", 1, "left: textOffset = %d",
+ line->textOffset);
+ break;
+
+ case Line::RIGHT:
+ line->textOffset = totalWidth - line->rightOffset - lineWidth;
+ DBG_OBJ_MSGF ("construct.line", 1,
+ "right: textOffset = %d - %d - %d = %d",
+ totalWidth, line->rightOffset, lineWidth, line->textOffset);
+ break;
+
+ case Line::CENTER:
+ line->textOffset =
+ (line->leftOffset + totalWidth - line->rightOffset - lineWidth) / 2;
+ DBG_OBJ_MSGF ("construct.line", 1,
+ "center: textOffset = (%d + %d - %d - %d) /2 = %d",
+ line->leftOffset, totalWidth, line->rightOffset, lineWidth,
+ line->textOffset);
+ 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 +1857,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 +1913,256 @@ 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);
+ DBG_OBJ_SET_NUM ("paragraphs.size", paragraphs->size ());
- 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 +2183,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..b6b4ca0b 100644
--- a/dw/types.hh
+++ b/dw/types.hh
@@ -180,6 +180,9 @@ struct Extremes
{
int minWidth;
int maxWidth;
+ int minWidthIntrinsic;
+ int maxWidthIntrinsic;
+ int adjustmentWidth;
};
struct Content
@@ -188,11 +191,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 +224,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
diff --git a/dw/ui.cc b/dw/ui.cc
index 22199980..1ea10ebc 100644
--- a/dw/ui.cc
+++ b/dw/ui.cc
@@ -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,9 @@ void Embed::sizeRequestImpl (Requisition *requisition)
void Embed::getExtremesImpl (Extremes *extremes)
{
resource->getExtremes (extremes);
+ correctExtremes (extremes, false);
+ extremes->adjustmentWidth =
+ misc::max (extremes->minWidthIntrinsic, extremes->minWidth);
}
void Embed::sizeAllocateImpl (Allocation *allocation)
@@ -62,6 +66,36 @@ 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,
+ bool useAdjustmentWidth)
+{
+ resource->correctExtremesOfChild (child, extremes, useAdjustmentWidth);
+}
+
+void Embed::containerSizeChangedForChildren ()
+{
+ DBG_OBJ_ENTER0 ("resize", 0, "containerSizeChangedForChildren");
+ resource->containerSizeChangedForChildren ();
+ DBG_OBJ_LEAVE ();
+}
+
void Embed::enterNotifyImpl (core::EventCrossing *event)
{
resource->emitEnter();
@@ -87,21 +121,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 +199,7 @@ void Resource::ActivateEmitter::emitLeave (Resource *resource)
Resource::~Resource ()
{
+ DBG_OBJ_DELETE ();
}
void Resource::setEmbed (Embed *embed)
@@ -189,26 +209,57 @@ 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,
+ bool useAdjustmentWidth)
{
+ // 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 +315,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 +333,138 @@ 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,
+ bool useAdjustmentWidth)
{
- 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,
+ useAdjustmentWidth);
}
-void ComplexButtonResource::setDescent (int descent)
+void ComplexButtonResource::containerSizeChangedForChildren ()
{
- childWidget->setDescent (descent - reliefYThickness ());
+ layout->containerSizeChanged ();
}
Iterator *ComplexButtonResource::iterator (Content::Type mask, bool atEnd)
diff --git a/dw/ui.hh b/dw/ui.hh
index e92c48d1..c64880b4 100644
--- a/dw/ui.hh
+++ b/dw/ui.hh
@@ -231,6 +231,17 @@ 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,
+ bool useAdjustmentWidth);
+
+ void containerSizeChangedForChildren ();
+
void enterNotifyImpl (core::EventCrossing *event);
void leaveNotifyImpl (core::EventCrossing *event);
bool buttonPressImpl (core::EventButton *event);
@@ -241,18 +252,27 @@ 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,
+ bool useAdjustmentWidth)
+ { Widget::correctExtremesOfChild (child, extremes, useAdjustmentWidth); }
};
/**
@@ -329,16 +349,25 @@ 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,
+ bool useAdjustmentWidth);
+ 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 +406,7 @@ private:
public:
ComplexButtonResource *resource;
- void canvasSizeChanged (int width, int ascent, int descent);
+ void resizeQueued (bool extremesChanged);
};
friend class LayoutReceiver;
@@ -406,9 +435,16 @@ 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,
+ bool useAdjustmentWidth);
+ 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 7e9591ff..464459ba 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 = true;
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,62 +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);
+
+ 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);
+
+ resizeFlag = RESIZE_QUEUED;
+ extremesFlag = EXTREMES_QUEUED;
+ } else {
+ resizeFlag = NEEDS_RESIZE;
+ extremesFlag = EXTREMES_CHANGED;
+ }
- setFlags (NEEDS_RESIZE);
- setFlags (NEEDS_ALLOCATE);
+ 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
@@ -213,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);
@@ -224,6 +500,392 @@ 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. A value of "false" is passed for "useCorrected" in the
+ * context of correctExtemes etc., to avoid cyclic dependencies.
+ *
+ */
+int Widget::getMinWidth (Extremes *extremes, bool forceValue)
+{
+ DBG_IF_RTFL {
+ if (extremes)
+ DBG_OBJ_ENTER ("resize", 0, "getMinWidth", "[%d (%d) / %d (%d), %s",
+ extremes->minWidth, extremes->minWidthIntrinsic,
+ extremes->maxWidth, extremes->maxWidthIntrinsic,
+ forceValue ? "true" : "false");
+ else
+ DBG_OBJ_ENTER ("resize", 0, "getMinWidth", "(nil), %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.
+ if (extremes)
+ minWidth = extremes->adjustmentWidth;
+ else
+ minWidth = 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, bool useAdjustmentWidth)
+{
+ 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 =
+ useAdjustmentWidth ? getMinWidth (extremes, false) : 0;
+ 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, useAdjustmentWidth);
+ DBG_OBJ_MSG_END ();
+ } else /* if (quasiParent) */ {
+ DBG_OBJ_MSG ("resize", 1, "delegated to quasiParent");
+ DBG_OBJ_MSG_START ();
+ quasiParent->correctExtremesOfChild (this, extremes, useAdjustmentWidth);
+ 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;
}
/**
@@ -231,15 +893,49 @@ 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);
+ DBG_OBJ_SET_NUM ("extremes.adjustmentWidth", extremes->adjustmentWidth);
} else
*extremes = this->extremes;
+
+ leaveGetExtremes ();
+
+ DBG_OBJ_LEAVE ();
}
/**
@@ -248,6 +944,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 ||
@@ -288,6 +1011,10 @@ void Widget::sizeAllocate (Allocation *allocation)
}
/*unsetFlags (NEEDS_RESIZE);*/
+
+ leaveSizeAllocate ();
+
+ DBG_OBJ_LEAVE ();
}
bool Widget::buttonPress (EventButton *event)
@@ -359,6 +1086,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");
}
/**
@@ -414,6 +1194,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
@@ -424,7 +1208,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);
}
/**
@@ -446,10 +1231,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);
}
/*
@@ -510,6 +1311,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.
*/
@@ -569,11 +1389,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 ();
@@ -606,18 +1430,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)
{
}
@@ -630,16 +1446,280 @@ 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::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::correctExtremesOfChild (Widget *child, Extremes *extremes,
+ bool useAdjustmentWidth)
+{
+ // 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 =
+ useAdjustmentWidth ? child->getMinWidth (extremes, false) : 0;
+ 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,
+ useAdjustmentWidth);
+ 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()
{
}
-void Widget::setAscent (int ascent)
+bool Widget::isBlockLevel ()
{
+ // Most widgets are not block-level.
+ return false;
}
-void Widget::setDescent (int descent)
+bool Widget::isPossibleContainer ()
{
+ // In most (all?) cases identical to:
+ return isBlockLevel ();
}
bool Widget::buttonPressImpl (EventButton *event)
@@ -659,7 +1739,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();
@@ -667,7 +1747,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();
@@ -679,5 +1759,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..f9d1293c 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,30 @@ 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,
+ bool useAdjustmentWidth);
+
+ 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 +332,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 +365,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, bool useAdjustmentWidth);
+ 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 +471,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 +505,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..aaaa45b7 100644
--- a/lout/identity.hh
+++ b/lout/identity.hh
@@ -51,7 +51,7 @@ namespace identity {
* \em name should be unique, e.g. the fully qualified class name.
* </ul>
*
- * After this, <i>class</i>::CLASS_ID refers to a number, which denotes the
+ * After this, <i>class</i>\::CLASS_ID refers to a number, which denotes the
* class. (If this is still -1, since the class has not yet been instantiated,
* any test will fail, which is correct.)
*
@@ -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.cc b/lout/misc.cc
index 8d630efc..9b333c93 100644
--- a/lout/misc.cc
+++ b/lout/misc.cc
@@ -133,6 +133,7 @@ void StringBuffer::clear ()
BitSet::BitSet(int initBits)
{
+ numBits = initBits;
numBytes = bytesForBits(initBits);
bits = (unsigned char*)malloc(numBytes * sizeof(unsigned char));
clear();
@@ -146,7 +147,7 @@ BitSet::~BitSet()
void BitSet::intoStringBuffer(misc::StringBuffer *sb)
{
sb->append("[");
- for (int i = 0; i < numBytes; i++)
+ for (int i = 0; i < numBits; i++)
sb->append(get(i) ? "1" : "0");
sb->append("]");
}
@@ -161,6 +162,9 @@ bool BitSet::get(int i) const
void BitSet::set(int i, bool val)
{
+ if (i > numBits)
+ numBits = i;
+
if (8 * i >= numBytes) {
int newNumBytes = numBytes;
while (8 * i >= newNumBytes)
diff --git a/lout/misc.hh b/lout/misc.hh
index 6a04c89a..0a05db31 100644
--- a/lout/misc.hh
+++ b/lout/misc.hh
@@ -12,7 +12,7 @@ namespace lout {
/**
* \brief Miscellaneous stuff, which does not fit anywhere else.
*
- * Actually, the other parts, beginning with ::object, depend on this.
+ * Actually, the other parts, beginning with \ref object, depend on this.
*/
namespace misc {
@@ -223,6 +223,30 @@ 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;
+ }
+
+ /**
+ * \brief Copies some elements into another vector of the same
+ * type.
+ *
+ * Cannot be used to copy elements within one vector. (For this,
+ * it would have to be extended to copy backwards in some cases.)
+ */
+ inline void copyTo(SimpleVector<T> *dest, int thisStart = 0,
+ int thisLast = -1, int destStart = 0) {
+ assert (dest != this);
+ if (thisLast == -1)
+ thisLast = this->size () - 1;
+ for (int i = thisStart; i <= thisLast; i++)
+ dest->set (i - thisStart + destStart, get (i));
+ }
};
/**
@@ -379,7 +403,7 @@ public:
this->startExtra = index;
resizeExtra ();
} else {
- if (index < startExtra) {
+ if (index < startExtra) {
consolidate ();
insert (index, numInsert);
} else if (index < startExtra + numExtra) {
@@ -421,14 +445,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 +524,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 +561,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 ();
@@ -528,7 +579,7 @@ class BitSet
{
private:
unsigned char *bits;
- int numBytes;
+ int numBits, numBytes;
inline int bytesForBits(int bits) { return bits == 0 ? 1 : (bits + 7) / 8; }
diff --git a/lout/msg.h b/lout/msg.h
index 4993c105..e52ff986 100644
--- a/lout/msg.h
+++ b/lout/msg.h
@@ -13,7 +13,6 @@
*/
#define _MSG(...)
#define _MSG_WARN(...)
-#define _MSG_ERR(...)
#define MSG(...) \
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/signal.hh b/lout/signal.hh
index 117779d6..00ecb91d 100644
--- a/lout/signal.hh
+++ b/lout/signal.hh
@@ -204,10 +204,10 @@ class Receiver;
* <li> \em Folding signals means to represent the signal itself by an integer
* number (enumeration), and translate the arguments in an object::Object*
* array. (If a given argument is not an instance of object::Object*,
- * the wrappers in ::object can be used.)
+ * the wrappers in \ref object can be used.)
* </ul>
*
- * \sa ::signal
+ * \sa \ref signal
*/
class Emitter: public object::Object
{
@@ -248,7 +248,7 @@ public:
* If defining a signal group, a sub class of this class must be defined,
* in which only the abstract signal methods must be defined.
*
- * \sa ::signal
+ * \sa \ref signal
*/
class Receiver: public object::Object
{
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..0cdb9499 100644
--- a/src/IO/IO.c
+++ b/src/IO/IO.c
@@ -21,6 +21,7 @@
#include "../klist.h"
#include "IO.h"
#include "iowatch.hh"
+#include "tls.h"
/*
* Symbolic defines for shutdown() function
@@ -162,6 +163,7 @@ static bool_t IO_read(IOData_t *io)
ssize_t St;
bool_t ret = FALSE;
int io_key = io->Key;
+ void *conn = a_Tls_connection(io->FD);
_MSG(" IO_read\n");
@@ -170,7 +172,8 @@ static bool_t IO_read(IOData_t *io)
io->Status = 0;
while (1) {
- St = read(io->FD, Buf, IOBufLen);
+ St = conn ? a_Tls_read(conn, Buf, IOBufLen)
+ : read(io->FD, Buf, IOBufLen);
if (St > 0) {
dStr_append_l(io->Buf, Buf, St);
continue;
@@ -214,12 +217,14 @@ static bool_t IO_write(IOData_t *io)
{
ssize_t St;
bool_t ret = FALSE;
+ void *conn = a_Tls_connection(io->FD);
_MSG(" IO_write\n");
io->Status = 0;
while (1) {
- St = write(io->FD, io->Buf->str, io->Buf->len);
+ St = conn ? a_Tls_write(conn, io->Buf->str, io->Buf->len)
+ : write(io->FD, io->Buf->str, io->Buf->len);
if (St < 0) {
/* Error */
if (errno == EINTR) {
@@ -298,6 +303,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 +357,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:
@@ -369,8 +377,8 @@ void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info,
char *newline = memchr(io->Buf->str, '\n', io->Buf->len);
int msglen = newline ? newline - io->Buf->str : 2048;
- MSG_WARN("IO_write, closing with pending data not sent: "
- "\"%s\"\n", dStr_printable(io->Buf, msglen));
+ MSG("IO_write, closing with pending data not sent: \"%s\"\n",
+ dStr_printable(io->Buf, msglen));
}
/* close FD, remove from ValidIOs and remove its watch */
IO_close_fd(io, Op == OpEnd ? IO_StopWr : IO_StopRdWr);
@@ -378,14 +386,21 @@ void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info,
dFree(Info);
break;
default:
- MSG_WARN("Unused CCC\n");
+ MSG_WARN("Unused CCC IO 1B\n");
break;
}
} 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");
+ MSG_WARN("Unused CCC IO 1F\n");
break;
}
}
@@ -406,14 +421,15 @@ 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;
default:
- MSG_WARN("Unused CCC\n");
+ MSG_WARN("Unused CCC IO 2B\n");
break;
}
} else { /* 2 FWD */
@@ -432,7 +448,7 @@ void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info,
dFree(Info);
break;
default:
- MSG_WARN("Unused CCC\n");
+ MSG_WARN("Unused CCC IO 2F\n");
break;
}
}
diff --git a/src/IO/Makefile.am b/src/IO/Makefile.am
index c889dae8..d8fed40a 100644
--- a/src/IO/Makefile.am
+++ b/src/IO/Makefile.am
@@ -1,6 +1,9 @@
AM_CPPFLAGS = \
-I$(top_srcdir) \
- -DDILLO_BINDIR='"$(bindir)/"'
+ -DDILLO_BINDIR='"$(bindir)/"' \
+ -DCA_CERTS_FILE='"@CA_CERTS_FILE@"' \
+ -DCA_CERTS_DIR='"@CA_CERTS_DIR@"'
+
AM_CFLAGS = @LIBFLTK_CFLAGS@
AM_CXXFLAGS = @LIBFLTK_CXXFLAGS@
@@ -12,6 +15,8 @@ libDiof_a_SOURCES = \
about.c \
Url.h \
http.c \
+ tls.h \
+ tls.c \
dpi.c \
IO.c \
iowatch.cc \
diff --git a/src/IO/Url.h b/src/IO/Url.h
index d9333d67..3f5a559b 100644
--- a/src/IO/Url.h
+++ b/src/IO/Url.h
@@ -16,10 +16,7 @@ extern void a_Http_freeall(void);
int a_Http_init(void);
int a_Http_proxy_auth(void);
void a_Http_set_proxy_passwd(const char *str);
-char *a_Http_make_connect_str(const DilloUrl *url);
-const char *a_Http_get_proxy_urlstr();
-Dstr *a_Http_make_query_str(const DilloUrl *url, const DilloUrl *requester,
- int web_flags, bool_t use_proxy);
+void a_Http_connect_done(int fd, bool_t success);
void a_Http_ccc (int Op, int Branch, int Dir, ChainLink *Info,
void *Data1, void *Data2);
diff --git a/src/IO/about.c b/src/IO/about.c
index f9966c3d..07dbbb01 100644
--- a/src/IO/about.c
+++ b/src/IO/about.c
@@ -305,6 +305,8 @@ const char *const AboutSplash=
"</table>\n"
"</table>\n"
"\n"
+"<table border='0' width='100%' cellpadding='0' cellspacing='0'><tr><td height='10'></table>\n"
+"\n"
"\n"
"<!-- the main layout table, a small vertical spacer -->\n"
"\n"
diff --git a/src/IO/http.c b/src/IO/http.c
index a0021a9e..379d51c1 100644
--- a/src/IO/http.c
+++ b/src/IO/http.c
@@ -27,6 +27,7 @@
#include <arpa/inet.h> /* for inet_ntop */
#include "IO.h"
+#include "tls.h"
#include "Url.h"
#include "../msg.h"
#include "../klist.h"
@@ -49,46 +50,47 @@ D_STMT_START { \
#define _MSG_BW(web, root, ...)
static const int HTTP_SOCKET_USE_PROXY = 0x1;
-static const int HTTP_SOCKET_QUEUED = 0x4;
-static const int HTTP_SOCKET_TO_BE_FREED = 0x8;
+static const int HTTP_SOCKET_QUEUED = 0x2;
+static const int HTTP_SOCKET_TO_BE_FREED = 0x4;
+static const int HTTP_SOCKET_TLS = 0x8;
-/* 'Url' and 'web' are just references (no need to deallocate them here). */
+/* 'web' is just a reference (no need to deallocate it here). */
typedef struct {
int SockFD;
- uint_t port; /* need a separate port in order to support PROXY */
uint_t flags;
DilloWeb *web; /* reference to client's web structure */
+ DilloUrl *url;
Dlist *addr_list; /* Holds the DNS answer */
- int Err; /* Holds the errno of the connect() call */
ChainLink *Info; /* Used for CCC asynchronous operations */
- char *connected_to; /* Used for per-host connection limit */
+ char *connected_to; /* Used for per-server connection limit */
+ uint_t connect_port;
+ Dstr *https_proxy_reply;
} SocketData_t;
/* 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;
+ char *host;
+ uint_t port;
+ bool_t https;
+
+ int active_conns;
+ int running_the_queue;
+ Dlist *queue;
+} Server_t;
typedef struct {
- char *host;
- int active_connections;
- SocketQueue_t 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);
-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);
+ int fd;
+ int skey;
+} FdMapEntry_t;
+
+static void Http_socket_enqueue(Server_t *srv, SocketData_t* sock);
+static Server_t *Http_server_get(const char *host, uint_t port, bool_t https);
+static void Http_server_remove(Server_t *srv);
+static void Http_connect_socket(ChainLink *Info);
+static char *Http_get_connect_str(const DilloUrl *url);
+static void Http_send_query(SocketData_t *S);
static void Http_socket_free(int SKey);
/*
@@ -99,7 +101,12 @@ static Klist_t *ValidSocks = NULL; /* Active sockets list. It holds pointers to
static DilloUrl *HTTP_Proxy = NULL;
static char *HTTP_Proxy_Auth_base64 = NULL;
static char *HTTP_Language_hdr = NULL;
-static Dlist *host_connections;
+static Dlist *servers;
+
+/* 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
@@ -124,7 +131,8 @@ int a_Http_init(void)
HTTP_Proxy_Auth_base64 = a_Misc_encode_base64(prefs.http_proxyuser);
*/
- host_connections = dList_new(5);
+ servers = dList_new(5);
+ fd_map = dList_new(20);
return 0;
}
@@ -155,33 +163,127 @@ 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);
}
-static void Http_connect_queued_sockets(HostConnection_t *hc)
+/*
+ * 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);
+}
+
+static void Http_fd_map_add_entry(SocketData_t *sd)
+{
+ FdMapEntry_t *e = dNew0(FdMapEntry_t, 1);
+ e->fd = sd->SockFD;
+ e->skey = VOIDP2INT(sd->Info->LocalKey);
+
+ if (dList_find_custom(fd_map, INT2VOIDP(e->fd), Http_fd_map_cmp)) {
+ MSG_ERR("FD ENTRY ALREADY FOUND FOR %d\n", e->fd);
+ assert(0);
+ }
+
+ dList_append(fd_map, e);
+}
+
+/*
+ * 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);
+ } else {
+ MSG("FD ENTRY NOT FOUND FOR %d\n", fd);
+ }
+}
+
+void a_Http_connect_done(int fd, bool_t success)
+{
+ SocketData_t *sd;
+ FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd),
+ Http_fd_map_cmp);
+
+ if (fme && (sd = a_Klist_get_data(ValidSocks, fme->skey))) {
+ ChainLink *info = sd->Info;
+ bool_t valid_web = a_Web_valid(sd->web);
+
+ if (success && valid_web) {
+ a_Chain_bfcb(OpSend, info, &sd->SockFD, "FD");
+ Http_send_query(sd);
+ } else {
+ if (valid_web)
+ MSG_BW(sd->web, 1, "Could not establish connection.");
+ MSG("fd %d is done and failed\n", sd->SockFD);
+ dClose(fd);
+ Http_socket_free(VOIDP2INT(info->LocalKey)); /* free sd */
+ a_Chain_bfcb(OpAbort, info, NULL, "Both");
+ dFree(info);
+ }
+ } else {
+ MSG("**** but no luck with fme %p or sd\n", fme);
+ }
+}
+
+static void Http_socket_activate(Server_t *srv, SocketData_t *sd)
+{
+ dList_remove(srv->queue, sd);
+ sd->flags &= ~HTTP_SOCKET_QUEUED;
+ srv->active_conns++;
+ sd->connected_to = srv->host;
+}
+
+static void Http_connect_queued_sockets(Server_t *srv)
{
SocketData_t *sd;
- while (hc->active_connections < prefs.http_max_conns &&
- (sd = Http_socket_dequeue(&hc->queue))) {
+ int i;
+
+ srv->running_the_queue++;
- sd->flags &= ~HTTP_SOCKET_QUEUED;
+ for (i = 0;
+ (i < dList_length(srv->queue) &&
+ srv->active_conns < prefs.http_max_conns);
+ i++) {
+ sd = dList_nth_data(srv->queue, i);
if (sd->flags & HTTP_SOCKET_TO_BE_FREED) {
- dFree(sd);
- } else if (a_Web_valid(sd->web)) {
- /* start connecting the socket */
- if (Http_connect_socket(sd->Info) < 0) {
- ChainLink *Info = sd->Info;
- MSG_BW(sd->web, 1, "ERROR: %s", dStrerror(sd->Err));
- a_Chain_bfcb(OpAbort, Info, NULL, "Both");
- Http_socket_free(VOIDP2INT(Info->LocalKey)); /* free sd */
- dFree(Info);
- } else {
- sd->connected_to = hc->host;
- hc->active_connections++;
+ dList_remove(srv->queue, sd);
+ dFree(sd);
+ i--;
+ } else {
+ int connect_ready = TLS_CONNECT_READY;
+
+ if (sd->flags & HTTP_SOCKET_TLS)
+ connect_ready = a_Tls_connect_ready(sd->url);
+
+ if (connect_ready == TLS_CONNECT_NEVER || !a_Web_valid(sd->web)) {
+ int SKey = VOIDP2INT(sd->Info->LocalKey);
+
+ Http_socket_free(SKey);
+ } else if (connect_ready == TLS_CONNECT_READY) {
+ i--;
+ Http_socket_activate(srv, sd);
+ Http_connect_socket(sd->Info);
}
}
}
+
+ _MSG("Queue http%s://%s:%u len %d\n", srv->https ? "s" : "", srv->host,
+ srv->port, dList_length(srv->queue));
+
+ if (--srv->running_the_queue == 0) {
+ if (srv->active_conns == 0)
+ Http_server_remove(srv);
+ }
}
/*
@@ -194,16 +296,24 @@ static void Http_socket_free(int SKey)
if ((S = a_Klist_get_data(ValidSocks, SKey))) {
a_Klist_remove(ValidSocks, SKey);
+ dStr_free(S->https_proxy_reply, 1);
+
if (S->flags & HTTP_SOCKET_QUEUED) {
S->flags |= HTTP_SOCKET_TO_BE_FREED;
+ a_Url_free(S->url);
} else {
+ if (S->SockFD != -1)
+ Http_fd_map_remove_entry(S->SockFD);
+ a_Tls_reset_server_state(S->url);
if (S->connected_to) {
- HostConnection_t *hc = Http_host_connection_get(S->connected_to);
- hc->active_connections--;
- Http_connect_queued_sockets(hc);
- if (hc->active_connections == 0)
- Http_host_connection_remove(hc);
+ a_Tls_close_by_fd(S->SockFD);
+
+ Server_t *srv = Http_server_get(S->connected_to, S->connect_port,
+ (S->flags & HTTP_SOCKET_TLS));
+ srv->active_conns--;
+ Http_connect_queued_sockets(srv);
}
+ a_Url_free(S->url);
dFree(S);
}
}
@@ -261,20 +371,23 @@ static Dstr *Http_make_content_type(const DilloUrl *url)
/*
* Make the http query string
*/
-Dstr *a_Http_make_query_str(const DilloUrl *url, const DilloUrl *requester,
- int web_flags, bool_t use_proxy)
+static Dstr *Http_make_query_str(DilloWeb *web, bool_t use_proxy)
{
char *ptr, *cookies, *referer, *auth;
+ const DilloUrl *url = web->url;
Dstr *query = dStr_new(""),
*request_uri = dStr_new(""),
*proxy_auth = dStr_new("");
/* BUG: dillo doesn't actually understand application/xml yet */
const char *accept_hdr_value =
- web_flags & WEB_Image ? "image/png,image/*;q=0.8,*/*;q=0.5" :
- web_flags & WEB_Stylesheet ? "text/css,*/*;q=0.1" :
+ web->flags & WEB_Image ? "image/png,image/*;q=0.8,*/*;q=0.5" :
+ 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) ? "keep-alive" : "close";
+
if (use_proxy) {
dStr_sprintfa(request_uri, "%s%s",
URL_STR(url),
@@ -292,7 +405,7 @@ Dstr *a_Http_make_query_str(const DilloUrl *url, const DilloUrl *requester,
(URL_PATH_(url) || URL_QUERY_(url)) ? "" : "/");
}
- cookies = a_Cookies_get_query(url, requester);
+ cookies = a_Cookies_get_query(url, web->requester);
auth = a_Auth_get_auth_str(url, request_uri->str);
referer = Http_get_referer(url);
if (URL_FLAGS(url) & URL_Post) {
@@ -309,15 +422,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 +446,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);
@@ -357,14 +470,13 @@ Dstr *a_Http_make_query_str(const DilloUrl *url, const DilloUrl *requester,
/*
* Create and submit the HTTP query to the IO engine
*/
-static void Http_send_query(ChainLink *Info, SocketData_t *S)
+static void Http_send_query(SocketData_t *S)
{
Dstr *query;
DataBuf *dbuf;
/* Create the query */
- query = a_Http_make_query_str(S->web->url, S->web->requester, S->web->flags,
- S->flags & HTTP_SOCKET_USE_PROXY);
+ query = Http_make_query_str(S->web, S->flags & HTTP_SOCKET_USE_PROXY);
dbuf = a_Chain_dbuf_new(query->str, query->len, 0);
/* actually this message is sent too early.
@@ -372,26 +484,49 @@ static void Http_send_query(ChainLink *Info, SocketData_t *S)
_MSG_BW(S->web, 1, "Sending query to %s...", URL_HOST_(S->web->url));
/* send query */
- a_Chain_bcb(OpSend, Info, dbuf, NULL);
+ a_Chain_bcb(OpSend, S->Info, dbuf, NULL);
dFree(dbuf);
dStr_free(query, 1);
}
/*
- * This function gets called after the DNS succeeds solving a hostname.
+ * Prepare an HTTPS connection. If necessary, tunnel it through a proxy.
+ * Then perform the TLS handshake.
+ */
+static void Http_connect_tls(ChainLink *info)
+{
+ int SKey = VOIDP2INT(info->LocalKey);
+ SocketData_t *S = a_Klist_get_data(ValidSocks, SKey);
+
+ if (S->flags & HTTP_SOCKET_USE_PROXY) {
+ char *connect_str = Http_get_connect_str(S->url);
+ DataBuf *dbuf = a_Chain_dbuf_new(connect_str, strlen(connect_str), 0);
+
+ a_Chain_bfcb(OpSend, info, &S->SockFD, "FD");
+ S->https_proxy_reply = dStr_new(NULL);
+ a_Chain_bcb(OpSend, info, dbuf, NULL);
+
+ dFree(dbuf);
+ dFree(connect_str);
+ } else {
+ a_Tls_handshake(S->SockFD, S->url);
+ }
+}
+
+/*
+ * This function is called after the DNS succeeds in solving a hostname.
* Task: Finish socket setup and start connecting the socket.
- * Return value: 0 on success; -1 on error.
*/
-static int Http_connect_socket(ChainLink *Info)
+static void Http_connect_socket(ChainLink *Info)
{
int i, status;
+ SocketData_t *S;
+ DilloHost *dh;
#ifdef ENABLE_IPV6
struct sockaddr_in6 name;
#else
struct sockaddr_in name;
#endif
- SocketData_t *S;
- DilloHost *dh;
socklen_t socket_len = 0;
S = a_Klist_get_data(ValidSocks, VOIDP2INT(Info->LocalKey));
@@ -399,10 +534,11 @@ static int Http_connect_socket(ChainLink *Info)
/* TODO: iterate this address list until success, or end-of-list */
for (i = 0; (dh = dList_nth_data(S->addr_list, i)); ++i) {
if ((S->SockFD = socket(dh->af, SOCK_STREAM, IPPROTO_TCP)) < 0) {
- S->Err = errno;
MSG("Http_connect_socket ERROR: %s\n", dStrerror(errno));
continue;
}
+ Http_fd_map_add_entry(S);
+
/* set NONBLOCKING and close on exec. */
fcntl(S->SockFD, F_SETFL, O_NONBLOCK | fcntl(S->SockFD, F_GETFL));
fcntl(S->SockFD, F_SETFD, FD_CLOEXEC | fcntl(S->SockFD, F_GETFD));
@@ -416,10 +552,11 @@ 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 = htons(S->connect_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));
+ MSG("Connecting to %s:%u\n", inet_ntoa(sin->sin_addr),
+ S->connect_port);
break;
}
#ifdef ENABLE_IPV6
@@ -429,39 +566,34 @@ static int Http_connect_socket(ChainLink *Info)
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&name;
socket_len = sizeof(struct sockaddr_in6);
sin6->sin6_family = dh->af;
- sin6->sin6_port =
- S->port ? htons(S->port) : htons(DILLO_URL_HTTP_PORT);
+ sin6->sin6_port = htons(S->connect_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))
- MSG("Connecting to %s\n", buf);
+ MSG("Connecting to %s:%u\n", buf, S->connect_port);
break;
}
#endif
}/*switch*/
-
MSG_BW(S->web, 1, "Contacting host...");
status = connect(S->SockFD, (struct sockaddr *)&name, socket_len);
if (status == -1 && errno != EINPROGRESS) {
- S->Err = errno;
- dClose(S->SockFD);
- MSG("Http_connect_socket ERROR: %s\n", dStrerror(S->Err));
+ MSG("Http_connect_socket ERROR: %s\n", dStrerror(errno));
+ a_Http_connect_done(S->SockFD, FALSE);
+ } else if (S->flags & HTTP_SOCKET_TLS) {
+ Http_connect_tls(Info);
} 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 */
+ a_Http_connect_done(S->SockFD, TRUE);
}
+ return;
}
-
- return -1;
}
/*
* Test proxy settings and check the no_proxy domains list
* Return value: whether to use proxy or not.
*/
-static int Http_must_use_proxy(const DilloUrl *url)
+static int Http_must_use_proxy(const char *hostname)
{
char *np, *p, *tok;
int ret = 0;
@@ -469,14 +601,13 @@ static int Http_must_use_proxy(const DilloUrl *url)
if (HTTP_Proxy) {
ret = 1;
if (prefs.no_proxy) {
- const char *host = URL_HOST(url);
- size_t host_len = strlen(host);
+ size_t host_len = strlen(hostname);
np = dStrdup(prefs.no_proxy);
for (p = np; (tok = dStrsep(&p, " ")); ) {
int start = host_len - strlen(tok);
- if (start >= 0 && dStrAsciiCasecmp(host + start, tok) == 0) {
+ if (start >= 0 && dStrAsciiCasecmp(hostname + start, tok) == 0) {
/* no_proxy token is suffix of host string */
ret = 0;
break;
@@ -485,22 +616,21 @@ static int Http_must_use_proxy(const DilloUrl *url)
dFree(np);
}
}
- _MSG("Http_must_use_proxy: %s\n %s\n", URL_STR(url), ret ? "YES":"NO");
+ _MSG("Http_must_use_proxy: %s\n %s\n", hostname, ret ? "YES":"NO");
return ret;
}
/*
* 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)
+static char *Http_get_connect_str(const DilloUrl *url)
{
Dstr *dstr;
const char *auth1;
int auth_len;
char *auth2, *proxy_auth, *retstr;
- dReturn_val_if_fail(Http_must_use_proxy(url), NULL);
+ dReturn_val_if_fail(Http_must_use_proxy(URL_HOST(url)), NULL);
dstr = dStr_new("");
auth1 = URL_AUTHORITY(url);
@@ -532,14 +662,6 @@ char *a_Http_make_connect_str(const DilloUrl *url)
}
/*
- * Return URL string of HTTP proxy, if any
- */
-const char *a_Http_get_proxy_urlstr()
-{
- return HTTP_Proxy ? URL_STR(HTTP_Proxy) : NULL;
-}
-
-/*
* Callback function for the DNS resolver.
* Continue connecting the socket, or abort upon error condition.
* S->web is checked to assert the operation wasn't aborted while waiting.
@@ -547,34 +669,35 @@ const char *a_Http_get_proxy_urlstr()
static void Http_dns_cb(int Status, Dlist *addr_list, void *data)
{
int SKey = VOIDP2INT(data);
+ bool_t clean_up = TRUE;
SocketData_t *S;
- HostConnection_t *hc;
+ Server_t *srv;
S = a_Klist_get_data(ValidSocks, SKey);
if (S) {
- if (!a_Web_valid(S->web)) {
- a_Chain_bfcb(OpAbort, S->Info, NULL, "Both");
- dFree(S->Info);
- Http_socket_free(SKey);
+ const char *host = URL_HOST((S->flags & HTTP_SOCKET_USE_PROXY) ?
+ HTTP_Proxy : S->url);
+ if (a_Web_valid(S->web)) {
+ if (Status == 0 && addr_list) {
+
+ /* Successful DNS answer; save the IP */
+ S->addr_list = addr_list;
+ clean_up = FALSE;
+ srv = Http_server_get(host, S->connect_port,
+ (S->flags & HTTP_SOCKET_TLS));
+ Http_socket_enqueue(srv, S);
+ Http_connect_queued_sockets(srv);
+ } else {
+ /* DNS wasn't able to resolve the hostname */
+ MSG_BW(S->web, 0, "ERROR: DNS can't resolve %s", host);
+ }
+ }
+ if (clean_up) {
+ ChainLink *info = S->Info;
- } else if (Status == 0 && addr_list) {
- /* Successful DNS answer; save the IP */
- S->addr_list = addr_list;
- S->flags |= HTTP_SOCKET_QUEUED;
- if (S->flags & HTTP_SOCKET_USE_PROXY)
- 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_connect_queued_sockets(hc);
- } else {
- /* DNS wasn't able to resolve the hostname */
- 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");
- dFree(S->Info);
Http_socket_free(SKey);
+ a_Chain_bfcb(OpAbort, info, NULL, "Both");
+ dFree(info);
}
}
}
@@ -590,6 +713,7 @@ static int Http_get(ChainLink *Info, void *Data1)
{
SocketData_t *S;
char *hostname;
+ const DilloUrl *url;
S = a_Klist_get_data(ValidSocks, VOIDP2INT(Info->LocalKey));
/* Reference Web data */
@@ -598,18 +722,20 @@ static int Http_get(ChainLink *Info, void *Data1)
S->Info = Info;
/* Proxy support */
- if (Http_must_use_proxy(S->web->url)) {
- hostname = dStrdup(URL_HOST(HTTP_Proxy));
- S->port = URL_PORT(HTTP_Proxy);
+ if (Http_must_use_proxy(URL_HOST(S->web->url))) {
+ url = HTTP_Proxy;
S->flags |= HTTP_SOCKET_USE_PROXY;
} else {
- hostname = dStrdup(URL_HOST(S->web->url));
- S->port = URL_PORT(S->web->url);
- S->flags &= ~HTTP_SOCKET_USE_PROXY;
+ url = S->web->url;
}
+ hostname = dStrdup(URL_HOST(url));
+ S->connect_port = URL_PORT(url);
+ S->url = a_Url_dup(S->web->url);
+ if (!dStrAsciiCasecmp(URL_SCHEME(S->url), "https"))
+ S->flags |= HTTP_SOCKET_TLS;
/* Let the user know what we'll do */
- MSG_BW(S->web, 1, "DNS resolving %s", URL_HOST_(S->web->url));
+ MSG_BW(S->web, 1, "DNS resolving %s", hostname);
/* Let the DNS engine resolve the hostname, and when done,
* we'll try to connect the socket from the callback function */
@@ -620,14 +746,75 @@ static int Http_get(ChainLink *Info, void *Data1)
}
/*
+ * Can the old socket's fd be reused for the new socket?
+ *
+ * NOTE: old and new must come from the same Server_t.
+ * This is not built to accept arbitrary sockets.
+ */
+static bool_t Http_socket_reuse_compatible(SocketData_t *old,
+ SocketData_t *new)
+{
+ /*
+ * If we are using TLS through a proxy, we need to ensure that old and new
+ * are going through to the same host:port.
+ */
+ if (a_Web_valid(new->web) &&
+ ((old->flags & HTTP_SOCKET_TLS) == 0 ||
+ (old->flags & HTTP_SOCKET_USE_PROXY) == 0 ||
+ ((URL_PORT(old->url) == URL_PORT(new->url)) &&
+ !dStrAsciiCasecmp(URL_HOST(old->url), URL_HOST(new->url)))))
+ return TRUE;
+ return FALSE;
+}
+
+/*
+ * 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);
+
+ if (old_sd) {
+ Server_t *srv = Http_server_get(old_sd->connected_to,
+ old_sd->connect_port,
+ (old_sd->flags & HTTP_SOCKET_TLS));
+ int i, n = dList_length(srv->queue);
+
+ for (i = 0; i < n; i++) {
+ new_sd = dList_nth_data(srv->queue, i);
+
+ if (!(new_sd->flags & HTTP_SOCKET_TO_BE_FREED) &&
+ Http_socket_reuse_compatible(old_sd, new_sd)) {
+ const bool_t success = TRUE;
+
+ new_sd->SockFD = old_sd->SockFD;
+
+ old_sd->connected_to = NULL;
+ srv->active_conns--;
+ Http_socket_free(SKey);
+
+ MSG("Reusing fd %d for %s\n", new_sd->SockFD,URL_STR(new_sd->url));
+ Http_socket_activate(srv, new_sd);
+ Http_fd_map_add_entry(new_sd);
+ a_Http_connect_done(new_sd->SockFD, success);
+ return;
+ }
+ }
+ dClose(old_sd->SockFD);
+ Http_socket_free(SKey);
+ }
+}
+
+/*
* CCC function for the HTTP module
*/
void a_Http_ccc(int Op, int Branch, int Dir, ChainLink *Info,
void *Data1, void *Data2)
{
int SKey = VOIDP2INT(Info->LocalKey);
-
- (void)Data2; /* suppress unused parameter warning */
+ SocketData_t *sd;
+ DataBuf *dbuf;
dReturn_if_fail( a_Chain_check("a_Http_ccc", Op, Branch, Dir, Info) );
@@ -648,103 +835,207 @@ 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:
- /* something bad happened... */
- a_Chain_bcb(OpAbort, Info, NULL, NULL);
+ MSG("ABORT 1B\n");
Http_socket_free(SKey);
+ a_Chain_bcb(OpAbort, Info, NULL, NULL);
dFree(Info);
break;
+ default:
+ MSG_WARN("Unused CCC 1B Op %d\n", Op);
+ break;
}
} else { /* 1 FWD */
/* HTTP send-query status branch */
switch (Op) {
+ case OpAbort:
+ MSG("ABORT 1F\n");
+ if ((sd = a_Klist_get_data(ValidSocks, SKey)))
+ MSG_BW(sd->web, 1, "Can't get %s", URL_STR(sd->url));
+ Http_socket_free(SKey);
+ a_Chain_fcb(OpAbort, Info, NULL, "Both");
+ dFree(Info);
+ break;
default:
- MSG_WARN("Unused CCC\n");
+ MSG_WARN("Unused CCC 1F Op %d\n", Op);
+ break;
+ }
+ }
+ } else if (Branch == 2) {
+ if (Dir == FWD) {
+ sd = a_Klist_get_data(ValidSocks, SKey);
+ assert(sd);
+ /* Receiving from server */
+ switch (Op) {
+ case OpSend:
+ if (sd->https_proxy_reply) {
+ dbuf = Data1;
+ dStr_append(sd->https_proxy_reply, dbuf->Buf);
+ if (strstr(sd->https_proxy_reply->str, "\r\n\r\n")) {
+ if (sd->https_proxy_reply->len >= 12 &&
+ sd->https_proxy_reply->str[9] == '2') {
+ /* e.g. "HTTP/1.1 200 Connection established[...]" */
+ MSG("CONNECT through proxy succeeded. Reply:\n%s\n",
+ sd->https_proxy_reply->str);
+ dStr_free(sd->https_proxy_reply, 1);
+ sd->https_proxy_reply = NULL;
+ a_Tls_handshake(sd->SockFD, sd->url);
+ } else {
+ MSG_BW(sd->web, 1, "Can't connect through proxy to %s",
+ URL_HOST(sd->url));
+ MSG("CONNECT through proxy failed. Server sent:\n%s\n",
+ sd->https_proxy_reply->str);
+ Http_socket_free(SKey);
+ a_Chain_bfcb(OpAbort, Info, NULL, "Both");
+ dFree(Info);
+ }
+ }
+ } else {
+ /* Data1 = dbuf */
+ a_Chain_fcb(OpSend, Info, Data1, "send_page_2eof");
+ }
+ break;
+ case OpEnd:
+ if (sd->https_proxy_reply) {
+ MSG("CONNECT through proxy failed. "
+ "Full reply not received:\n%s\n",
+ sd->https_proxy_reply->len ? sd->https_proxy_reply->str :
+ "(nothing)");
+ Http_socket_free(SKey);
+ a_Chain_bfcb(OpAbort, Info, NULL, "Both");
+ } else {
+ Http_socket_free(SKey);
+ a_Chain_fcb(OpEnd, Info, NULL, NULL);
+ }
+ dFree(Info);
+ break;
+ default:
+ MSG_WARN("Unused CCC 2F Op %d\n", Op);
+ 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:
+ Http_socket_free(SKey);
+ a_Chain_bcb(OpAbort, Info, NULL, NULL);
+ dFree(Info);
+ break;
+ default:
+ MSG_WARN("Unused CCC 2B Op %d\n", Op);
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(Server_t *srv, SocketData_t* sock)
{
- SocketQueueEntry_t *se = dNew(SocketQueueEntry_t, 1);
-
- se->sock = sock;
- se->next = NULL;
+ sock->flags |= HTTP_SOCKET_QUEUED;
- if (sq->tail)
- sq->tail->next = se;
- sq->tail = se;
+ if ((sock->web->flags & WEB_Image) == 0) {
+ int i, n = dList_length(srv->queue);
- if (! sq->head)
- sq->head = se;
-}
+ for (i = 0; i < n; i++) {
+ SocketData_t *curr = dList_nth_data(srv->queue, i);
-static SocketData_t* Http_socket_dequeue(SocketQueue_t *sq)
-{
- 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);
+ if (a_Web_valid(curr->web) && (curr->web->flags & WEB_Image)) {
+ dList_insert_pos(srv->queue, sock, i);
+ return;
+ }
+ }
}
-
- return sd;
+ dList_append(srv->queue, sock);
}
-static HostConnection_t *Http_host_connection_get(const char *host)
+static Server_t *Http_server_get(const char *host, uint_t port, bool_t https)
{
int i;
- HostConnection_t *hc;
+ Server_t *srv;
- for (i = 0; i < dList_length(host_connections); i++) {
- hc = (HostConnection_t*) dList_nth_data(host_connections, i);
+ for (i = 0; i < dList_length(servers); i++) {
+ srv = (Server_t*) dList_nth_data(servers, i);
- if (dStrAsciiCasecmp(host, hc->host) == 0)
- return hc;
+ if (port == srv->port && https == srv->https &&
+ !dStrAsciiCasecmp(host, srv->host))
+ return srv;
}
- hc = dNew0(HostConnection_t, 1);
- Http_socket_queue_init(&hc->queue);
- hc->host = dStrdup(host);
- dList_append(host_connections, hc);
+ srv = dNew0(Server_t, 1);
+ srv->queue = dList_new(10);
+ srv->running_the_queue = 0;
+ srv->host = dStrdup(host);
+ srv->port = port;
+ srv->https = https;
+ dList_append(servers, srv);
- return hc;
+ return srv;
}
-static void Http_host_connection_remove(HostConnection_t *hc)
+static void Http_server_remove(Server_t *srv)
{
- assert(hc->queue.head == NULL);
- dList_remove_fast(host_connections, hc);
- dFree(hc->host);
- dFree(hc);
+ SocketData_t *sd;
+
+ while ((sd = dList_nth_data(srv->queue, 0))) {
+ dList_remove_fast(srv->queue, sd);
+ dFree(sd);
+ }
+ dList_free(srv->queue);
+ dList_remove_fast(servers, srv);
+ dFree(srv->host);
+ dFree(srv);
+}
+
+static void Http_servers_remove_all()
+{
+ Server_t *srv;
+ SocketData_t *sd;
+
+ while (dList_length(servers) > 0) {
+ srv = (Server_t*) dList_nth_data(servers, 0);
+ while ((sd = dList_nth_data(srv->queue, 0))) {
+ dList_remove(srv->queue, sd);
+ dFree(sd);
+ }
+ Http_server_remove(srv);
+ }
+ dList_free(servers);
}
-static void Http_host_connection_remove_all()
+static void Http_fd_map_remove_all()
{
- HostConnection_t *hc;
+ FdMapEntry_t *fme;
+ int i, n = dList_length(fd_map);
- while (dList_length(host_connections) > 0) {
- hc = (HostConnection_t*) dList_nth_data(host_connections, 0);
- while (Http_socket_dequeue(&hc->queue));
- Http_host_connection_remove(hc);
+ for (i = 0; i < n; i++) {
+ fme = (FdMapEntry_t *) dList_nth_data(fd_map, i);
+ dFree(fme);
}
- dList_free(host_connections);
+ dList_free(fd_map);
}
/*
@@ -753,7 +1044,8 @@ static void Http_host_connection_remove_all()
*/
void a_Http_freeall(void)
{
- Http_host_connection_remove_all();
+ Http_servers_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/IO/tls.c b/src/IO/tls.c
new file mode 100644
index 00000000..dfe76744
--- /dev/null
+++ b/src/IO/tls.c
@@ -0,0 +1,1258 @@
+/*
+ * File: tls.c
+ *
+ * Copyright 2004 Garrett Kajmowicz <gkajmowi@tbaytel.net>
+ * (for some bits derived from the https dpi, e.g., certificate handling)
+ * Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008,
+ * 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
+ * (for the certificate hostname checking from wget)
+ * Copyright (C) 2011 Benjamin Johnson <obeythepenguin@users.sourceforge.net>
+ * (for the https code offered from dplus browser that formed the basis...)
+ *
+ * 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.
+ *
+ * As a special exception, permission is granted to link Dillo with the OpenSSL
+ * or LibreSSL library, and distribute the linked executables without
+ * including the source code for OpenSSL or LibreSSL in the source
+ * distribution. You must obey the GNU General Public License, version 3, in
+ * all respects for all of the code used other than OpenSSL or LibreSSL.
+ */
+
+/* https://www.ssllabs.com/ssltest/viewMyClient.html
+ * https://github.com/lgarron/badssl.com
+ */
+
+/*
+ * Using TLS in Applications: http://datatracker.ietf.org/wg/uta/documents/
+ * TLS: http://datatracker.ietf.org/wg/tls/documents/
+ */
+
+#include "config.h"
+#include "../msg.h"
+
+#ifndef ENABLE_SSL
+
+void a_Tls_init()
+{
+ MSG("TLS: Disabled at compilation time.\n");
+}
+
+#else
+
+#include <assert.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <ctype.h> /* tolower for wget stuff */
+#include <stdio.h>
+#include <errno.h>
+#include "../../dlib/dlib.h"
+#include "../dialog.hh"
+#include "../klist.h"
+#include "iowatch.hh"
+#include "tls.h"
+#include "Url.h"
+
+#include <openssl/ssl.h>
+#include <openssl/rand.h>
+#include <openssl/err.h>
+#include <openssl/x509v3.h> /* for hostname checking */
+
+#define CERT_STATUS_NONE 0
+#define CERT_STATUS_RECEIVING 1
+#define CERT_STATUS_CLEAN 2
+#define CERT_STATUS_BAD 3
+#define CERT_STATUS_USER_ACCEPTED 4
+
+typedef struct {
+ char *hostname;
+ int port;
+ int cert_status;
+} Server_t;
+
+typedef struct {
+ int fd;
+ int connkey;
+} FdMapEntry_t;
+
+/*
+ * Data type for TLS connection information
+ */
+typedef struct {
+ int fd;
+ DilloUrl *url;
+ SSL *ssl;
+ bool_t connecting;
+} Conn_t;
+
+/* List of active TLS connections */
+static Klist_t *conn_list = NULL;
+
+/*
+ * If ssl_context is still NULL, this corresponds to TLS being disabled.
+ */
+static SSL_CTX *ssl_context;
+static Dlist *servers;
+static Dlist *fd_map;
+
+static void Tls_connect_cb(int fd, void *vconnkey);
+
+/*
+ * Compare by FD.
+ */
+static int Tls_fd_map_cmp(const void *v1, const void *v2)
+{
+ int fd = VOIDP2INT(v2);
+ const FdMapEntry_t *e = v1;
+
+ return (fd != e->fd);
+}
+
+static void Tls_fd_map_add_entry(int fd, int connkey)
+{
+ FdMapEntry_t *e = dNew0(FdMapEntry_t, 1);
+ e->fd = fd;
+ e->connkey = connkey;
+
+ if (dList_find_custom(fd_map, INT2VOIDP(e->fd), Tls_fd_map_cmp)) {
+ MSG_ERR("TLS FD ENTRY ALREADY FOUND FOR %d\n", e->fd);
+ assert(0);
+ }
+
+ dList_append(fd_map, e);
+//MSG("ADD ENTRY %d %s\n", e->fd, URL_STR(sd->url));
+}
+
+/*
+ * Remove and free entry from fd_map.
+ */
+static void Tls_fd_map_remove_entry(int fd)
+{
+ void *data = dList_find_custom(fd_map, INT2VOIDP(fd), Tls_fd_map_cmp);
+
+//MSG("REMOVE ENTRY %d\n", fd);
+ if (data) {
+ dList_remove_fast(fd_map, data);
+ dFree(data);
+ } else {
+ MSG("TLS FD ENTRY NOT FOUND FOR %d\n", fd);
+ }
+}
+
+/*
+ * Return TLS connection information for a given file
+ * descriptor, or NULL if no TLS connection was found.
+ */
+void *a_Tls_connection(int fd)
+{
+ Conn_t *conn;
+
+ if (fd_map) {
+ FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd),
+ Tls_fd_map_cmp);
+
+ if (fme && (conn = a_Klist_get_data(conn_list, fme->connkey)))
+ return conn;
+ }
+ return NULL;
+}
+
+/*
+ * Add a new TLS connection information node.
+ */
+static int Tls_conn_new(int fd, const DilloUrl *url, SSL *ssl)
+{
+ int key;
+
+ Conn_t *conn = dNew0(Conn_t, 1);
+ conn->fd = fd;
+ conn->url = a_Url_dup(url);
+ conn->ssl = ssl;
+ conn->connecting = TRUE;
+
+ key = a_Klist_insert(&conn_list, conn);
+
+ Tls_fd_map_add_entry(fd, key);
+
+ return key;
+}
+
+/*
+ * Let's monitor for TLS alerts.
+ */
+static void Tls_info_cb(const SSL *ssl, int where, int ret)
+{
+ if (where & SSL_CB_ALERT) {
+ const char *str = SSL_alert_desc_string_long(ret);
+
+ if (strcmp(str, "close notify"))
+ MSG("TLS ALERT on %s: %s\n", (where & SSL_CB_READ) ? "read" : "write",
+ str);
+ }
+}
+
+/*
+ * Load trusted certificates.
+ * This is like using SSL_CTX_load_verify_locations() but permitting more
+ * than one bundle and more than one directory. Due to the notoriously
+ * abysmal openssl documentation, this was worked out from reading discussion
+ * on the web and then reading openssl source to see what it normally does.
+ */
+static void Tls_load_certificates()
+{
+ /* curl-7.37.1 says that the following bundle locations are used on "Debian
+ * systems", "Redhat and Mandriva", "old(er) Redhat", "FreeBSD", and
+ * "OpenBSD", respectively -- and that the /etc/ssl/certs/ path is needed on
+ * "SUSE". No doubt it's all changed some over time, but this gives us
+ * something to work with.
+ */
+ uint_t u;
+ char *userpath;
+ static const char *const ca_files[] = {
+ "/etc/ssl/certs/ca-certificates.crt",
+ "/etc/pki/tls/certs/ca-bundle.crt",
+ "/usr/share/ssl/certs/ca-bundle.crt",
+ "/usr/local/share/certs/ca-root.crt",
+ "/etc/ssl/cert.pem",
+ CA_CERTS_FILE
+ };
+
+ static const char *const ca_paths[] = {
+ "/etc/ssl/certs/",
+ CA_CERTS_DIR
+ };
+
+ X509_STORE *store = SSL_CTX_get_cert_store(ssl_context);
+ X509_LOOKUP *lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
+
+ for (u = 0; u < sizeof(ca_files) / sizeof(ca_files[0]); u++) {
+ if (*ca_files[u])
+ X509_LOOKUP_load_file(lookup, ca_files[u], X509_FILETYPE_PEM);
+ }
+
+ lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
+ for (u = 0; u < sizeof(ca_paths)/sizeof(ca_paths[0]); u++) {
+ if (*ca_paths[u])
+ X509_LOOKUP_add_dir(lookup, ca_paths[u], X509_FILETYPE_PEM);
+ }
+
+ userpath = dStrconcat(dGethomedir(), "/.dillo/certs/", NULL);
+ X509_LOOKUP_add_dir(lookup, userpath, X509_FILETYPE_PEM);
+ dFree(userpath);
+
+ /* Clear out errors in the queue (file not found, etc.) */
+ while(ERR_get_error())
+ ;
+}
+
+/*
+ * Initialize the OpenSSL library.
+ */
+void a_Tls_init(void)
+{
+ SSL_library_init();
+ SSL_load_error_strings();
+ if (RAND_status() != 1) {
+ /* The standard solution is to provide it with more entropy, but this
+ * involves knowing very well that you are doing exactly the right thing.
+ */
+ MSG_ERR("Disabling HTTPS: Insufficient entropy for openssl.\n");
+ return;
+ }
+
+ /* Create SSL context */
+ ssl_context = SSL_CTX_new(SSLv23_client_method());
+ if (ssl_context == NULL) {
+ MSG_ERR("Disabling HTTPS: Error creating SSL context.\n");
+ return;
+ }
+
+ SSL_CTX_set_info_callback(ssl_context, Tls_info_cb);
+
+ /* 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; RC4, for which methods were
+ * found in 2013 to defeat it somewhat too easily.
+ */
+ SSL_CTX_set_cipher_list(ssl_context,
+ "ALL:!aNULL:!eNULL:!LOW:!EXPORT40:!RC4");
+
+ /* SSL2 has been known to be insecure forever, disabling SSL3 is in response
+ * to POODLE, and disabling compression is in response to CRIME.
+ */
+ SSL_CTX_set_options(ssl_context,
+ SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_COMPRESSION);
+
+ /* This lets us deal with self-signed certificates */
+ SSL_CTX_set_verify(ssl_context, SSL_VERIFY_NONE, NULL);
+
+ Tls_load_certificates();
+
+ fd_map = dList_new(20);
+ servers = dList_new(8);
+}
+
+/*
+ * Save certificate with a hashed filename.
+ * Return: 0 on success, 1 on failure.
+ */
+static int Tls_save_certificate_home(X509 * cert)
+{
+ char buf[4096];
+
+ FILE * fp = NULL;
+ uint_t i = 0;
+ int ret = 1;
+
+ /* Attempt to create .dillo/certs blindly - check later */
+ snprintf(buf, 4096, "%s/.dillo/", dGethomedir());
+ mkdir(buf, 01777);
+ snprintf(buf, 4096, "%s/.dillo/certs/", dGethomedir());
+ mkdir(buf, 01777);
+
+ do {
+ snprintf(buf, 4096, "%s/.dillo/certs/%lx.%u",
+ dGethomedir(), X509_subject_name_hash(cert), i);
+
+ fp=fopen(buf, "r");
+ if (fp == NULL){
+ /* File name doesn't exist so we can use it safely */
+ fp=fopen(buf, "w");
+ if (fp == NULL){
+ MSG("Unable to open cert save file in home dir\n");
+ break;
+ } else {
+ PEM_write_X509(fp, cert);
+ fclose(fp);
+ MSG("Wrote certificate\n");
+ ret = 0;
+ break;
+ }
+ } else {
+ fclose(fp);
+ }
+ i++;
+ /* Don't loop too many times - just give up */
+ } while (i < 1024);
+
+ return ret;
+}
+
+/*
+ * Ordered comparison of servers.
+ */
+static int Tls_servers_cmp(const void *v1, const void *v2)
+{
+ const Server_t *s1 = (const Server_t *)v1, *s2 = (const Server_t *)v2;
+ int cmp = dStrAsciiCasecmp(s1->hostname, s2->hostname);
+
+ if (!cmp)
+ cmp = s1->port - s2->port;
+ return cmp;
+}
+/*
+ * Ordered comparison of server with URL.
+ */
+static int Tls_servers_by_url_cmp(const void *v1, const void *v2)
+{
+ const Server_t *s = (const Server_t *)v1;
+ const DilloUrl *url = (const DilloUrl *)v2;
+
+ int cmp = dStrAsciiCasecmp(s->hostname, URL_HOST(url));
+
+ if (!cmp)
+ cmp = s->port - URL_PORT(url);
+ return cmp;
+}
+
+/*
+ * The purpose here is to permit a single initial connection to a server.
+ * Once we have the certificate, know whether we like it -- and whether the
+ * user accepts it -- HTTP can run through queued sockets as normal.
+ *
+ * Return: TLS_CONNECT_READY or TLS_CONNECT_NOT_YET or TLS_CONNECT_NEVER.
+ */
+int a_Tls_connect_ready(const DilloUrl *url)
+{
+ Server_t *s;
+ int ret = TLS_CONNECT_READY;
+
+ dReturn_val_if_fail(ssl_context, TLS_CONNECT_NEVER);
+
+ if ((s = dList_find_sorted(servers, url, Tls_servers_by_url_cmp))) {
+ if (s->cert_status == CERT_STATUS_RECEIVING)
+ ret = TLS_CONNECT_NOT_YET;
+ else if (s->cert_status == CERT_STATUS_BAD)
+ ret = TLS_CONNECT_NEVER;
+
+ if (s->cert_status == CERT_STATUS_NONE)
+ s->cert_status = CERT_STATUS_RECEIVING;
+ } else {
+ s = dNew(Server_t, 1);
+
+ s->hostname = dStrdup(URL_HOST(url));
+ s->port = URL_PORT(url);
+ s->cert_status = CERT_STATUS_RECEIVING;
+ dList_insert_sorted(servers, s, Tls_servers_cmp);
+ }
+ return ret;
+}
+
+static int Tls_cert_status(const DilloUrl *url)
+{
+ Server_t *s = dList_find_sorted(servers, url, Tls_servers_by_url_cmp);
+
+ return s ? s->cert_status : CERT_STATUS_NONE;
+}
+
+/*
+ * Did we find problems with the certificate, and did the user proceed to
+ * reject the connection?
+ */
+static int Tls_user_said_no(const DilloUrl *url)
+{
+ return Tls_cert_status(url) == CERT_STATUS_BAD;
+}
+
+/*
+ * Did everything seem proper with the certificate -- no warnings to
+ * click through?
+ */
+int a_Tls_certificate_is_clean(const DilloUrl *url)
+{
+ return Tls_cert_status(url) == CERT_STATUS_CLEAN;
+}
+
+/******************** BEGINNING OF STUFF DERIVED FROM wget-1.16.3 */
+
+#define ASTERISK_EXCLUDES_DOT /* mandated by rfc2818 */
+
+/* Return true is STRING (case-insensitively) matches PATTERN, false
+ otherwise. The recognized wildcard character is "*", which matches
+ any character in STRING except ".". Any number of the "*" wildcard
+ may be present in the pattern.
+
+ This is used to match of hosts as indicated in rfc2818: "Names may
+ contain the wildcard character * which is considered to match any
+ single domain name component or component fragment. E.g., *.a.com
+ matches foo.a.com but not bar.foo.a.com. f*.com matches foo.com but
+ not bar.com [or foo.bar.com]."
+
+ If the pattern contain no wildcards, pattern_match(a, b) is
+ equivalent to !strcasecmp(a, b). */
+
+static bool_t pattern_match (const char *pattern, const char *string)
+{
+
+ const char *p = pattern, *n = string;
+ char c;
+ for (; (c = tolower (*p++)) != '\0'; n++)
+ if (c == '*')
+ {
+ for (c = tolower (*p); c == '*'; c = tolower (*++p))
+ ;
+ for (; *n != '\0'; n++)
+ if (tolower (*n) == c && pattern_match (p, n))
+ return TRUE;
+#ifdef ASTERISK_EXCLUDES_DOT
+ else if (*n == '.')
+ return FALSE;
+#endif
+ return c == '\0';
+ }
+ else
+ {
+ if (c != tolower (*n))
+ return FALSE;
+ }
+ return *n == '\0';
+}
+
+/*
+ * Check that the certificate corresponds to the site it's presented for.
+ *
+ * Return TRUE if the hostname matched or the user indicated acceptance.
+ * FALSE on failure.
+ */
+static bool_t Tls_check_cert_hostname(X509 *cert, const char *host,
+ int *choice)
+{
+ dReturn_val_if_fail(cert && host, FALSE);
+
+ char *msg;
+ GENERAL_NAMES *subjectAltNames;
+ bool_t success = TRUE, alt_name_checked = FALSE;;
+ char common_name[256];
+
+ /* Check that HOST matches the common name in the certificate.
+ #### The following remains to be done:
+
+ - When matching against common names, it should loop over all
+ common names and choose the most specific one, i.e. the last
+ one, not the first one, which the current code picks.
+
+ - Ensure that ASN1 strings from the certificate are encoded as
+ UTF-8 which can be meaningfully compared to HOST. */
+
+ subjectAltNames = X509_get_ext_d2i (cert, NID_subject_alt_name, NULL, NULL);
+
+ if (subjectAltNames)
+ {
+ /* Test subject alternative names */
+
+ Dstr *err = dStr_new("");
+ dStr_sprintf(err, "Hostname %s does not match any of certificate's "
+ "Subject Alternative Names: ", host);
+
+ /* Do we want to check for dNSNAmes or ipAddresses (see RFC 2818)?
+ * Signal it by host_in_octet_string. */
+ ASN1_OCTET_STRING *host_in_octet_string = a2i_IPADDRESS (host);
+
+ int numaltnames = sk_GENERAL_NAME_num (subjectAltNames);
+ int i;
+ for (i=0; i < numaltnames; i++)
+ {
+ const GENERAL_NAME *name =
+ sk_GENERAL_NAME_value (subjectAltNames, i);
+ if (name)
+ {
+ if (host_in_octet_string)
+ {
+ if (name->type == GEN_IPADD)
+ {
+ /* Check for ipAddress */
+ /* TODO: Should we convert between IPv4-mapped IPv6
+ * addresses and IPv4 addresses? */
+ alt_name_checked = TRUE;
+ if (!ASN1_STRING_cmp (host_in_octet_string,
+ name->d.iPAddress))
+ break;
+ dStr_sprintfa(err, "%s ", name->d.iPAddress);
+ }
+ }
+ else if (name->type == GEN_DNS)
+ {
+ /* dNSName should be IA5String (i.e. ASCII), however who
+ * does trust CA? Convert it into UTF-8 for sure. */
+ unsigned char *name_in_utf8 = NULL;
+
+ /* Check for dNSName */
+ alt_name_checked = TRUE;
+
+ if (0 <= ASN1_STRING_to_UTF8 (&name_in_utf8, name->d.dNSName))
+ {
+ /* Compare and check for NULL attack in ASN1_STRING */
+ if (pattern_match ((char *)name_in_utf8, host) &&
+ (strlen ((char *)name_in_utf8) ==
+ (size_t)ASN1_STRING_length (name->d.dNSName)))
+ {
+ OPENSSL_free (name_in_utf8);
+ break;
+ }
+ dStr_sprintfa(err, "%s ", name_in_utf8);
+ OPENSSL_free (name_in_utf8);
+ }
+ }
+ }
+ }
+ sk_GENERAL_NAME_pop_free(subjectAltNames, GENERAL_NAME_free);
+ if (host_in_octet_string)
+ ASN1_OCTET_STRING_free(host_in_octet_string);
+
+ if (alt_name_checked == TRUE && i >= numaltnames)
+ {
+ success = FALSE;
+ *choice = a_Dialog_choice("Dillo TLS security warning",
+ err->str, "Continue", "Cancel", NULL);
+
+ switch (*choice){
+ case 1:
+ success = TRUE;
+ break;
+ case 2:
+ break;
+ default:
+ break;
+ }
+ }
+ dStr_free(err, 1);
+ }
+
+ if (alt_name_checked == FALSE)
+ {
+ /* Test commomName */
+ X509_NAME *xname = X509_get_subject_name(cert);
+ common_name[0] = '\0';
+ X509_NAME_get_text_by_NID (xname, NID_commonName, common_name,
+ sizeof (common_name));
+
+ if (!pattern_match (common_name, host))
+ {
+ success = FALSE;
+ msg = dStrconcat("Certificate common name ", common_name,
+ " doesn't match requested host name ", host, NULL);
+ *choice = a_Dialog_choice("Dillo TLS security warning",
+ msg, "Continue", "Cancel", NULL);
+ dFree(msg);
+
+ switch (*choice){
+ case 1:
+ success = TRUE;
+ break;
+ case 2:
+ break;
+ default:
+ break;
+ }
+ }
+ else
+ {
+ /* We now determine the length of the ASN1 string. If it
+ * differs from common_name's length, then there is a \0
+ * before the string terminates. This can be an instance of a
+ * null-prefix attack.
+ *
+ * https://www.blackhat.com/html/bh-usa-09/bh-usa-09-archives.html#Marlinspike
+ * */
+
+ int i = -1, j;
+ X509_NAME_ENTRY *xentry;
+ ASN1_STRING *sdata;
+
+ if (xname) {
+ for (;;)
+ {
+ j = X509_NAME_get_index_by_NID (xname, NID_commonName, i);
+ if (j == -1) break;
+ i = j;
+ }
+ }
+
+ xentry = X509_NAME_get_entry(xname,i);
+ sdata = X509_NAME_ENTRY_get_data(xentry);
+ if (strlen (common_name) != (size_t)ASN1_STRING_length (sdata))
+ {
+ success = FALSE;
+ msg = dStrconcat("Certificate common name is invalid (contains a NUL "
+ "character). This may be an indication that the "
+ "host is not who it claims to be -- that is, not "
+ "the real ", host, NULL);
+ *choice = a_Dialog_choice("Dillo TLS security warning",
+ msg, "Continue", "Cancel", NULL);
+ dFree(msg);
+
+ switch (*choice){
+ case 1:
+ success = TRUE;
+ break;
+ case 2:
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+ return success;
+}
+
+/******************** END OF STUFF DERIVED FROM wget-1.16.3 */
+
+/*
+ * Get the certificate at the end of the chain, or NULL on failure.
+ *
+ * Rumor has it that the stack can be NULL if a connection has been reused
+ * and that the stack can then be reconstructed if necessary, but it doesn't
+ * sound like a case we'll encounter.
+ */
+static X509 *Tls_get_end_of_chain(SSL *ssl)
+{
+ STACK_OF(X509) *sk = SSL_get_peer_cert_chain(ssl);
+
+ return sk ? sk_X509_value(sk, sk_X509_num(sk) - 1) : NULL;
+}
+
+static void Tls_get_issuer_name(X509 *cert, char *buf, uint_t buflen)
+{
+ if (cert) {
+ X509_NAME_oneline(X509_get_issuer_name(cert), buf, buflen);
+ } else {
+ strncpy(buf, "(unknown)", buflen);
+ buf[buflen-1] = '\0';
+ }
+}
+
+static void Tls_get_expiration_str(X509 *cert, char *buf, uint_t buflen)
+{
+ ASN1_TIME *exp_date = X509_get_notAfter(cert);
+ BIO *b = BIO_new(BIO_s_mem());
+ int rc = ASN1_TIME_print(b, exp_date);
+
+ if (rc > 0) {
+ rc = BIO_gets(b, buf, buflen);
+ }
+ if (rc <= 0) {
+ strncpy(buf, "(unknown)", buflen);
+ buf[buflen-1] = '\0';
+ }
+ BIO_free(b);
+}
+
+/*
+ * Examine the certificate, and, if problems are detected, ask the user what
+ * to do.
+ * Return: -1 if connection should be canceled, or 0 if it should continue.
+ */
+static int Tls_examine_certificate(SSL *ssl, Server_t *srv,const char *host)
+{
+ X509 *remote_cert;
+ long st;
+ const uint_t buflen = 4096;
+ char buf[buflen], *cn, *msg;
+ int choice = -1, ret = -1;
+ char *title = dStrconcat("Dillo TLS security warning: ", host, NULL);
+
+ remote_cert = SSL_get_peer_certificate(ssl);
+ if (remote_cert == NULL){
+ /* Inform user that remote system cannot be trusted */
+ choice = a_Dialog_choice(title,
+ "The remote system is not presenting a certificate. "
+ "This site cannot be trusted. Sending data is not safe.",
+ "Continue", "Cancel", NULL);
+
+ /* Abort on anything but "Continue" */
+ if (choice == 1){
+ ret = 0;
+ }
+
+ } else if (Tls_check_cert_hostname(remote_cert, host, &choice)) {
+ /* Figure out if (and why) the remote system can't be trusted */
+ st = SSL_get_verify_result(ssl);
+ switch (st) {
+ case X509_V_OK:
+ ret = 0;
+ break;
+ case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
+ /* Either self signed and untrusted */
+ /* Extract CN from certificate name information */
+ if ((cn = strstr(remote_cert->name, "/CN=")) == NULL) {
+ strcpy(buf, "(no CN given)");
+ } else {
+ char *cn_end;
+
+ cn += 4;
+
+ if ((cn_end = strstr(cn, "/")) == NULL )
+ cn_end = cn + strlen(cn);
+
+ strncpy(buf, cn, (size_t) (cn_end - cn));
+ buf[cn_end - cn] = '\0';
+ }
+ msg = dStrconcat("The remote certificate is self-signed and "
+ "untrusted. For address: ", buf, NULL);
+ choice = a_Dialog_choice(title,
+ msg, "Continue", "Cancel", "Save Certificate", NULL);
+ dFree(msg);
+
+ switch (choice){
+ case 1:
+ ret = 0;
+ break;
+ case 2:
+ break;
+ case 3:
+ /* Save certificate to a file here and recheck the chain */
+ /* Potential security problems because we are writing
+ * to the filesystem */
+ Tls_save_certificate_home(remote_cert);
+ ret = 1;
+ break;
+ default:
+ break;
+ }
+ break;
+ case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
+ case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
+ choice = a_Dialog_choice(title,
+ "The issuer for the remote certificate cannot be found. "
+ "The authenticity of the remote certificate cannot be trusted.",
+ "Continue", "Cancel", NULL);
+
+ if (choice == 1) {
+ ret = 0;
+ }
+ break;
+
+ case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
+ case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:
+ case X509_V_ERR_CERT_SIGNATURE_FAILURE:
+ case X509_V_ERR_CRL_SIGNATURE_FAILURE:
+ choice = a_Dialog_choice(title,
+ "The remote certificate signature could not be read "
+ "or is invalid and should not be trusted",
+ "Continue", "Cancel", NULL);
+
+ if (choice == 1) {
+ ret = 0;
+ }
+ break;
+ case X509_V_ERR_CERT_NOT_YET_VALID:
+ case X509_V_ERR_CRL_NOT_YET_VALID:
+ choice = a_Dialog_choice(title,
+ "Part of the remote certificate is not yet valid. "
+ "Certificates usually have a range of dates over which "
+ "they are to be considered valid, and the certificate "
+ "presented has a starting validity after today's date "
+ "You should be cautious about using this site",
+ "Continue", "Cancel", NULL);
+
+ if (choice == 1) {
+ ret = 0;
+ }
+ break;
+ case X509_V_ERR_CERT_HAS_EXPIRED:
+ case X509_V_ERR_CRL_HAS_EXPIRED:
+ Tls_get_expiration_str(remote_cert, buf, buflen);
+ msg = dStrconcat("The remote certificate expired on: ", buf,
+ ". This site can no longer be trusted.", NULL);
+
+ choice = a_Dialog_choice(title, msg, "Continue", "Cancel", NULL);
+ if (choice == 1) {
+ ret = 0;
+ }
+ dFree(msg);
+ break;
+ case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
+ case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
+ case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:
+ case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:
+ choice = a_Dialog_choice(title,
+ "There was an error in the certificate presented. "
+ "Some of the certificate data was improperly formatted "
+ "making it impossible to determine if the certificate "
+ "is valid. You should not trust this certificate.",
+ "Continue", "Cancel", NULL);
+ if (choice == 1) {
+ ret = 0;
+ }
+ break;
+ case X509_V_ERR_INVALID_CA:
+ case X509_V_ERR_INVALID_PURPOSE:
+ case X509_V_ERR_CERT_UNTRUSTED:
+ case X509_V_ERR_CERT_REJECTED:
+ case X509_V_ERR_KEYUSAGE_NO_CERTSIGN:
+ choice = a_Dialog_choice(title,
+ "One of the certificates in the chain is being used "
+ "incorrectly (possibly due to configuration problems "
+ "with the remote system. The connection should not "
+ "be trusted",
+ "Continue", "Cancel", NULL);
+ if (choice == 1) {
+ ret = 0;
+ }
+ break;
+ case X509_V_ERR_SUBJECT_ISSUER_MISMATCH:
+ case X509_V_ERR_AKID_SKID_MISMATCH:
+ case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH:
+ choice = a_Dialog_choice(title,
+ "Some of the information presented by the remote system "
+ "does not match other information presented. "
+ "This may be an attempt to eavesdrop on communications",
+ "Continue", "Cancel", NULL);
+ if (choice == 1) {
+ ret = 0;
+ }
+ break;
+ case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
+ Tls_get_issuer_name(Tls_get_end_of_chain(ssl), buf, buflen);
+ msg = dStrconcat("Certificate chain led to a self-signed certificate "
+ "instead of a trusted root. Name: ", buf , NULL);
+ choice = a_Dialog_choice(title, msg, "Continue", "Cancel", NULL);
+ if (choice == 1) {
+ ret = 0;
+ }
+ dFree(msg);
+ break;
+ case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
+ Tls_get_issuer_name(Tls_get_end_of_chain(ssl), buf, buflen);
+ msg = dStrconcat("The issuer certificate of an untrusted certificate "
+ "cannot be found. Issuer: ", buf, NULL);
+ choice = a_Dialog_choice(title, msg, "Continue", "Cancel", NULL);
+ if (choice == 1) {
+ ret = 0;
+ }
+ dFree(msg);
+ break;
+ default: /* Need to add more options later */
+ snprintf(buf, 80,
+ "The remote certificate cannot be verified (code %ld)", st);
+ choice = a_Dialog_choice(title,
+ buf, "Continue", "Cancel", NULL);
+ /* abort on anything but "Continue" */
+ if (choice == 1){
+ ret = 0;
+ }
+ }
+ X509_free(remote_cert);
+ remote_cert = 0;
+ }
+ dFree(title);
+
+ if (choice == 2)
+ srv->cert_status = CERT_STATUS_BAD;
+ else if (choice == -1)
+ srv->cert_status = CERT_STATUS_CLEAN;
+ else
+ srv->cert_status = CERT_STATUS_USER_ACCEPTED;
+
+ return ret;
+}
+
+/*
+ * If the connection was closed before we got the certificate, we need to
+ * reset state so that we'll try again.
+ */
+void a_Tls_reset_server_state(const DilloUrl *url)
+{
+ if (servers) {
+ Server_t *s = dList_find_sorted(servers, url, Tls_servers_by_url_cmp);
+
+ if (s && s->cert_status == CERT_STATUS_RECEIVING)
+ s->cert_status = CERT_STATUS_NONE;
+ }
+}
+
+/*
+ * Close an open TLS connection.
+ */
+static void Tls_close_by_key(int connkey)
+{
+ Conn_t *c;
+
+ if ((c = a_Klist_get_data(conn_list, connkey))) {
+ a_Tls_reset_server_state(c->url);
+ if (c->connecting) {
+ a_IOwatch_remove_fd(c->fd, -1);
+ dClose(c->fd);
+ }
+ SSL_shutdown(c->ssl);
+ SSL_free(c->ssl);
+
+ a_Url_free(c->url);
+ Tls_fd_map_remove_entry(c->fd);
+ a_Klist_remove(conn_list, connkey);
+ dFree(c);
+ }
+}
+
+static void Tls_print_cert_chain(SSL *ssl)
+{
+ STACK_OF(X509) *sk = SSL_get_peer_cert_chain(ssl);
+
+ if (sk) {
+ const uint_t buflen = 4096;
+ char buf[buflen];
+ int rc, i, n = sk_X509_num(sk);
+ X509 *cert = NULL;
+ EVP_PKEY *public_key;
+ int key_type, key_bits;
+ const char *type_str;
+ BIO *b;
+
+ for (i = 0; i < n; i++) {
+ cert = sk_X509_value(sk, i);
+ public_key = X509_get_pubkey(cert);
+
+ /* We are trying to find a way to get the hash function used
+ * with a certificate. This way, which is not very pleasant, puts
+ * a string such as "sha256WithRSAEncryption" in our buffer and we
+ * then trim off the "With..." part.
+ */
+ b = BIO_new(BIO_s_mem());
+ rc = i2a_ASN1_OBJECT(b, cert->sig_alg->algorithm);
+
+ if (rc > 0) {
+ rc = BIO_gets(b, buf, buflen);
+ }
+ if (rc <= 0) {
+ strcpy(buf, "(unknown)");
+ buf[buflen-1] = '\0';
+ } else {
+ char *s = strstr(buf, "With");
+
+ if (s) {
+ *s = '\0';
+ if (!strcmp(buf, "sha1")) {
+ MSG_WARN("In 2015, browsers have begun to deprecate SHA1 "
+ "certificates.\n");
+ } else if (!strncmp(buf, "md", 2)) {
+ MSG_ERR("Browsers stopped accepting MD5 certificates around "
+ "2012.\n");
+ }
+ }
+ }
+ BIO_free(b);
+ MSG("%s ", buf);
+
+
+ key_type = EVP_PKEY_type(public_key->type);
+ type_str = key_type == EVP_PKEY_RSA ? "RSA" :
+ key_type == EVP_PKEY_DSA ? "DSA" :
+ key_type == EVP_PKEY_DH ? "DH" :
+ key_type == EVP_PKEY_EC ? "EC" : "???";
+ key_bits = EVP_PKEY_bits(public_key);
+ X509_NAME_oneline(X509_get_subject_name(cert), buf, buflen);
+ buf[buflen-1] = '\0';
+ MSG("%d-bit %s: %s\n", key_bits, type_str, buf);
+ EVP_PKEY_free(public_key);
+
+ if (key_type == EVP_PKEY_RSA && key_bits <= 1024) {
+ /* TODO: Gather warnings into one popup. */
+ MSG_WARN("In 2014/5, browsers have been deprecating 1024-bit RSA "
+ "keys.\n");
+ }
+ }
+
+ if (cert) {
+ X509_NAME_oneline(X509_get_issuer_name(cert), buf, buflen);
+ buf[buflen-1] = '\0';
+ MSG("root: %s\n", buf);
+ }
+ }
+}
+
+/*
+ * Connect, set a callback if it's still not completed. If completed, check
+ * the certificate and report back to http.
+ */
+static void Tls_connect(int fd, int connkey)
+{
+ int ret;
+ bool_t ongoing = FALSE, failed = TRUE;
+ Conn_t *conn;
+
+ if (!(conn = a_Klist_get_data(conn_list, connkey))) {
+ MSG("Tls_connect: conn for fd %d not valid\n", fd);
+ return;
+ }
+
+ assert(!ERR_get_error());
+
+ ret = SSL_connect(conn->ssl);
+
+ if (ret <= 0) {
+ int err1_ret = SSL_get_error(conn->ssl, ret);
+ if (err1_ret == SSL_ERROR_WANT_READ ||
+ err1_ret == SSL_ERROR_WANT_WRITE) {
+ int want = err1_ret == SSL_ERROR_WANT_READ ? DIO_READ : DIO_WRITE;
+
+ _MSG("iowatching fd %d for tls -- want %s\n", fd,
+ err1_ret == SSL_ERROR_WANT_READ ? "read" : "write");
+ a_IOwatch_remove_fd(fd, -1);
+ a_IOwatch_add_fd(fd, want, Tls_connect_cb, INT2VOIDP(connkey));
+ ongoing = TRUE;
+ failed = FALSE;
+ } else if (err1_ret == SSL_ERROR_SYSCALL || err1_ret == SSL_ERROR_SSL) {
+ unsigned long err2_ret = ERR_get_error();
+
+ if (err2_ret) {
+ do {
+ MSG("SSL_connect() failed: %s\n",
+ ERR_error_string(err2_ret, NULL));
+ } while ((err2_ret = ERR_get_error()));
+ } else {
+ /* nothing in the error queue */
+ if (ret == 0) {
+ MSG("TLS connect error: \"an EOF was observed that violates "
+ "the protocol\"\n");
+ /*
+ * I presume we took too long on our side and the server grew
+ * impatient.
+ */
+ } else if (ret == -1) {
+ MSG("TLS connect error: %s\n", dStrerror(errno));
+
+ /* If the following can happen, I'll add code to handle it, but
+ * I don't want to add code blindly if it isn't getting used
+ */
+ assert(errno != EAGAIN && errno != EINTR);
+ } else {
+ MSG_ERR("According to the man page for SSL_get_error(), this "
+ "was not a possibility (ret %d).\n", ret);
+ }
+ }
+ } else {
+ MSG("SSL_get_error() returned %d on a connect.\n", err1_ret);
+ }
+ } else {
+ Server_t *srv = dList_find_sorted(servers, conn->url,
+ Tls_servers_by_url_cmp);
+
+ if (srv->cert_status == CERT_STATUS_RECEIVING) {
+ /* Making first connection with the server. Show some information. */
+ SSL *ssl = conn->ssl;
+ const char *version = SSL_get_version(ssl);
+ const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl);
+
+ MSG("%s: %s, cipher %s\n", URL_AUTHORITY(conn->url), version,
+ SSL_CIPHER_get_name(cipher));
+ Tls_print_cert_chain(ssl);
+ }
+
+ if (srv->cert_status == CERT_STATUS_USER_ACCEPTED ||
+ (Tls_examine_certificate(conn->ssl, srv, URL_HOST(conn->url))!=-1)) {
+ failed = FALSE;
+ }
+ }
+
+ /*
+ * If there were problems with the certificate, the connection may have
+ * been closed by the server if the user responded too slowly to a popup.
+ */
+
+ if (!ongoing) {
+ if (a_Klist_get_data(conn_list, connkey)) {
+ conn->connecting = FALSE;
+ if (failed) {
+ Tls_close_by_key(connkey);
+ }
+ a_IOwatch_remove_fd(fd, DIO_READ|DIO_WRITE);
+ a_Http_connect_done(fd, failed ? FALSE : TRUE);
+ } else {
+ MSG("Connection disappeared. Too long with a popup popped up?\n");
+ }
+ }
+}
+
+static void Tls_connect_cb(int fd, void *vconnkey)
+{
+ Tls_connect(fd, VOIDP2INT(vconnkey));
+}
+
+/*
+ * Perform the TLS handshake on an open socket.
+ */
+void a_Tls_handshake(int fd, const DilloUrl *url)
+{
+ SSL *ssl;
+ bool_t success = TRUE;
+ int connkey = -1;
+
+ if (!ssl_context)
+ success = FALSE;
+
+ if (success && Tls_user_said_no(url)) {
+ success = FALSE;
+ }
+
+ assert(!ERR_get_error());
+
+ if (success && !(ssl = SSL_new(ssl_context))) {
+ unsigned long err_ret = ERR_get_error();
+ do {
+ MSG("SSL_new() failed: %s\n", ERR_error_string(err_ret, NULL));
+ } while ((err_ret = ERR_get_error()));
+ success = FALSE;
+ }
+
+ /* assign TLS connection to this file descriptor */
+ if (success && !SSL_set_fd(ssl, fd)) {
+ unsigned long err_ret = ERR_get_error();
+ do {
+ MSG("SSL_set_fd() failed: %s\n", ERR_error_string(err_ret, NULL));
+ } while ((err_ret = ERR_get_error()));
+ success = FALSE;
+ }
+
+ if (success)
+ connkey = Tls_conn_new(fd, url, ssl);
+
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+ /* Server Name Indication. From the openssl changelog, it looks like this
+ * came along in 2010.
+ */
+ if (success && !a_Url_host_is_ip(URL_HOST(url)))
+ SSL_set_tlsext_host_name(ssl, URL_HOST(url));
+#endif
+
+ if (!success) {
+ a_Tls_reset_server_state(url);
+ a_Http_connect_done(fd, success);
+ } else {
+ Tls_connect(fd, connkey);
+ }
+}
+
+/*
+ * Read data from an open TLS connection.
+ */
+int a_Tls_read(void *conn, void *buf, size_t len)
+{
+ Conn_t *c = (Conn_t*)conn;
+ return SSL_read(c->ssl, buf, len);
+}
+
+/*
+ * Write data to an open TLS connection.
+ */
+int a_Tls_write(void *conn, void *buf, size_t len)
+{
+ Conn_t *c = (Conn_t*)conn;
+ return SSL_write(c->ssl, buf, len);
+}
+
+void a_Tls_close_by_fd(int fd)
+{
+ FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd),
+ Tls_fd_map_cmp);
+
+ if (fme) {
+ Tls_close_by_key(fme->connkey);
+ }
+}
+
+static void Tls_servers_freeall()
+{
+ if (servers) {
+ Server_t *s;
+ int i, n = dList_length(servers);
+
+ for (i = 0; i < n; i++) {
+ s = (Server_t *) dList_nth_data(servers, i);
+ dFree(s->hostname);
+ dFree(s);
+ }
+ dList_free(servers);
+ }
+}
+
+static void Tls_fd_map_remove_all()
+{
+ if (fd_map) {
+ 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);
+ }
+}
+
+/*
+ * Clean up the OpenSSL library
+ */
+void a_Tls_freeall(void)
+{
+ if (ssl_context)
+ SSL_CTX_free(ssl_context);
+ Tls_fd_map_remove_all();
+ Tls_servers_freeall();
+}
+
+#endif /* ENABLE_SSL */
diff --git a/src/IO/tls.h b/src/IO/tls.h
new file mode 100644
index 00000000..9bc89de5
--- /dev/null
+++ b/src/IO/tls.h
@@ -0,0 +1,49 @@
+#ifndef __TLS_H__
+#define __TLS_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "../url.h"
+
+#define TLS_CONNECT_NEVER -1
+#define TLS_CONNECT_NOT_YET 0
+#define TLS_CONNECT_READY 1
+
+void a_Tls_init();
+
+
+#ifdef ENABLE_SSL
+int a_Tls_certificate_is_clean(const DilloUrl *url);
+int a_Tls_connect_ready(const DilloUrl *url);
+void a_Tls_reset_server_state(const DilloUrl *url);
+
+/* Use to initiate a TLS connection. */
+void a_Tls_handshake(int fd, const DilloUrl *url);
+
+void *a_Tls_connection(int fd);
+
+void a_Tls_freeall();
+
+void a_Tls_close_by_fd(int fd);
+int a_Tls_read(void *conn, void *buf, size_t len);
+int a_Tls_write(void *conn, void *buf, size_t len);
+#else
+
+#define a_Tls_certificate_is_clean(host) 0
+#define a_Tls_connect_ready(url) TLS_CONNECT_NEVER
+#define a_Tls_reset_server_state(url) ;
+#define a_Tls_handshake(fd, url) ;
+#define a_Tls_connection(fd) NULL
+#define a_Tls_freeall() ;
+#define a_Tls_close_by_fd(fd) ;
+#define a_Tls_read(conn, buf, len) 0
+#define a_Tls_write(conn, buf, len) 0
+#endif
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __TLS_H__ */
+
diff --git a/src/Makefile.am b/src/Makefile.am
index 65a42cad..425f8614 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@
@@ -19,7 +21,7 @@ dillo_LDADD = \
$(top_builddir)/dw/libDw-core.a \
$(top_builddir)/lout/liblout.a \
@LIBJPEG_LIBS@ @LIBPNG_LIBS@ @LIBFLTK_LIBS@ @LIBZ_LIBS@ \
- @LIBICONV_LIBS@ @LIBPTHREAD_LIBS@ @LIBX11_LIBS@
+ @LIBICONV_LIBS@ @LIBPTHREAD_LIBS@ @LIBX11_LIBS@ @LIBSSL_LIBS@
dillo_SOURCES = \
dillo.cc \
@@ -35,6 +37,8 @@ dillo_SOURCES = \
bw.c \
cookies.c \
cookies.h \
+ hsts.c \
+ hsts.h \
auth.c \
auth.h \
md5.c \
@@ -96,6 +100,7 @@ dillo_SOURCES = \
plain.cc \
html.cc \
html.hh \
+ html_charrefs.h \
html_common.hh \
form.cc \
form.hh \
@@ -125,5 +130,5 @@ dillo_SOURCES = \
xembed.cc \
xembed.hh
-dist_sysconf_DATA = domainrc keysrc
+dist_sysconf_DATA = domainrc keysrc hsts_preload
EXTRA_DIST = chg srch
diff --git a/src/cache.c b/src/cache.c
index a2ad357b..b082ef89 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -26,6 +26,7 @@
#include "dicache.h"
#include "nav.h"
#include "cookies.h"
+#include "hsts.h"
#include "misc.h"
#include "capi.h"
#include "decode.h"
@@ -55,7 +56,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)*/
@@ -205,7 +206,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 +309,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 +499,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 +653,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, *hsts;
#ifndef DISABLE_COOKIES
Dlist *Cookies;
#endif
@@ -709,6 +711,25 @@ 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);
+ }
+
+ if (prefs.http_strict_transport_security &&
+ !dStrAsciiCasecmp(URL_SCHEME(entry->Url), "https") &&
+ !a_Url_host_is_ip(URL_HOST(entry->Url)) &&
+ (hsts = Cache_parse_field(header, "Strict-Transport-Security"))) {
+ a_Hsts_set(hsts, entry->Url);
+ dFree(hsts);
+ }
+
/*
* Get Transfer-Encoding and initialize decoder
*/
@@ -824,6 +845,43 @@ 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);
+ }
+ 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.
@@ -832,16 +890,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");
@@ -865,7 +924,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;
}
@@ -886,51 +946,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;
}
/*
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);
diff --git a/src/capi.c b/src/capi.c
index 85aef974..8c4a1ae2 100644
--- a/src/capi.c
+++ b/src/capi.c
@@ -16,7 +16,9 @@
*/
#include <string.h>
+#include <errno.h>
+#include "config.h"
#include "msg.h"
#include "capi.h"
#include "IO/IO.h" /* for IORead &friends */
@@ -268,6 +270,7 @@ static int Capi_url_uses_dpi(DilloUrl *url, char **server_ptr)
Dstr *tmp;
if ((dStrnAsciiCasecmp(url_str, "http:", 5) == 0) ||
+ (dStrnAsciiCasecmp(url_str, "https:", 6) == 0) ||
(dStrnAsciiCasecmp(url_str, "about:", 6) == 0)) {
/* URL doesn't use dpi (server = NULL) */
} else if (dStrnAsciiCasecmp(url_str, "dpi:/", 5) == 0) {
@@ -298,39 +301,7 @@ static char *Capi_dpi_build_cmd(DilloWeb *web, char *server)
{
char *cmd;
- if (strcmp(server, "proto.https") == 0) {
- /* Let's be kind and make the HTTP query string for the dpi */
- char *proxy_connect = a_Http_make_connect_str(web->url);
- Dstr *http_query = a_Http_make_query_str(web->url, web->requester,
- web->flags, FALSE);
-
- if ((uint_t) http_query->len > strlen(http_query->str)) {
- /* Can't handle NULLs embedded in query data */
- MSG_ERR("HTTPS query truncated!\n");
- }
-
- /* BUG: WORKAROUND: request to only check the root URL's certificate.
- * This avoids the dialog bombing that stems from loading multiple
- * https images/resources in a single page. A proper fix would take
- * either to implement the https-dpi as a server (with state),
- * or to move back https handling into dillo. */
- if (proxy_connect) {
- const char *proxy_urlstr = a_Http_get_proxy_urlstr();
- cmd = a_Dpip_build_cmd("cmd=%s proxy_url=%s proxy_connect=%s "
- "url=%s query=%s check_cert=%s",
- "open_url", proxy_urlstr,
- proxy_connect, URL_STR(web->url),
- http_query->str,
- (web->flags & WEB_RootUrl) ? "true" : "false");
- } else {
- cmd = a_Dpip_build_cmd("cmd=%s url=%s query=%s check_cert=%s",
- "open_url", URL_STR(web->url),http_query->str,
- (web->flags & WEB_RootUrl) ? "true" : "false");
- }
- dFree(proxy_connect);
- dStr_free(http_query, 1);
-
- } else if (strcmp(server, "downloads") == 0) {
+ if (strcmp(server, "downloads") == 0) {
/* let the downloads server get it */
cmd = a_Dpip_build_cmd("cmd=%s url=%s destination=%s",
"download", URL_STR(web->url), web->filename);
@@ -345,7 +316,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 +356,92 @@ 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: %s.\n",
+ web->filename, dStrerror(errno));
+ }
}
+ } 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") ||
+ !dStrAsciiCasecmp(scheme, "https")) {
+ /* http request */
+
+#ifndef ENABLE_SSL
+ if (!dStrAsciiCasecmp(scheme, "https")) {
+ if (web->flags & WEB_RootUrl)
+ a_UIcmd_set_msg(web->bw,
+ "HTTPS was disabled at compilation time");
+ a_Web_free(web);
+ return 0;
+ }
+#endif
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) {
@@ -632,7 +614,8 @@ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info,
Capi_conn_ref(conn);
Info->LocalKey = conn;
conn->InfoSend = Info;
- if (strcmp(conn->server, "http") == 0) {
+ if (strcmp(conn->server, "http") == 0 ||
+ strcmp(conn->server, "https") == 0) {
a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Http_ccc, 1, 1);
a_Chain_bcb(OpStart, Info, Data2, NULL);
} else {
@@ -659,7 +642,7 @@ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info,
dFree(Info);
break;
default:
- MSG_WARN("Unused CCC\n");
+ MSG_WARN("Unused CCC Capi 1B\n");
break;
}
} else { /* 1 FWD */
@@ -681,6 +664,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,
@@ -699,7 +683,7 @@ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info,
dFree(Info);
break;
default:
- MSG_WARN("Unused CCC\n");
+ MSG_WARN("Unused CCC Capi 1F\n");
break;
}
}
@@ -714,7 +698,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:
@@ -733,7 +720,7 @@ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info,
dFree(Info);
break;
default:
- MSG_WARN("Unused CCC\n");
+ MSG_WARN("Unused CCC Capi 2B\n");
break;
}
} else { /* 2 FWD */
@@ -744,7 +731,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) {
@@ -775,8 +770,24 @@ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info,
Capi_conn_unref(conn);
dFree(Info);
break;
+ case OpAbort:
+ conn = Info->LocalKey;
+ conn->InfoRecv = NULL;
+ a_Cache_process_dbuf(IOAbort, NULL, 0, conn->url);
+ if (Data2) {
+ if (!strcmp(Data2, "Both") && conn->InfoSend) {
+ /* abort the other branch too */
+ a_Capi_ccc(OpAbort, 1, BCK, conn->InfoSend, NULL, NULL);
+ }
+ }
+ /* if URL == expect-url */
+ a_Nav_cancel_expect_if_eq(conn->bw, conn->url);
+ /* finish conn */
+ Capi_conn_unref(conn);
+ dFree(Info);
+ break;
default:
- MSG_WARN("Unused CCC\n");
+ MSG_WARN("Unused CCC Capi 2F\n");
break;
}
}
diff --git a/src/colors.c b/src/colors.c
index d4cc60c5..3e194339 100644
--- a/src/colors.c
+++ b/src/colors.c
@@ -310,7 +310,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
diff --git a/src/css.cc b/src/css.cc
index 5bdf4fdb..5c64c619 100644
--- a/src/css.cc
+++ b/src/css.cc
@@ -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);
}
}
diff --git a/src/css.hh b/src/css.hh
index 22e7e700..c2a28770 100644
--- a/src/css.hh
+++ b/src/css.hh
@@ -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/dialog.cc b/src/dialog.cc
index 2c2781b1..03949a1c 100644
--- a/src/dialog.cc
+++ b/src/dialog.cc
@@ -325,6 +325,7 @@ static void choice_cb(Fl_Widget *button, void *number)
{
choice_answer = VOIDP2INT(number);
_MSG("choice_cb: %d\n", choice_answer);
+
button->window()->hide();
}
@@ -354,31 +355,19 @@ int a_Dialog_choice(const char *title, const char *msg, ...)
int gap = 8;
int ww = 140 + n * 60, wh = 120;
int bw = (ww - gap) / n - gap, bh = 45;
- int ih = 50;
Fl_Window *window = new Fl_Window(ww, wh, title);
window->set_modal();
window->begin();
- Fl_Group *ib = new Fl_Group(0, 0, window->w(), window->h());
- ib->begin();
- window->resizable(ib);
-
- /* '?' Icon */
- Fl_Box *o = new Fl_Box(10, (wh - bh - ih) / 2, ih, ih);
- o->box(FL_THIN_UP_BOX);
- o->labelfont(FL_TIMES_BOLD);
- o->labelsize(34);
- o->color(FL_WHITE);
- o->labelcolor(FL_BLUE);
- o->label("?");
- o->show();
- if (msg != NULL){
- Fl_Box *box = new Fl_Box(60, 0, ww - 60, wh - bh, msg);
- box->labelfont(FL_HELVETICA);
- box->labelsize(14);
- box->align(FL_ALIGN_WRAP);
- }
+ Fl_Text_Buffer *buf = new Fl_Text_Buffer();
+ buf->text(msg);
+ Fl_Text_Display *td = new Fl_Text_Display(0, 0, ww, wh - bh);
+ td->buffer(buf);
+ td->textsize((int) rint(14.0 * prefs.font_factor));
+ td->wrap_mode(Fl_Text_Display::WRAP_AT_BOUNDS, 0);
+
+ window->resizable(td);
int xpos = gap;
va_start(ap, msg);
@@ -397,6 +386,8 @@ int a_Dialog_choice(const char *title, const char *msg, ...)
while (window->shown())
Fl::wait();
_MSG("Dialog_choice answer = %d\n", answer);
+ td->buffer(NULL);
+ delete buf;
delete window;
return choice_answer;
diff --git a/src/dillo.cc b/src/dillo.cc
index 20b2796e..24271103 100644
--- a/src/dillo.cc
+++ b/src/dillo.cc
@@ -45,18 +45,22 @@
#include "dns.h"
#include "web.hh"
+#include "IO/tls.h"
#include "IO/Url.h"
#include "IO/mime.h"
#include "capi.h"
#include "dicache.h"
#include "cookies.h"
+#include "hsts.h"
#include "domain.h"
#include "auth.h"
#include "styleengine.hh"
#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 +381,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;
@@ -466,15 +478,19 @@ int main(int argc, char **argv)
a_Dns_init();
a_Web_init();
a_Http_init();
+ a_Tls_init();
a_Mime_init();
a_Capi_init();
a_Dicache_init();
a_Bw_init();
a_Cookies_init();
+ a_Hsts_init(Paths::getPrefsFP(PATHS_HSTS_PRELOAD));
a_Auth_init();
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);
@@ -582,9 +598,11 @@ int main(int argc, char **argv)
*/
a_Domain_freeall();
a_Cookies_freeall();
+ a_Hsts_freeall();
a_Cache_freeall();
a_Dicache_freeall();
a_Http_freeall();
+ a_Tls_freeall();
a_Dns_freeall();
a_History_freeall();
a_Prefs_freeall();
diff --git a/src/form.cc b/src/form.cc
index 3316d313..92ee3a42 100644
--- a/src/form.cc
+++ b/src/form.cc
@@ -658,7 +658,7 @@ void Html_tag_content_textarea(DilloHtml *html, const char *tag, int tagsize)
} else {
if (html->DocType != DT_HTML || html->DocTypeVersion <= 4.01f)
BUG_MSG("<textarea> requires rows attribute.");
- rows = 10;
+ rows = 2;
}
if (rows < 1 || rows > MAX_ROWS) {
int badRows = rows;
@@ -951,9 +951,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;
diff --git a/src/gif.c b/src/gif.c
index 34424d33..adc9f49d 100644
--- a/src/gif.c
+++ b/src/gif.c
@@ -800,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 ||
diff --git a/src/hsts.c b/src/hsts.c
new file mode 100644
index 00000000..ecbd9765
--- /dev/null
+++ b/src/hsts.c
@@ -0,0 +1,364 @@
+/*
+ * File: hsts.c
+ * HTTP Strict Transport Security
+ *
+ * Copyright 2015 corvid
+ *
+ * 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.
+ *
+ */
+
+/* To preload hosts, as of 2015, chromium is the list keeper:
+ * https://src.chromium.org/viewvc/chrome/trunk/src/net/http/transport_security_state_static.json
+ * although mozilla's is easier to work from (and they trim it based on
+ * criteria such as max-age must be at least some number of months)
+ * https://mxr.mozilla.org/mozilla-central/source/security/manager/ssl/nsSTSPreloadList.inc?raw=1
+ */
+
+#include <time.h>
+#include <errno.h>
+#include <limits.h> /* for INT_MAX */
+#include <ctype.h> /* for isspace */
+#include <stdlib.h> /* for strtol */
+
+#include "hsts.h"
+#include "msg.h"
+#include "../dlib/dlib.h"
+#include "IO/tls.h"
+
+typedef struct {
+ char *host;
+ time_t expires_at;
+ bool_t subdomains;
+} HstsData_t;
+
+/* When there is difficulty in representing future dates, use the (by far)
+ * most likely latest representable time of January 19, 2038.
+ */
+static time_t hsts_latest_representable_time;
+static Dlist *domains;
+
+static void Hsts_free_policy(HstsData_t *p)
+{
+ dFree(p->host);
+ dFree(p);
+}
+
+void a_Hsts_freeall()
+{
+ if (prefs.http_strict_transport_security) {
+ HstsData_t *policy;
+ int i, n = dList_length(domains);
+
+ for (i = 0; i < n; i++) {
+ policy = dList_nth_data(domains, i);
+ Hsts_free_policy(policy);
+ }
+ dList_free(domains);
+ }
+}
+
+/*
+ * Compare function for searching a domain node by domain string
+ */
+static int Domain_node_domain_str_cmp(const void *v1, const void *v2)
+{
+ const HstsData_t *node = v1;
+ const char *host = v2;
+
+ return dStrAsciiCasecmp(node->host, host);
+}
+
+static HstsData_t *Hsts_get_policy(const char *host)
+{
+ return dList_find_sorted(domains, host, Domain_node_domain_str_cmp);
+}
+
+static void Hsts_remove_policy(HstsData_t *policy)
+{
+ if (policy) {
+ _MSG("HSTS: removed policy for %s\n", policy->host);
+ Hsts_free_policy(policy);
+ dList_remove(domains, policy);
+ }
+}
+
+/*
+ * Return the time_t for a future time.
+ */
+static time_t Hsts_future_time(long seconds_from_now)
+{
+ time_t ret, now = time(NULL);
+ struct tm *tm = gmtime(&now);
+
+ if (seconds_from_now > INT_MAX - tm->tm_sec)
+ tm->tm_sec = INT_MAX;
+ else
+ tm->tm_sec += seconds_from_now;
+
+ ret = mktime(tm);
+ if (ret == (time_t) -1)
+ ret = hsts_latest_representable_time;
+
+ return ret;
+}
+
+/*
+ * Compare function for searching domains.
+ */
+static int Domain_node_cmp(const void *v1, const void *v2)
+{
+ const HstsData_t *node1 = v1, *node2 = v2;
+
+ return dStrAsciiCasecmp(node1->host, node2->host);
+}
+
+static void Hsts_set_policy(const char *host, long max_age, bool_t subdomains)
+{
+ time_t exp = Hsts_future_time(max_age);
+ HstsData_t *policy = Hsts_get_policy(host);
+
+ _MSG("HSTS: %s %s%s: until %s", (policy ? "modify" : "add"), host,
+ (subdomains ? " and subdomains" : ""), ctime(&exp));
+
+ if (policy == NULL) {
+ policy = dNew0(HstsData_t, 1);
+ policy->host = dStrdup(host);
+ dList_insert_sorted(domains, policy, Domain_node_cmp);
+ }
+ policy->subdomains = subdomains;
+ policy->expires_at = exp;
+}
+
+/*
+ * Read the next attribute.
+ */
+static char *Hsts_parse_attr(const char **header_str)
+{
+ const char *str;
+ uint_t len;
+
+ while (dIsspace(**header_str))
+ (*header_str)++;
+
+ str = *header_str;
+ /* find '=' at end of attr, ';' after attr/val pair, '\0' end of string */
+ len = strcspn(str, "=;");
+ *header_str += len;
+
+ while (len && (str[len - 1] == ' ' || str[len - 1] == '\t'))
+ len--;
+ return dStrndup(str, len);
+}
+
+/*
+ * Get the value in *header_str.
+ */
+static char *Hsts_parse_value(const char **header_str)
+{
+ uint_t len;
+ const char *str;
+
+ if (**header_str == '=') {
+ (*header_str)++;
+ while (dIsspace(**header_str))
+ (*header_str)++;
+
+ str = *header_str;
+ /* finds ';' after attr/val pair or '\0' at end of string */
+ len = strcspn(str, ";");
+ *header_str += len;
+
+ while (len && (str[len - 1] == ' ' || str[len - 1] == '\t'))
+ len--;
+ } else {
+ str = *header_str;
+ len = 0;
+ }
+ return dStrndup(str, len);
+}
+
+/*
+ * Advance past any value.
+ */
+static void Hsts_eat_value(const char **str)
+{
+ if (**str == '=')
+ *str += strcspn(*str, ";");
+}
+
+/*
+ * The reponse for this url had an HSTS header, so let's take action.
+ */
+void a_Hsts_set(const char *header, const DilloUrl *url)
+{
+ long max_age;
+ const char *host = URL_HOST(url);
+ bool_t max_age_valid = FALSE, subdomains = FALSE;
+
+ _MSG("HSTS header for %s: %s\n", host, header);
+
+ if (!a_Tls_certificate_is_clean(url)) {
+ /* RFC 6797 gives rationale in section 14.3. */
+ _MSG("But there were certificate warnings, so ignore it (!)\n");
+ return;
+ }
+
+ /* Iterate until there is nothing left of the string */
+ while (*header) {
+ char *attr;
+ char *value;
+
+ /* Get attribute */
+ attr = Hsts_parse_attr(&header);
+
+ /* Get the value for the attribute and store it */
+ if (dStrAsciiCasecmp(attr, "max-age") == 0) {
+ value = Hsts_parse_value(&header);
+ if (isdigit(*value)) {
+ errno = 0;
+ max_age = strtol(value, NULL, 10);
+ if (errno == ERANGE)
+ max_age = INT_MAX;
+ max_age_valid = TRUE;
+ }
+ dFree(value);
+ } else if (dStrAsciiCasecmp(attr, "includeSubDomains") == 0) {
+ subdomains = TRUE;
+ Hsts_eat_value(&header);
+ } else if (dStrAsciiCasecmp(attr, "preload") == 0) {
+ /* 'preload' is not part of the RFC, but what does google care for
+ * standards? They require that 'preload' be specified by a domain
+ * in order to be added to their preload list.
+ */
+ } else {
+ MSG("HSTS: header contains unknown attribute: '%s'\n", attr);
+ Hsts_eat_value(&header);
+ }
+
+ dFree(attr);
+
+ if (*header == ';')
+ header++;
+ }
+ if (max_age_valid) {
+ if (max_age > 0)
+ Hsts_set_policy(host, max_age, subdomains);
+ else
+ Hsts_remove_policy(Hsts_get_policy(host));
+ }
+}
+
+static bool_t Hsts_expired(HstsData_t *policy)
+{
+ time_t now = time(NULL);
+ bool_t ret = (now > policy->expires_at) ? TRUE : FALSE;
+
+ if (ret) {
+ _MSG("HSTS: expired\n");
+ }
+ return ret;
+}
+
+bool_t a_Hsts_require_https(const char *host)
+{
+ bool_t ret = FALSE;
+
+ if (host) {
+ HstsData_t *policy = Hsts_get_policy(host);
+
+ if (policy) {
+ _MSG("HSTS: matched host %s\n", host);
+ if (Hsts_expired(policy))
+ Hsts_remove_policy(policy);
+ else
+ ret = TRUE;
+ }
+ if (!ret) {
+ const char *domain_str;
+
+ for (domain_str = strchr(host+1, '.');
+ domain_str != NULL && *domain_str;
+ domain_str = strchr(domain_str+1, '.')) {
+ policy = Hsts_get_policy(domain_str+1);
+
+ if (policy && policy->subdomains) {
+ _MSG("HSTS: matched %s under %s subdomain rule\n", host,
+ policy->host);
+ if (Hsts_expired(policy)) {
+ Hsts_remove_policy(policy);
+ } else {
+ ret = TRUE;
+ break;
+ }
+ }
+ }
+ }
+ }
+ return ret;
+}
+
+static void Hsts_preload(FILE *stream)
+{
+ const int LINE_MAXLEN = 4096;
+ const long ONE_YEAR = 60 * 60 * 24 * 365;
+
+ char *rc, *subdomains;
+ char line[LINE_MAXLEN];
+ char domain[LINE_MAXLEN];
+
+ /* Get all lines in the file */
+ while (!feof(stream)) {
+ line[0] = '\0';
+ rc = fgets(line, LINE_MAXLEN, stream);
+ if (!rc && ferror(stream)) {
+ MSG_WARN("HSTS: Error while reading preload entries: %s\n",
+ dStrerror(errno));
+ return; /* bail out */
+ }
+
+ /* Remove leading and trailing whitespace */
+ dStrstrip(line);
+
+ if (line[0] != '\0' && line[0] != '#') {
+ int i = 0, j = 0;
+
+ /* Get the domain */
+ while (line[i] != '\0' && !dIsspace(line[i]))
+ domain[j++] = line[i++];
+ domain[j] = '\0';
+
+ /* Skip past whitespace */
+ while (dIsspace(line[i]))
+ i++;
+
+ subdomains = line + i;
+
+ if (dStrAsciiCasecmp(subdomains, "true") == 0)
+ Hsts_set_policy(domain, ONE_YEAR, TRUE);
+ else if (dStrAsciiCasecmp(subdomains, "false") == 0)
+ Hsts_set_policy(domain, ONE_YEAR, FALSE);
+ else {
+ MSG_WARN("HSTS: format of line not recognized. Ignoring '%s'.\n",
+ line);
+ }
+ }
+ }
+}
+
+void a_Hsts_init(FILE *preload_file)
+{
+ if (prefs.http_strict_transport_security) {
+ struct tm future_tm = {7, 14, 3, 19, 0, 138, 0, 0, 0, 0, 0};
+
+ hsts_latest_representable_time = mktime(&future_tm);
+ domains = dList_new(32);
+
+ if (preload_file)
+ Hsts_preload(preload_file);
+ }
+}
+
diff --git a/src/hsts.h b/src/hsts.h
new file mode 100644
index 00000000..693aec10
--- /dev/null
+++ b/src/hsts.h
@@ -0,0 +1,19 @@
+#ifndef __HSTS_H__
+#define __HSTS_H__
+
+#include "d_size.h"
+#include "url.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+void a_Hsts_init(FILE *fp);
+void a_Hsts_set(const char *header, const DilloUrl *url);
+bool_t a_Hsts_require_https(const char *host);
+void a_Hsts_freeall( void );
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif /* !__HSTS_H__ */
diff --git a/src/hsts_preload b/src/hsts_preload
new file mode 100755
index 00000000..22e3aa3c
--- /dev/null
+++ b/src/hsts_preload
@@ -0,0 +1,2037 @@
+# This HTTP Strict Transport Security preload file was created on 2015-06-28
+# from the list in
+# https://mxr.mozilla.org/mozilla-central/source/security/manager/ssl/nsSTSPreloadList.inc
+# Format: domain include_subdomains
+
+007sascha.de true
+0x0a.net true
+1000minds.com true
+17hats.com true
+188trafalgar.ca true
+18f.gsa.gov true
+1a-diamantscheiben.de true
+1a-jva.de true
+1a-vermessung.at true
+1a-werkstattgeraete.de true
+2048game.co.uk true
+2600hq.com true
+301.website true
+302.nyc true
+314chan.org true
+3do3dont.com true
+47ronin.com false
+4g-server.eu true
+4sqsu.eu true
+5apps.com false
+7183.org true
+8ack.de true
+9point6.com true
+abecodes.net false
+abiapp.net true
+abmahnhelfer.de true
+accounts.firefox.com true
+accounts.google.com true
+aclu.org false
+acuica.co.uk false
+acus.gov true
+adambyers.com true
+adamkostecki.de true
+adamstas.com true
+addvocate.com true
+adlershop.ch true
+admin.fedoraproject.org true
+admin.google.com true
+admin.stg.fedoraproject.org true
+adorai.tk true
+adsfund.org true
+advanced-online.eu true
+aerolog.co true
+aes256.ru true
+aeyoun.com true
+afp548.com true
+afrodigital.uk true
+agrios.de true
+ahoyconference.com true
+ahwatukeefoothillsmontessori.com true
+aids.gov true
+aie.de true
+aircomms.com true
+airlea.com true
+aiticon.com true
+ajouin.com true
+akachanikuji.com true
+akselinurmio.fi true
+al-shami.net true
+aladdinschools.appspot.com true
+alainwolf.net true
+alaninkenya.org true
+alanrickmanflipstable.com true
+alecvannoten.be true
+alethearose.com true
+alexgaynor.net true
+alexsexton.com true
+alexyang.me true
+allinonecyprus.com true
+alza.cz true
+alza.de true
+alza.sk true
+alzashop.com true
+amaforums.org false
+amdouglas.uk true
+anadoluefessk.org true
+anadoluefessporkulubu.org true
+anakros.me true
+andere-gedanken.net true
+andreasbreitenlohner.de true
+andrewimeson.com true
+andymartin.cc true
+anetaben.nl true
+angularjs.org true
+anime.my false
+animurecs.com true
+ankakaak.com true
+ankarakart.com.tr true
+annahmeschluss.de true
+annevankesteren.com true
+annevankesteren.nl true
+annevankesteren.org true
+anonym-surfen.de true
+ansdell.net true
+antipolygraph.org true
+antoniomarques.eu true
+anycoin.me true
+apachehaus.de false
+apadvantage.com true
+api.intercom.io false
+api.lookout.com false
+api.simple.com false
+api.xero.com false
+apis.google.com true
+apn-einstellungen.de true
+aponow.de true
+app.lookout.com false
+app.manilla.com true
+app.recurly.com true
+app.simpletax.ca false
+app.yinxiang.com false
+appengine.google.com true
+apple-watch-zubehoer.de true
+apps-for-fishing.com true
+apps.facebook.com false
+apps.fedoraproject.org true
+apps.stg.fedoraproject.org true
+aprz.de true
+aranycsillag.net true
+arbitrary.ch true
+archlinux.de true
+areafiftylan.nl true
+arendburgers.nl true
+arguggi.co.uk true
+arivo.com.br true
+arlen.io true
+armytricka.cz true
+aroonchande.com true
+arteseideias.com.pt true
+arty.name true
+ask.fedoraproject.org true
+ask.stg.fedoraproject.org true
+atc.io true
+athenelive.com true
+atishchenko.com true
+atlantischild.hu true
+atlassian.net true
+atte.fi true
+auf-feindgebiet.de true
+aurainfosec.com true
+aurainfosec.com.au true
+auraredeye.com true
+auraredshield.com true
+authentication.io true
+autoledky.sk true
+axka.com false
+azirevpn.com true
+badges.fedoraproject.org true
+badges.stg.fedoraproject.org true
+baer.im true
+baff.lu true
+bagelsbakery.com true
+balcan-underground.net true
+balikonos.cz true
+bank.simple.com false
+bardiharborow.com true
+barslecht.com true
+barslecht.nl true
+baruch.me true
+bassh.net true
+bautied.de true
+bayrisch-fuer-anfaenger.de true
+bccx.com true
+bcrook.com false
+beach-inspector.com true
+beamitapp.com true
+beastowner.com true
+beastowner.li true
+bedeta.de true
+bedreid.dk true
+beercandle.com true
+ben-energy.com true
+benchling.com true
+beneathvt.com true
+benjamin.pe true
+benjamins.com true
+bentertain.de true
+best-wedding-quotes.com true
+bfelob.gov true
+bgneuesheim.de true
+bhatia.at true
+biathloncup.ru true
+biddl.com true
+big-andy.co.uk true
+bigbrownpromotions.com.au true
+bigdinosaur.org true
+billigssl.dk true
+bit-sentinel.com true
+bit.voyage true
+bitbucket.org false
+bitchan.it true
+bitcoin.de true
+bitcoinx.ro true
+bitfactory.ws true
+bitmex.com true
+bitmon.net true
+bitnet.io true
+bitpod.de true
+bjornjohansen.no true
+bl4ckb0x.com true
+bl4ckb0x.de true
+bl4ckb0x.info true
+bl4ckb0x.net true
+bl4ckb0x.org true
+blablacar.co.uk true
+blablacar.com true
+blablacar.com.tr true
+blablacar.com.ua true
+blablacar.de true
+blablacar.es true
+blablacar.fr true
+blablacar.hr true
+blablacar.hu true
+blablacar.in true
+blablacar.it true
+blablacar.mx true
+blablacar.nl true
+blablacar.pl true
+blablacar.pt true
+blablacar.ro true
+blablacar.rs true
+blablacar.ru true
+blackberrycentral.com true
+blessnet.jp true
+blockchain.info true
+blocksatz-medien.de true
+bloemendal.me true
+blog.cyveillance.com true
+blog.gparent.org true
+blog.linode.com false
+blog.torproject.org false
+blubbablasen.de true
+bodo-wolff.de false
+bohramt.de true
+boiseonlinemall.com true
+bonitabrazilian.co.nz true
+bookingapp.nl true
+bownty.dk true
+boxcryptor.com true
+boypoint.de true
+bradkovach.com true
+brage.info false
+braineet.com true
+brainfork.ml true
+braintreegateway.com true
+brakemanpro.com true
+bran.cc true
+branchtrack.com false
+brandbuilderwebsites.com true
+breeswish.org true
+brks.xyz true
+broeselei.at true
+brossmanit.com true
+brunosouza.org true
+buddhistische-weisheiten.org true
+bugzil.la true
+bugzilla.mozilla.org true
+buiko.com true
+buildkite.com true
+bulktrade.de true
+bundaberg.com true
+burtrum.org true
+business.facebook.com false
+business.lookout.com false
+businesshosting.nl true
+bustimes.org true
+buzzconcert.com true
+bygningsregistrering.dk true
+bysymphony.com true
+bytepark.de false
+bzv-fr.eu true
+ca.gparent.org true
+cackette.com true
+call.me true
+calomel.org true
+calories.org true
+calvin.me true
+camolist.com true
+canhazip.com true
+cao.gov true
+capitaltg.com true
+cardoni.net true
+caremad.io true
+carezone.com false
+carlosalves.info true
+cartouche24.eu true
+cartucce24.it true
+casa-su.casa true
+catnapstudios.com true
+cbhq.net true
+cdlcenter.com true
+cdnb.co true
+cdt.org true
+certible.com true
+certly.io true
+cfo.gov true
+chahub.com true
+chainmonitor.com true
+chartstoffarm.de false
+chatbot.me true
+check.torproject.org false
+checkout.google.com true
+cheesetart.my false
+chrisirwin.ca true
+chrisjean.com true
+chrome-devtools-frontend.appspot.com true
+chrome.com false
+chrome.google.com true
+chromiumcodereview.appspot.com false
+chroniclesofgeorge.com true
+chulado.com true
+cio.gov true
+cklie.de true
+ckliemann.com true
+ckliemann.net true
+cktennis.com true
+clan-ww.com true
+clapping-rhymes.com true
+classdojo.com true
+clerkendweller.uk true
+clevisto.com true
+climateinteractive.org true
+clintwilson.technology true
+cloud.google.com true
+cloudcert.org true
+cloudns.com.au true
+cloudpebble.net true
+cloudsecurityalliance.org true
+cloudstoragemaus.com true
+cloudup.com true
+code-poets.co.uk true
+code.facebook.com false
+code.google.com true
+codepoints.net true
+codepref.com true
+codepx.com true
+codereview.appspot.com false
+codereview.chromium.org true
+coinapult.com true
+coinbase.com true
+coindam.com false
+collinmbarrett.com true
+coloradocomputernetworking.net true
+comdurav.com true
+commencepayments.com true
+completionist.audio true
+comssa.org.au true
+config.schokokeks.org false
+conformal.com true
+conrad-kostecki.de true
+console.support true
+consumersentinel.gov true
+contributor.google.com true
+controlcenter.gigahost.dk true
+cor-ser.es true
+cordial-restaurant.com true
+costablancavoorjou.com true
+cotonea.de true
+courtlistener.com true
+covenantoftheriver.org true
+covoiturage.fr true
+cpvmatch.eu true
+cracker.in.th true
+crm.onlime.ch false
+crowdjuris.com true
+crute.me true
+crypto.cat false
+crypto.graphics true
+cryptobin.org true
+cryptography.io true
+cryptopartyatx.org true
+cryptopush.com true
+csacongress.org true
+cspbuilder.info true
+csuw.net true
+cube.de true
+cupcake.io true
+cupcake.is true
+curiosity-driven.org true
+curlybracket.co.uk true
+curtacircuitos.com.br false
+cyanogenmod.xxx true
+cybershambles.com true
+cybozu.com true
+cybozulive.com true
+cycleluxembourg.lu true
+cyon.ch true
+cyphertite.com true
+cyprus-company-service.com true
+czakey.net true
+czbix.com true
+czk.mk true
+d42.no true
+daknob.net true
+danielalvarez.net true
+danonsecurity.com true
+danskoferie.dk true
+danw.io true
+daphne.informatik.uni-freiburg.de true
+darchoods.net false
+darkengine.io true
+darknode.in true
+darkpony.ru true
+darkserver.fedoraproject.org true
+darkserver.stg.fedoraproject.org true
+darlo.co.uk true
+darom.jp true
+dash-board.jp false
+data-abundance.com true
+data.qld.gov.au false
+datasnitch.co.uk true
+datenkeks.de true
+daveoc64.co.uk true
+davidlyness.com true
+davidmcevoy.org.uk true
+davidnoren.com true
+daylightpirates.org true
+dbgamestudio.com true
+dccode.gov true
+deadbeef.ninja true
+dealcruiser.nl true
+debtkit.co.uk true
+dedimax.de true
+dee.pe true
+defcon.org true
+dekasan.ru true
+deliverance.co.uk false
+denh.am true
+depechemode-live.com true
+derevtsov.com false
+derhil.de true
+desmaakvanplanten.be true
+detectify.com false
+developer.mydigipass.com false
+developers.facebook.com false
+devinfo.net false
+devklog.net true
+diamante.ro true
+die-besten-weisheiten.de true
+digital1st.co.uk true
+dillonkorman.com true
+dinamoelektrik.com true
+disking.co.uk true
+dist.torproject.org false
+dixmag.com false
+dl.google.com true
+dlc.viasinc.com true
+dm.lookout.com false
+dm.mylookout.com false
+dn42.us true
+dnmlab.it true
+dnsman.se true
+doc.python.org true
+docs.google.com true
+docs.python.org true
+dohosting.ru true
+domainkauf.de true
+domains.google.com true
+donmez.uk true
+donmez.ws true
+donotcall.gov true
+doridian.com true
+doridian.de true
+doridian.net true
+doridian.org true
+dpsg-roden.de true
+dragons-of-highlands.cz true
+dreadbyte.com true
+dreamsforabetterworld.com.au true
+drive.google.com true
+dropbox.com true
+dropboxer.net true
+drtroyhendrickson.com true
+drumbandesperanto.nl true
+dubrovskiy.net true
+ducohosting.com true
+dyeager.org true
+dylanscott.com.au true
+dynaloop.net true
+dzlibs.io true
+e-kontakti.fi true
+e.mail.ru true
+earmarks.gov true
+easysimplecrm.com false
+eatsleeprepeat.net true
+ebanking.indovinabank.com.vn true
+ecake.in true
+ecdn.cz true
+ecfs.link true
+ecg.fr false
+ecosystem.atlassian.net true
+ectora.com true
+ed.gs true
+edge-cloud.net true
+edit.yahoo.com false
+edix.ru true
+eduid.se true
+eduroam.no true
+edyou.eu true
+ef.gy true
+eff.org true
+egfl.org.uk true
+egit.co true
+ego4u.com true
+ego4u.de true
+eksisozluk.com true
+electronic-ignition-system.com true
+ellegaard.dk true
+elliquiy.com true
+emailprivacytester.com true
+emptypath.com true
+encircleapp.com true
+encryptallthethings.net true
+encrypted.google.com true
+energy-drink-magazin.de true
+enigmail.net true
+enorekcah.com true
+enskat.de true
+enskatson-sippe.de true
+entropia.de false
+erisrenee.com true
+eromixx.com true
+erotische-aanbiedingen.nl true
+errors.zenpayroll.com false
+eru.me true
+esoa.net true
+espra.com true
+ethack.org true
+ethercalc.com true
+ethercalc.org true
+ethitter.com true
+etoprekrasno.ru true
+eurotramp.com true
+eva.cz true
+evalesc.com true
+everhome.de true
+eveshamglass.co.uk true
+evstatus.com true
+exiahost.com false
+exon.io true
+expatads.com true
+explodie.org true
+expoundite.net true
+extendwings.com true
+ezequiel-garzon.com true
+ezequiel-garzon.net true
+f-droid.org true
+f2f.cash true
+fa-works.com true
+fabhub.io true
+facebook.com false
+factor.cc false
+fairbill.com true
+fakturoid.cz true
+falconvintners.com true
+fangs.ink true
+fant.dk true
+faq.lookout.com false
+fassadenverkleidung24.de true
+fastcomcorp.net true
+fatherhood.gov true
+faucetbox.com true
+federalregister.gov true
+fedorahosted.org true
+fedorapeople.org true
+feedbin.com false
+feedthebot.com true
+feen.us true
+feminists.co true
+ferienhaus-polchow-ruegen.de false
+fewo-thueringer-wald.de true
+ffbans.org true
+fidelapp.com true
+fiftyshadesofluca.ml true
+fiken.no true
+filedir.com false
+filip-prochazka.com true
+finn.io false
+firebaseio-demo.com true
+firebaseio.com true
+firebirdrangecookers.com true
+firefart.at true
+firemail.io true
+firma-offshore.com true
+firstlook.org true
+fischer-its.com true
+fish-hook.ru true
+fitkram.cz true
+fj.simple.com false
+flagspot.net true
+flamer-scene.com true
+fleximus.org false
+floobits.com true
+florian-lillpopp.de true
+florianlillpopp.de true
+florianmitrea.uk true
+floweslawncare.com true
+flushstudios.com true
+fluxfingers.net true
+flynn.io true
+fniephaus.com true
+food4health.guide true
+foodwise.marketing true
+forewordreviews.com true
+forgix.com true
+forodeespanol.com true
+forum.linode.com false
+forum.quantifiedself.com true
+foxelbox.com true
+fralef.me false
+frederik-braun.com true
+freenetproject.org true
+freeshell.de true
+freesounding.com true
+freesounding.ru true
+freethought.org.au true
+fretscha.com true
+froggstack.de true
+fronteers.nl true
+fruchthof24.de true
+frusky.de false
+frusky.net true
+ftccomplaintassistant.gov true
+fteproxy.org true
+fundingempire.com true
+futos.de true
+fuzzing-project.org true
+fx5.de true
+g2g.com true
+gallery44.org true
+gambit.pro true
+gambitnash.co.uk true
+gambitnash.com true
+gambitprint.com true
+gamercredo.com true
+gamercredo.net true
+gameserver-sponsor.de true
+garron.net true
+gavick.com true
+gaytorrent.ru true
+gc.net true
+ge3k.net true
+gemeinfreie-lieder.de true
+genuxation.com true
+genuxtsg.com true
+geoip.fedoraproject.org true
+geoip.stg.fedoraproject.org true
+gerardozamudio.mx true
+gernert-server.de true
+get.zenpayroll.com false
+getable.com true
+getbambu.com false
+getcloak.com false
+getcolor.com true
+getdigitized.net true
+getfedora.org true
+getfittedstore.com true
+getmango.com true
+getnikola.com true
+getsello.com true
+getssl.uz true
+gheorghesarcov.ga true
+giacomopelagatti.it true
+github.com true
+github.party false
+gizzo.sk true
+glass.google.com true
+globalittech.com false
+globuli-info.de true
+glossopnorthendafc.co.uk true
+gmail.com false
+gmantra.org true
+gmcd.co true
+gnetwork.eu true
+go-zh.org true
+go.xero.com false
+gocardless.com true
+gokmenguresci.com true
+goldendata.io true
+golfscape.com false
+google true
+googlemail.com false
+googleplex.com true
+gopay.cz true
+goshop.cz true
+gothamlimo.com true
+goto.google.com true
+gotspot.com true
+gplintegratedit.com true
+gpsfix.cz true
+gra2.com true
+grandcapital.id true
+grandcapital.ru true
+grc.com false
+greensolid.biz true
+gregorytlee.me true
+grepular.com true
+grigalanzsoftware.com true
+grimm-gastrobedarf.de true
+grocock.me.uk true
+groetzner.net true
+groszek.pl true
+groups.google.com true
+gtraxapp.com true
+gudini.net true
+gugga.dk false
+guidetoiceland.is true
+gunnarhafdal.com true
+guphi.net true
+guru-naradi.cz true
+gurusupe.com true
+guthabenkarten-billiger.de true
+gvt2.com true
+gvt3.com true
+gw2treasures.com true
+gwijaya.com true
+h2check.org true
+haber1903.com true
+hachre.de false
+hack.li true
+hackerone-user-content.com true
+hackerone.com true
+haircrazy.com true
+hangouts.google.com true
+hansvaneijsden.com true
+happylifestyle.com true
+happyteamlabs.com true
+harvestapp.com true
+hash-list.com true
+hasilocke.de true
+haste.ch true
+haufschild.de true
+hausverbrauch.de true
+haveibeenpwned.com true
+hboeck.de true
+healthcare.gov false
+heartlandrentals.com true
+heavystresser.com true
+heftkaufen.de true
+heha.co false
+heid.ws true
+heijblok.com true
+helichat.de true
+help.simpletax.ca false
+helpadmin.net true
+helpium.de true
+hemlockhillscabinrentals.com true
+henriknoerr.com true
+heppler.net true
+herbert.io true
+herocentral.de true
+herzbotschaft.de true
+heute-kaufen.de true
+hex2013.com true
+hexony.com true
+hg.python.org true
+hicn.gq true
+hicoria.com true
+history.google.com false
+hiv.gov true
+hledejpravnika.cz true
+hobbyspeed.com true
+holymoly.lu true
+honeybadger.io false
+horza.org true
+hostedtalkgadget.google.com true
+hostinginnederland.nl true
+hostix.de true
+howrandom.org true
+howsmyssl.com true
+howsmytls.com true
+hozana.si true
+hpac-portal.com true
+hrackydomino.cz true
+hs-group.net true
+hsmr.cc true
+hsr.gov true
+hstsfail.appspot.com true
+html5.org true
+httpswatch.com true
+hushfile.it true
+i10z.com true
+i5y.co.uk true
+iamcarrico.com true
+ian.sh true
+iban.is true
+id-co.in true
+id-conf.com true
+id.atlassian.com true
+id.mayfirst.org false
+ideaweb.de true
+ieval.ro true
+ihrlotto.de true
+ijohan.nl true
+ikkatsu-satei.jp true
+ilbuongiorno.it true
+ilikerainbows.co true
+ilikerainbows.co.uk false
+imaginary.ca true
+imagr.io true
+imgg.es true
+imouto.my false
+impex.com.bd true
+in.xero.com false
+inb4.us true
+inbox.google.com true
+indiecert.net true
+indovinabank.com.vn true
+influxus.com true
+infogrfx.com true
+informnapalm.org true
+iniiter.com true
+initrd.net true
+inkbunny.net true
+inleaked.com true
+innophate-security.com true
+innophate-security.nl true
+insighti.org true
+insouciant.org true
+instasex.ch true
+integromat.com true
+interasistmen.se true
+interserved.com true
+iostips.ru true
+ipomue.com false
+ipsec.pl true
+iqualtech.com true
+iranianlawschool.com true
+iridiumbrowser.de true
+irische-segenswuensche.info true
+irmag.ru true
+ironfistdesign.com true
+isimonbrown.co.uk true
+isitchristmas.com true
+isogram.nl true
+it-schwerin.de true
+itdashboard.gov true
+itriskltd.com true
+itsamurai.ru true
+itshost.ru true
+ix8.ru true
+izdiwho.com true
+j-lsolutions.com true
+jackyyf.com false
+jacobparry.ca false
+jacuzziprozone.com true
+jahliveradio.com false
+jakub-boucek.cz true
+jamesbywater.co.uk true
+jamesbywater.com true
+jamesbywater.me true
+jamesbywater.uk true
+jamielinux.com true
+janoberst.com true
+jbn.mx true
+jelmer.co.uk true
+jelmer.uk true
+jeremyness.com true
+jetaprices.com true
+jettshome.org true
+jfreitag.de true
+jh-media.eu false
+jimshaver.net true
+jira.com true
+jitsi.org false
+jkb.pics true
+jkbuster.com true
+jmdekker.it true
+jmedved.com true
+jogorama.com.br true
+johannes.io true
+johners.me true
+johnmichel.org true
+jonas-keidel.de true
+jonaswitmer.ch true
+jonathan.ir true
+jondevin.com true
+jonnybarnes.uk true
+jonpads.com true
+jpbike.cz true
+jrc9.ca true
+julianmeyer.de true
+juliansimioni.com true
+jwilsson.com true
+jwilsson.me true
+jwnotifier.org true
+k-dev.de true
+kaheim.de true
+kalevlamps.co.uk true
+kalmar.com true
+kaneo-gmbh.de true
+kanzashi.com true
+karaoketonight.com true
+kardize24.pl true
+karmaspa.se true
+kartonmodellbau.org true
+kaufberatung.community true
+kavovary-kava.cz true
+kdex.de true
+kdyby.org true
+kedarastudios.com true
+keeleysam.com true
+keeleysam.me true
+keepa.com true
+keepclean.me true
+keeperapp.com true
+keepersecurity.com true
+kernel-error.de true
+kevincox.ca true
+keybase.io true
+keycdn.com true
+keycom.co.uk true
+keyerror.com true
+khanovaskola.cz true
+khipu.com true
+khmath.com true
+ki-on.net true
+kinderbuecher-kostenlos.de true
+kinganywhere.eu true
+kingmanhall.org true
+kinogb.net false
+kinsights.com false
+kintone.com true
+kirei.se true
+kissflow.com true
+kitsta.com true
+klarmobil-empfehlen.de true
+klatschreime.de true
+klausbrinch.dk true
+klaxn.com true
+kleidertauschpartys.de true
+kliemann.me true
+klingeletest.de true
+knip.ch true
+knowledgehook.com true
+koen.io true
+koenrouwhorst.nl true
+koenvdheuvel.me true
+kojipkgs.fedoraproject.org true
+kollawat.me true
+komandakovalchuk.com false
+konklone.com true
+koop-bremen.de true
+koordinate.net true
+korinar.com true
+kosho.org true
+kpdyer.com true
+kpebetka.net true
+kraken.io true
+kredite.sale true
+kredite24.de true
+ks-watch.de true
+kuppingercole.com true
+kupschke.net true
+kura.io true
+labaia.info true
+laf.in.net true
+lagerauftrag.info true
+lancejames.com true
+lapetition.be true
+lasst-uns-beten.de true
+lastpass.com false
+laukstein.com true
+launchkey.com true
+lavalite.de true
+lavval.com true
+lb-toner.de true
+leadbook.ru true
+leakedminecraft.net true
+leanclub.org true
+ledhouse.sk true
+legoutdesplantes.be true
+leibniz-remscheid.de true
+leifdreizler.com true
+lellyboi.ml true
+lence.net true
+leninalbertop.com.ve true
+leonardcamacho.me true
+leonax.net true
+leonklingele.de true
+les-corsaires.net true
+libfte.org true
+libraryfreedomproject.org true
+lichtspot.de true
+liebel.org true
+light.mail.ru true
+lighting-centres.co.uk true
+lillpopp.eu true
+lilpwny.com true
+limitededitioncomputers.com true
+limitededitionsolutions.com true
+limpid.nl true
+lingolia.com true
+linode.com false
+linorman1997.me true
+linux-admin-california.com true
+linx.li true
+linx.net true
+lists.mayfirst.org false
+lists.stg.fedoraproject.org true
+livej.am true
+livekaarten.nl true
+ljs.io true
+lloyd-day.me true
+lmmtfy.io true
+lnx.li true
+lobste.rs true
+lockify.com true
+lodash.com true
+loenshotel.de true
+loftboard.eu true
+logentries.com false
+login.corp.google.com true
+login.launchpad.net true
+login.persona.org true
+login.sapo.pt true
+login.ubuntu.com true
+login.xero.com false
+login.yahoo.com false
+lolicore.ch true
+lookout.com false
+lookyman.net true
+lookzook.com true
+lore.azurewebsites.net true
+ludwig.im true
+luelistan.net true
+lumi.do false
+luneta.nearbuysystems.com false
+luxwatch.com true
+lymia.moe true
+lyst.co.uk true
+m.facebook.com false
+m.mail.ru true
+m0wef.uk true
+maartenvandekamp.nl true
+mach-politik.ch true
+madars.org true
+madeitwor.se true
+mafamane.com true
+maff.scot false
+magneticanvil.com true
+mahamed91.pw true
+mail-settings.google.com true
+mail.de true
+mail.google.com true
+mail.yahoo.com false
+mailbox.org true
+mailmag.net true
+makeitdynamic.com true
+makeyourlaws.org true
+mall.cz true
+mall.hu true
+mall.pl true
+mall.sk true
+malnex.de true
+malwre.io true
+mammaw.com true
+man3s.jp true
+manage.zenpayroll.com false
+manageprojects.com true
+manager.linode.com false
+mandala-ausmalbilder.de true
+manicode.com true
+markayapilandirma.com true
+market.android.com true
+markhaehnel.de true
+markusueberallassetmanagement.de true
+marshut.net true
+massivum.de false
+masters.black true
+matatall.com false
+mathiasbynens.be true
+matteomarescotti.it true
+mattfin.ch true
+mattmccutchen.net true
+mattsvensson.com true
+max.gov true
+maximelouet.me true
+mbasic.facebook.com false
+mbp.banking.co.at false
+mcard.vn true
+mccrypto.de true
+mcnext.net true
+md5file.com true
+mdfnet.se false
+me.net.nz true
+meamod.com true
+mebio.us true
+medallia.io true
+mediacru.sh true
+medium.com true
+medovea.ru true
+medtehnika.ua true
+meetfinch.com true
+meetings2.com true
+mega.co.nz true
+megaplan.cz true
+megaplan.ru true
+mehmetince.net true
+meinebo.it true
+members.mayfirst.org false
+members.nearlyfreespeech.net false
+mercuryamericas.com true
+meritz.rocks true
+mertcangokgoz.com true
+metrobriefs.com true
+mevs.cz true
+mh-bloemen.co.jp true
+miasarafina.de true
+michalspacek.cz true
+miconcinemas.com true
+mig5.net true
+mijn-email.org true
+mike-bland.com true
+miketabor.com true
+mikewest.org true
+miku.hatsune.my false
+mim.properties true
+mimeit.de true
+mimovrste.com true
+mindcoding.ro true
+mindoktor.se true
+minecraftvoter.com true
+mineover.es true
+minez-nightswatch.com false
+minikneet.com true
+minnesotadata.com true
+mironet.cz true
+miskatonic.org true
+miss-inventory.co.uk true
+mister.hosting true
+mitell.jp false
+mittenhacks.com true
+mjanja.ch true
+mkcert.org true
+mkw.st true
+mnsure.org true
+mobilcom-debitel-empfehlen.de true
+mobile.usaa.com false
+mobilux.lv true
+mobobe.com true
+modeldimension.com true
+mokote.com true
+mondwandler.de true
+morethanadream.lv true
+moriz.de true
+moriz.net true
+mothereff.in true
+mountainmusicpromotions.com true
+mountainroseherbs.com true
+movlib.org true
+mp3juices.is true
+mpreserver.com true
+mqas.net true
+mr-hosting.com true
+msa-aesch.ch true
+msc-seereisen.net true
+mtau.com true
+mthode.org true
+mths.be true
+mtouch.facebook.com false
+mudcrab.us true
+mujadin.se true
+multigamecard.com true
+munich-rage.de true
+munki.org true
+munuc.org true
+musi.cx true
+musicgamegalaxy.de true
+musmann.io true
+mustika.cf true
+mutamatic.com true
+mutantmonkey.in true
+mutantmonkey.info true
+mutantmonkey.sexy true
+mvno.io true
+mvsecurity.nl true
+mwe.st false
+my.onlime.ch false
+my.xero.com false
+myaccount.google.com true
+mygadgetguardian.lookout.com false
+mygretchen.de true
+mykontool.de true
+mylookout.com false
+myni.io true
+mynigma.org true
+myplaceonline.com true
+myprintcard.de true
+myvirtualserver.com true
+nachsendeauftrag.net true
+nachsenden.info true
+naiharngym.com true
+nameid.org true
+namepros.com true
+nan.zone true
+nanderson.me true
+narodniki.com true
+nationalpriorities.org true
+nayahe.ru true
+nbl.org.tw true
+nctx.co.uk true
+ndarville.com true
+nectarleaf.com true
+neg9.org false
+neilwynne.com false
+neko.li true
+nella-project.org true
+nellacms.com true
+nellacms.org true
+nellafw.org true
+nerven.se true
+net-safe.info true
+netbox.cc true
+netera.se true
+netrelay.email true
+netrider.net.au true
+newstarnootropics.com true
+nextend.net true
+ng-security.com true
+nginxnudes.com true
+nicolaw.uk true
+nieselregen.com true
+niloxy.com true
+nmctest.net true
+nmd.so true
+nodari.com.ar true
+noemax.com true
+noob-box.net true
+nopex.no true
+northernmuscle.ca true
+nos-oignons.net true
+nostraforma.com false
+notalone.gov true
+nouvelle-vague-saint-cast.fr true
+novacoast.com true
+nowhere.dk true
+npw.net true
+nsboutique.com true
+nu3.at true
+nu3.ch true
+nu3.co.uk true
+nu3.com true
+nu3.de true
+nu3.dk true
+nu3.fi true
+nu3.fr true
+nu3.no true
+nu3.se true
+null.tips true
+nutsandboltsmedia.com true
+nuvini.com true
+nwa.xyz true
+nwgh.org true
+nymphetomania.net true
+oakslighting.co.uk true
+ocrami.us true
+offshore-firma.org true
+oguya.ch true
+ohling.org true
+ohnemusik.com true
+okmx.de true
+olivierlemoal.fr true
+omitech.co.uk true
+onedot.nl true
+onedrive.com true
+onedrive.live.com false
+onsitemassageco.com true
+ooonja.de true
+openacademies.com true
+oplop.appspot.com true
+opsmate.com false
+optimus.io true
+orbograph-hrcm.com true
+orcahq.com true
+orhideous.name true
+oscarvk.ch true
+osquery.io true
+osterkraenzchen.de true
+otakuworld.de true
+ouvirmusica.com.br true
+ovenapp.io true
+oversight.io true
+ownmovies.fr true
+p.linode.com false
+packagist.org false
+pactf.com true
+pajonzeck.de true
+palava.tv true
+pap.la false
+parent5446.us true
+partyvan.eu true
+partyvan.it true
+partyvan.nl true
+partyvan.se true
+passphrase.today true
+passport.yandex.by true
+passport.yandex.com true
+passport.yandex.com.tr true
+passport.yandex.kz true
+passport.yandex.ru true
+passport.yandex.ua true
+passwd.io true
+password.codes true
+passwords.google.com true
+pasta-factory.co.il true
+paste.linode.com false
+pastebin.linode.com false
+patechmasters.com true
+patriksimek.cz true
+patt.us true
+pauladamsmith.com true
+paulschreiber.com true
+pay.gigahost.dk true
+paymentaccuracy.gov true
+payments-reference.org true
+paymill.com true
+paymill.de true
+paypal.com false
+payroll.xero.com false
+pbprint.ru false
+pclob.gov true
+pdf.yt true
+peercraft.com true
+pentesterlab.com true
+perfectionis.me true
+personaldatabasen.no true
+pestici.de true
+petplum.com true
+petrolplus.ru true
+pharmaboard.de true
+phil.tw true
+philosopherswool.com true
+phoenix.dj true
+phoenixlogan.com true
+phryanjr.com false
+phurl.de true
+pi-supply.com true
+picksin.club true
+picsto.re true
+pieperhome.de true
+pierre-schmitz.com true
+pieterhordijk.com true
+pijuice.com true
+piratedb.com true
+piratedot.com true
+pirateproxy.sx true
+pixel.facebook.com false
+pixi.me true
+play.google.com true
+plothost.com true
+plus.google.com false
+plus.sandbox.google.com false
+plzenskybarcamp.cz true
+pmg-offshore-company.com true
+pmg-purchase.com true
+pmg-purchase.net true
+poedgirl.com true
+pollpodium.nl true
+polymathematician.com true
+polypho.nyc true
+ponythread.com true
+portal.tirol.gv.at true
+posteo.de false
+postfinance.ch true
+posttigo.com true
+prakharprasad.com true
+prefontaine.name true
+preissler.co.uk true
+preloaded-hsts.badssl.com true
+presidentials2016.com true
+privategiant.com true
+profiles.google.com true
+progressiveplanning.com true
+projectascension.io true
+projektzentrisch.de true
+prontolight.com true
+proofwiki.org true
+propagandism.org true
+prospo.co true
+prowhisky.de true
+proximato.com true
+proxybay.club true
+proxybay.co true
+proxybay.info true
+ptn.moscow true
+puac.de true
+pubkey.is true
+publications.qld.gov.au false
+puiterwijk.org true
+pult.co false
+purewebmasters.com false
+pwd.ovh true
+pypa.io true
+pypi.python.org true
+python.org false
+qa.fedoraproject.org true
+qa.stg.fedoraproject.org true
+qetesh.de true
+qualityhomesystems.com true
+quebecmailbox.com true
+quli.nl true
+quuz.org true
+r3s1stanc3.me true
+rad-route.de true
+radiormi.com true
+rafaelcz.de true
+ragingserenity.com true
+railgun.ac true
+raiseyourflag.com true
+ramsor-gaming.de true
+rasing.me true
+raspass.me true
+ravchat.com true
+rawstorieslondon.com true
+raydobe.me false
+raymii.org true
+reaconverter.com true
+red-t-shirt.ru true
+redirect.fedoraproject.org true
+redirect.stg.fedoraproject.org true
+redletter.link true
+redlink.de true
+redteam-pentesting.de true
+reedloden.com true
+refundo.cz true
+refundo.sk true
+reg.ru false
+release-monitoring.org true
+reliable-mail.de true
+renem.net true
+report-uri.io true
+research.facebook.com false
+research.md true
+residentsinsurance.co.uk true
+resources.flowfinity.com true
+reviews.anime.my true
+riccy.org true
+richiemail.net true
+ricochet.im true
+riesenmagnete.de true
+rika.me true
+rippleunion.com true
+rischard.org true
+rlalique.com true
+rmmanfredi.com true
+robertof.ovh true
+robinadr.com true
+robinsonyu.com true
+robteix.com true
+robtex.com true
+rodosto.com true
+roeper.party true
+roland.io true
+romab.com true
+roman-pavlik.cz true
+romans-place.me.uk true
+romulusapp.com false
+room-checkin24.de true
+roosterpgplus.nl true
+roots.io true
+roquecenter.org true
+rosenkeller.org true
+rotunneling.net true
+roundcube.mayfirst.org false
+royalacademy.org.uk true
+rpy.xyz true
+rssr.se true
+ru-sprachstudio.ch true
+rubecodeberg.com true
+rubendv.be true
+rubyshop.nl true
+rudloff.pro true
+rusadmin.biz true
+ruudkoot.nl true
+rws-vertriebsportal.de true
+ryan-goldstein.com true
+s-c.se true
+sabahattin-gucukoglu.com true
+safescan.com true
+sagerus.com true
+sageth.com true
+saintsrobotics.com true
+sakaki.anime.my true
+salaervergleich.com true
+sale4ru.ru true
+salserocafe.com true
+samba.org true
+samfunnet.no false
+samizdat.cz true
+samuelkeeley.com true
+sanatfilan.com false
+sandbox.mydigipass.com false
+sarahlicity.co.uk true
+saulchristie.com true
+save.gov true
+saveaward.gov true
+savvytime.com true
+schachburg.de true
+schokokeks.org true
+schreiber-netzwerk.eu true
+schreibnacht.de true
+schwarzer.it true
+sciencex.com true
+scotthel.me true
+scotthelme.co.uk true
+scoutdb.ch true
+scrambl.is true
+scrambler.in false
+scrap.tf true
+screenlight.tv true
+scribe.systems true
+script.google.com true
+sdsl-speedtest.de true
+search-one.de true
+sec.gd true
+secretserveronline.com true
+secure.facebook.com false
+securedrop.org true
+securesuisse.ch true
+securify.nl true
+security-carpet.com true
+security.google.com true
+securityheaders.com true
+securitysnobs.com false
+secuvera.de true
+seifried.org true
+sellocdn.com true
+servergno.me true
+servertastic.com true
+servethecity-karlsruhe.de false
+setuid.io true
+seyahatsagliksigortalari.com true
+sh-network.de true
+shaaaaaaaaaaaaa.com true
+shadex.net true
+shakepeers.org true
+shamka.ru true
+shanewadleigh.com true
+shasso.com true
+shellsec.pw true
+shenyuqi.com true
+sherbers.de true
+shiinko.com false
+shipard.com true
+shodan.io true
+shopontarget.com true
+shortdiary.me true
+sidium.de true
+siewert-kau.de true
+sigterm.sh true
+sikayetvar.com true
+silentcircle.com false
+simbolo.co.uk false
+simple.com false
+simpletax.ca false
+simplia.cz true
+simplystudio.com true
+siraweb.org true
+siriad.com true
+sites.google.com true
+sitesko.de true
+sitesten.com true
+sizzle.co.uk true
+sjoorm.com true
+skeeley.com true
+skhosting.eu true
+skogsbruket.fi true
+skogskultur.fi true
+skydrive.live.com false
+slack-files.com true
+slack.com true
+slattery.co true
+sleio.com true
+slever.cz true
+slevomat.cz true
+slidebatch.com true
+slope.haus true
+slse.ca true
+smartcleaningcenter.nl true
+smartcoin.com.br true
+smartlend.se true
+smartship.co.jp true
+smith.is true
+snailing.org true
+snakehosting.dk true
+snazel.co.uk true
+sneezry.com true
+sny.no true
+soccergif.com true
+soci.ml true
+sockeye.cc true
+soia.ca true
+solihullcarnival.co.uk true
+solihulllionsclub.org.uk true
+sorz.org true
+souki.cz true
+soulfulglamour.uk true
+soulogic.com true
+sour.is true
+sourceway.de true
+southside-crew.com true
+souvik.me true
+spartantheatre.org true
+spawn.cz true
+speedcounter.net true
+spencerbaer.com true
+spideroak.com true
+spongepowered.org true
+spreadsheets.google.com true
+spreed.me true
+sprueche-zum-valentinstag.de true
+sprueche-zur-geburt.info true
+sprueche-zur-hochzeit.de true
+sprueche-zur-konfirmation.de true
+spyroszarzonis.com true
+squareup.com false
+srevilak.net true
+sro.center true
+ssl.google-analytics.com true
+sslmate.com true
+stablelib.com true
+stage.wepay.com false
+standardssuck.org true
+starapple.nl true
+static.wepay.com false
+staticanime.net false
+stationary-traveller.eu true
+stereo.lu true
+stereochro.me true
+stesti.cz true
+stevegrav.es true
+steventress.com true
+stewartremodelingadvantage.com true
+sticklerjs.org true
+stirling.co true
+stocktrade.de false
+storedsafe.com true
+stormhub.org true
+strasweb.fr false
+stretchmyan.us true
+stripe.com true
+strongest-privacy.com true
+stuartbaxter.co false
+studienportal.eu true
+studydrive.net true
+stulda.cz true
+subeesu.com true
+subrosa.io true
+sufix.cz true
+suite73.org true
+sunjaydhama.com true
+suos.io true
+supplies24.at true
+supplies24.es true
+support.mayfirst.org false
+surkatty.org true
+survivalmonkey.com true
+svager.cz true
+swehack.org false
+sychov.pro true
+sylaps.com true
+sysctl.se true
+sysdb.io true
+syss.de true
+t23m-navi.jp false
+tadigitalstore.com true
+tageau.com true
+taken.pl true
+talideon.com true
+talk.google.com true
+talkgadget.google.com true
+tallr.se true
+tallshoe.com true
+tas2580.net true
+taskotron.fedoraproject.org true
+taskotron.stg.fedoraproject.org true
+tatort-fanpage.de true
+tauchkater.de true
+tbspace.de true
+tcgrepublic.com true
+tdelmas.ovh true
+tdrs.info true
+teachforcanada.ca true
+teamnorthgermany.de true
+teamupturn.com true
+techhipster.net true
+techhub.ml true
+techllage.com true
+techloaner.com true
+technotonic.com.au false
+tegelsensanitaironline.nl true
+tekshrek.com true
+tempus-aquilae.de true
+tent.io true
+terraelectronica.ru true
+terraweb.net true
+terrax.info true
+terrax.net true
+terrty.net true
+testsuite.org true
+texte-zur-taufe.de true
+thca.ca true
+theamp.com true
+thebimhub.com true
+thecoffeehouse.xyz true
+thecustomizewindows.com true
+theescapistswiki.com true
+thefrozenfire.com true
+thehiddenbay.net true
+themoep.at true
+thepaymentscompany.com true
+thepiratebay.al true
+therapynotes.com true
+thetomharling.com true
+theunitedstates.io true
+theweilai.com true
+thomastimepieces.com.au true
+thouni.de true
+thumbtack.com true
+thusoy.com true
+thyngster.com false
+tickopa.co.uk true
+tid.jp true
+timmy.ws true
+timotrans.de true
+timotrans.eu true
+timtaubert.de true
+tinfoilsecurity.com false
+tinkertry.com false
+tinte24.de true
+tintenfix.net true
+tipps-fuer-den-haushalt.de true
+tittelbach.at true
+titties.ml true
+tls.li true
+tmtopup.com true
+tno.io true
+tobias-kluge.de true
+todesschaf.org true
+todoist.com true
+tollsjekk.no true
+tom.horse true
+tomfisher.eu true
+tomharling.co.uk true
+tomharling.uk true
+tomrichards.net true
+tomvote.com true
+toner24.at true
+toner24.co.uk true
+toner24.es true
+toner24.fr true
+toner24.it true
+toner24.nl true
+toner24.pl true
+tonerdepot.de true
+tonerjet.at true
+tonerjet.co.uk true
+tonerklick.de true
+tonerkurier.de true
+tonermaus.de true
+tonermonster.de true
+tonex.de true
+tonex.nl true
+tonytan.cn true
+tonywebster.com true
+topbargains.com.au true
+topodin.com true
+topshelfguild.com true
+toptexture.com true
+tor2web.org true
+tormentedradio.com true
+torproject.org false
+torquato.de false
+toshnix.com true
+totem-eshop.cz true
+touch.facebook.com false
+touch.mail.ru true
+tox.im true
+tpbproxy.co true
+traas.org true
+tracktivity.com.au true
+translate.fedoraproject.org true
+translate.googleapis.com true
+translate.stg.fedoraproject.org true
+trashnothing.com true
+trauertexte.info true
+tresorit.com true
+tribaldos.com true
+tribut.de true
+ts3.consulting true
+tuamoronline.com true
+tucuxi.org true
+tuitle.com true
+tunebitfm.de true
+tuxplace.nl true
+twentymilliseconds.com true
+twisto.cz true
+twitter.com false
+twitteroauth.com true
+twofactorauth.org true
+twolinepassbrewing.com true
+typingrevolution.com true
+uae-company-service.com true
+ub3rk1tten.com false
+ubanquity.com true
+ubertt.org true
+ucfirst.nl true
+ukdefencejournal.org.uk true
+ukhas.net true
+ukrainians.ch true
+ulabox.com true
+unison.com true
+unitedadmins.com true
+unknownphenomena.net true
+unravel.ie true
+unterfrankenclan.de true
+uonstaffhub.com true
+uow.ninja true
+upitnik.rs true
+upload.facebook.com false
+uptrends.com true
+uptrends.de true
+usaa.com false
+uscntalk.com true
+uspsoig.gov true
+utilityapi.com true
+utleieplassen.no true
+vaddder.com true
+vasanth.org true
+vbh2o.com true
+vechkasov.ru true
+venicerealdeal.com true
+vhost.co.id true
+viasinc.com false
+vijos.org true
+visionless.me false
+vitrado.de true
+vmoagents.com false
+vocaloid.my true
+voicesuk.co.uk true
+vomitb.in true
+vortexhobbies.com true
+votocek.cz true
+votockova.cz true
+vox.vg true
+vpnzoom.com true
+vrobert.fr false
+vrtak-cz.net true
+vserver-preis-vergleich.de true
+vyplnto.cz true
+vzk.io false
+w-spotlight.appspot.com true
+wallet.google.com true
+walnutgaming.co.uk true
+walnutgaming.com true
+warrencreative.com false
+watsonhall.uk true
+wbg-vs.de true
+wearvr.com true
+webandmore.de false
+webandwords.com.au true
+webassadors.com false
+webcollect.org.uk true
+webeau.com true
+webfilings-eu-mirror.appspot.com true
+webfilings-eu.appspot.com true
+webfilings-mirror-hrd.appspot.com true
+webfilings.appspot.com true
+weblogzwolle.nl true
+webmail.gigahost.dk false
+webmail.onlime.ch false
+webmail.schokokeks.org false
+webmaniabr.com true
+webmarketingfestival.it true
+webogram.org true
+webrebels.org true
+websenat.de true
+webswitch.io true
+webtalis.nl true
+webtiles.co.uk true
+webtrh.cz true
+weggeweest.nl true
+welches-kinderfahrrad.de true
+welpy.com false
+wepay.com false
+wepay.in.th true
+wesecom.com true
+wesleyharris.ca true
+wettertoertchen.com true
+wevahoo.com true
+wf-bigsky-master.appspot.com true
+wf-demo-eu.appspot.com true
+wf-demo-hrd.appspot.com true
+wf-dogfood-hrd.appspot.com true
+wf-pentest.appspot.com true
+wf-staging-hr.appspot.com true
+wf-training-hrd.appspot.com true
+wf-training-master.appspot.com true
+wf-trial-hrd.appspot.com true
+whatwg.org true
+whd-guide.de true
+when-release.ru true
+when.fm true
+wherephoto.com true
+whitestagforge.com true
+whocalld.com true
+whonix.org true
+widememory.com false
+wieninternational.at true
+wifirst.net true
+wiki.python.org true
+wildbee.org true
+wilf1rst.com true
+williamsapiens.com true
+williamsonshore.com true
+willnorris.com true
+wills.co.tt true
+winhistory-forum.net true
+wisv.ch true
+wit.ai true
+wondershift.biz true
+wootton95.com true
+worldcubeassociation.org true
+wownmedia.com true
+wpletter.de true
+writeapp.me false
+wtfismyip.com true
+wubthecaptain.eu true
+wunderlist.com true
+wundi.net true
+wurzelzwerg.net true
+wvr-law.de true
+www.aclu.org false
+www.airbnb.com true
+www.apollo-auto.com true
+www.banking.co.at false
+www.braintreepayments.com false
+www.capitainetrain.com false
+www.cyveillance.com true
+www.dropbox.com true
+www.dropcam.com false
+www.entropia.de false
+www.eternalgoth.co.uk true
+www.etsy.com true
+www.evernote.com false
+www.facebook.com false
+www.gamesdepartment.co.uk false
+www.getcloak.com false
+www.gmail.com false
+www.googlemail.com false
+www.gov.uk false
+www.grc.com false
+www.healthcare.gov false
+www.heliosnet.com true
+www.honeybadger.io false
+www.intercom.io false
+www.irccloud.com false
+www.lastpass.com false
+www.linode.com false
+www.lookout.com false
+www.makeyourlaws.org true
+www.mydigipass.com false
+www.mylookout.com false
+www.noisebridge.net false
+www.opsmate.com true
+www.paypal.com false
+www.python.org true
+www.roddis.net true
+www.schokokeks.org true
+www.simbolo.co.uk false
+www.simple.com false
+www.therapynotes.com true
+www.tinfoilsecurity.com false
+www.torproject.org false
+www.twitter.com false
+www.usaa.com false
+www.viasinc.com true
+www.wepay.com false
+www.zenpayroll.com false
+wzrd.in true
+wzyboy.org true
+x.io true
+xbrlsuccess.appspot.com true
+xcoop.me true
+xenesisziarovky.sk true
+xf-liam.com true
+xho.me true
+xiaolvmu.me true
+xn--maraa-rta.org true
+xpd.se true
+xps2pdf.co.uk true
+xtrim.ru true
+xuntier.ch true
+y-o-w.com true
+yafuoku.ru true
+yahvehyireh.com true
+yamaken.jp true
+yanovich.net true
+yaporn.tv false
+yello.website true
+yenniferallulli.com true
+yenniferallulli.de true
+yenniferallulli.es true
+yenniferallulli.moda true
+yenniferallulli.nl true
+yetii.net true
+yksityisyydensuoja.fi true
+yokeepo.com true
+yorcom.nl true
+youdowell.com true
+yoursecondphone.co true
+ypart.eu true
+yunzhu.li true
+yunzhu.org true
+z.ai true
+zalan.do true
+zapier.com true
+zbasenem.pl true
+zenpayroll.com false
+zentraler-kreditausschuss.de true
+zentralwolke.de true
+zeplin.io false
+zeropush.com true
+zhang-hao.com true
+zhovner.com true
+zifb.in true
+zixiao.wang true
+zlatosnadno.cz true
+zlavomat.sk true
+zotero.org true
+zravypapir.cz true
diff --git a/src/html.cc b/src/html.cc
index fe861ce7..75d1820f 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"
@@ -356,17 +357,32 @@ bool a_Html_tag_set_valign_attr(DilloHtml *html, const char *tag, int tagsize)
/*
- * Create and add a new Textblock to the current Textblock
+ * Create and add a new Textblock to the current Textblock. Typically
+ * only one of addBreaks and addBreakOpt is true.
*/
-static void Html_add_textblock(DilloHtml *html, int space)
+static void Html_add_textblock(DilloHtml *html, bool addBreaks, int breakSpace,
+ bool addBreakOpt)
{
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 (addBreakOpt)
+ HT2TB(html)->addBreakOption (html->style (), false);
+
+ 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 ());
}
/*
@@ -788,113 +804,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:
@@ -902,130 +821,241 @@ 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., "&#47;" or "&#x2F;").
+ * 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.", 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 ';'.");
+ /* Don't require ';' for old HTML, except that our current heuristic
+ * is to require it in attributes to avoid cases like "&copy=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'.", 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., "&amp;" or "&hellip;").
+ * 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 "&copy=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 ';'.");
}
+ 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;
+}
- if (isocode >= 145 && isocode <= 151) {
- /* TODO: remove this hack. */
- isocode = Html_ms_stupid_quotes_2ucs(isocode);
- } else if (isocode == -1 && prefs.show_extra_warnings)
+/*
+ * 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 (toksize > 50) {
+ /* In pathological cases, attributes can be megabytes long and filled
+ * with character references. As of HTML5, the longest defined character
+ * reference is about 32 bytes long.
+ */
+ toksize = 50;
+ }
+
+ 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;
}
/*
@@ -1553,7 +1583,7 @@ static int
* rendering modes, so it may be better to chose another behaviour. --Jcid
*
* http://www.mozilla.org/docs/web-developer/quirks/doctypes.html
- * http://lists.auriga.wearlab.de/pipermail/dillo-dev/2004-October/002300.html
+ * http://lists.dillo.org/pipermail/dillo-dev/2004-October/002300.html
*
* This is not a full DOCTYPE parser, just enough for what Dillo uses.
*/
@@ -2017,7 +2047,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, false);
}
/*
@@ -2094,8 +2124,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 &&
@@ -2128,7 +2158,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;
@@ -2173,14 +2203,16 @@ DilloImage *a_Html_image_new(DilloHtml *html, const char *tag, int tagsize)
return NULL;
alt_ptr = a_Html_get_attr_wdef(html, tag, tagsize, "alt", NULL);
- if ((!alt_ptr || !*alt_ptr) && !prefs.load_images) {
+ if (!alt_ptr || !*alt_ptr) {
dFree(alt_ptr);
- alt_ptr = dStrdup("[IMG]"); // Place holder for img_off mode
+ alt_ptr = dStrdup("[IMG]");
}
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();
@@ -2197,10 +2229,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);
@@ -2315,6 +2347,7 @@ static void Html_tag_content_img(DilloHtml *html, const char *tag, int tagsize)
// multiple inheritance.
dw::Image *dwi = (dw::Image*)(dw::core::ImgRenderer*)Image->img_rndr;
HT2TB(html)->addWidget(dwi, html->style());
+ HT2TB(html)->addBreakOption (html->style (), false);
/* Image maps */
if (a_Html_get_attr(html, tag, tagsize, "ismap")) {
@@ -2448,7 +2481,6 @@ static void
type = UNKNOWN;
}
if (type == RECTANGLE || type == CIRCLE || type == POLYGON) {
- /* TODO: add support for coords in % */
if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "coords"))) {
coords = Html_read_coords(html, attrbuf);
@@ -2482,8 +2514,6 @@ static void
if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "href"))) {
url = a_Html_url_new(html, attrbuf, NULL, 0);
dReturn_if_fail ( url != NULL );
- if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "alt")))
- a_Url_set_alt(url, attrbuf);
link = Html_set_new_link(html, &url);
}
@@ -2797,7 +2827,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, false);
}
/*
@@ -3061,7 +3091,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, false);
}
/*
@@ -3262,27 +3292,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 */
@@ -3364,8 +3393,13 @@ static void Html_tag_open_base(DilloHtml *html, const char *tag, int tagsize)
if (html->InFlags & IN_HEAD) {
if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "href"))) {
- BaseUrl = a_Html_url_new(html, attrbuf, "", 1);
- if (URL_SCHEME_(BaseUrl)) {
+ bool_t html5 = html->DocType == DT_HTML &&
+ html->DocTypeVersion >= 5.0f;
+
+ BaseUrl = html5 ? a_Html_url_new(html, attrbuf, NULL, 0) :
+ a_Html_url_new(html, attrbuf, "", 1);
+
+ if (html5 || URL_SCHEME_(BaseUrl)) {
/* Pass the URL_SpamSafe flag to the new base url */
a_Url_set_flags(
BaseUrl, URL_FLAGS(html->base_url) & URL_SpamSafe);
@@ -3474,7 +3508,7 @@ const TagInfo Tags[] = {
{"a", B8(011101),'R',2, Html_tag_open_a, NULL, Html_tag_close_a},
{"abbr", B8(010101),'R',2, Html_tag_open_abbr, NULL, NULL},
/* acronym 010101 -- obsolete in HTML5 */
- {"address", B8(010110),'R',2,Html_tag_open_default, NULL, Html_tag_close_par},
+ {"address", B8(011110),'R',2,Html_tag_open_default, NULL, Html_tag_close_par},
{"area", B8(010001),'F',0, Html_tag_open_default, Html_tag_content_area,
NULL},
{"article", B8(011110),'R',2, Html_tag_open_sectioning, NULL, NULL},
@@ -3674,10 +3708,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
}
@@ -3879,8 +3913,13 @@ 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,
+ false /* Perhaps true for widgets oof? */);
+}
+
+static void Html_display_inline_block(DilloHtml *html)
+{
+ Html_add_textblock(html, false, 0, true);
}
static void Html_display_listitem(DilloHtml *html)
@@ -3985,6 +4024,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;
@@ -3992,7 +4034,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;
}
@@ -4060,7 +4101,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;
@@ -4119,16 +4160,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]);
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/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/nav.c b/src/nav.c
index 4ccb28be..3aac475a 100644
--- a/src/nav.c
+++ b/src/nav.c
@@ -353,6 +353,7 @@ void a_Nav_push(BrowserWindow *bw, const DilloUrl *url,
a_Nav_cancel_expect(bw);
a_Bw_expect(bw, url);
Nav_open_url(bw, url, requester, 0);
+ a_UIcmd_set_location_text(bw, URL_STR(url));
}
/*
diff --git a/src/paths.hh b/src/paths.hh
index 8f52cd86..ecc02f8b 100644
--- a/src/paths.hh
+++ b/src/paths.hh
@@ -15,6 +15,7 @@
#define PATHS_RC_PREFS "dillorc"
#define PATHS_RC_KEYS "keysrc"
#define PATHS_RC_DOMAIN "domainrc"
+#define PATHS_HSTS_PRELOAD "hsts_preload"
class Paths {
public:
diff --git a/src/png.c b/src/png.c
index 093e2600..652e861e 100644
--- a/src/png.c
+++ b/src/png.c
@@ -103,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;
diff --git a/src/prefs.c b/src/prefs.c
index fbd17f33..4ee65ba3 100644
--- a/src/prefs.c
+++ b/src/prefs.c
@@ -63,10 +63,14 @@ 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_strict_transport_security = TRUE;
prefs.http_user_agent = dStrdup(PREFS_HTTP_USER_AGENT);
prefs.limit_text_width = FALSE;
+ prefs.adjust_min_width = TRUE;
+ 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..d22ef656 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,8 @@ typedef struct {
bool_t load_background_images;
bool_t load_stylesheets;
bool_t parse_embedded_css;
+ bool_t http_persistent_conns;
+ bool_t http_strict_transport_security;
int32_t buffered_drawing;
char *font_serif;
char *font_sans_serif;
diff --git a/src/prefsparser.cc b/src/prefsparser.cc
index bf891491..a57a1642 100644
--- a/src/prefsparser.cc
+++ b/src/prefsparser.cc
@@ -167,11 +167,16 @@ void PrefsParser::parse(FILE *fp)
{ "home", &prefs.home, PREFS_URL, 0 },
{ "http_language", &prefs.http_language, PREFS_STRING, 0 },
{ "http_max_conns", &prefs.http_max_conns, PREFS_INT32, 0 },
+ { "http_persistent_conns", &prefs.http_persistent_conns, PREFS_BOOL, 0 },
{ "http_proxy", &prefs.http_proxy, PREFS_URL, 0 },
{ "http_proxyuser", &prefs.http_proxyuser, PREFS_STRING, 0 },
{ "http_referer", &prefs.http_referer, PREFS_STRING, 0 },
+ { "http_strict_transport_security",&prefs.http_strict_transport_security,
+ PREFS_BOOL, 0 },
{ "http_user_agent", &prefs.http_user_agent, PREFS_STRING, 0 },
{ "limit_text_width", &prefs.limit_text_width, PREFS_BOOL, 0 },
+ { "adjust_min_width", &prefs.adjust_min_width, PREFS_BOOL, 0 },
+ { "adjust_table_min_width", &prefs.adjust_table_min_width, PREFS_BOOL, 0 },
{ "load_images", &prefs.load_images, PREFS_BOOL, 0 },
{ "load_background_images", &prefs.load_background_images, PREFS_BOOL, 0 },
{ "load_stylesheets", &prefs.load_stylesheets, PREFS_BOOL, 0 },
diff --git a/src/styleengine.cc b/src/styleengine.cc
index 91a2b2c5..c005f881 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 ();
@@ -154,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);
}
@@ -588,6 +589,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;
@@ -599,6 +606,12 @@ void StyleEngine::apply (int i, StyleAttrs *attrs, CssPropertyList *props,
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"
attrs->lineHeight = dw::core::style::LENGTH_AUTO;
@@ -638,6 +651,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;
@@ -650,6 +666,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;
@@ -662,6 +684,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;
@@ -689,6 +714,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;
@@ -743,11 +780,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);
@@ -1000,7 +1032,7 @@ void StyleEngine::init () {
"code, tt, pre, samp, kbd {font-family: monospace}"
/* WORKAROUND: Reset font properties in tables as some
* pages rely on it (e.g. gmail).
- * http://developer.mozilla.org/En/Fixing_Table_Inheritance_in_Quirks_Mode
+ * http://developer.mozilla.org/en-US/Fixing_Table_Inheritance_in_Quirks_Mode
* has a detailed description of the issue.
*/
"table, caption {font-size: medium; font-weight: normal}";
diff --git a/src/styleengine.hh b/src/styleengine.hh
index a07d1863..db3e3b85 100644
--- a/src/styleengine.hh
+++ b/src/styleengine.hh
@@ -36,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 7f38cee4..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"
@@ -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);
diff --git a/src/tipwin.cc b/src/tipwin.cc
index 01d9a2f4..7cfa0844 100644
--- a/src/tipwin.cc
+++ b/src/tipwin.cc
@@ -41,7 +41,7 @@ TipWin::TipWin() : Fl_Menu_Window(1, 1) // will autosize
{
bgcolor = fl_color_cube(FL_NUM_RED - 1, FL_NUM_GREEN - 1, FL_NUM_BLUE - 2);
recent = 0;
- strcpy(tip, "");
+ tip[0] = '\0';
cur_widget = NULL;
set_override(); // no border
end();
diff --git a/src/uicmd.cc b/src/uicmd.cc
index 5225be75..9541a7df 100644
--- a/src/uicmd.cc
+++ b/src/uicmd.cc
@@ -933,7 +933,7 @@ static int UIcmd_save_file_check(const char *name)
int ch;
ds = dStr_sized_new(128);
dStr_sprintf(ds,
- "The file:\n %s (%d Bytes)\nalready exists. What do we do?",
+ "The file: %s (%d Bytes) already exists. What do we do?",
name, (int)ss.st_size);
ch = a_Dialog_choice("Dillo Save: File exists!", ds->str,
"Abort", "Continue", "Rename", NULL);
diff --git a/src/url.c b/src/url.c
index 4eacb7a4..5ffe58fd 100644
--- a/src/url.c
+++ b/src/url.c
@@ -46,6 +46,7 @@
#include <ctype.h>
#include "url.h"
+#include "hsts.h"
#include "msg.h"
static const char *HEX = "0123456789ABCDEF";
@@ -118,6 +119,12 @@ const char *a_Url_hostname(const DilloUrl *u)
}
}
+ if (!url->port) {
+ if (!dStrAsciiCasecmp(url->scheme, "http"))
+ url->port = URL_HTTP_PORT;
+ else if (!dStrAsciiCasecmp(url->scheme, "https"))
+ url->port = URL_HTTPS_PORT;
+ }
return url->hostname;
}
@@ -134,10 +141,17 @@ static DilloUrl *Url_object_new(const char *uri_str)
url = dNew0(DilloUrl, 1);
+ /* url->buffer is given a little extra room in case HSTS needs to transform
+ * a URL string ending in ":80" to ":443".
+ */
+ int len = strlen(uri_str)+2;
+ s = dNew(char, len);
+ memcpy(s, uri_str, len-1);
+ s = dStrstrip(s);
+
/* remove leading & trailing space from buffer */
- url->buffer = dStrstrip(dStrdup(uri_str));
+ url->buffer = s;
- s = (char *) url->buffer;
p = strpbrk(s, ":/?#");
if (p && p[0] == ':' && p > s) { /* scheme */
*p = 0;
@@ -198,7 +212,6 @@ void a_Url_free(DilloUrl *url)
dFree((char *)url->hostname);
dFree((char *)url->buffer);
dStr_free(url->data, 1);
- dFree((char *)url->alt);
dFree(url);
}
}
@@ -207,7 +220,6 @@ void a_Url_free(DilloUrl *url)
* Resolve the URL as RFC3986 suggests.
*/
static Dstr *Url_resolve_relative(const char *RelStr,
- DilloUrl *BaseUrlPar,
const char *BaseStr)
{
char *p, *s, *e;
@@ -218,9 +230,7 @@ static Dstr *Url_resolve_relative(const char *RelStr,
/* parse relative URL */
RelUrl = Url_object_new(RelStr);
- if (BaseUrlPar) {
- BaseUrl = BaseUrlPar;
- } else if (RelUrl->scheme == NULL) {
+ if (RelUrl->scheme == NULL) {
/* only required when there's no <scheme> in RelStr */
BaseUrl = Url_object_new(BaseStr);
}
@@ -330,8 +340,7 @@ static Dstr *Url_resolve_relative(const char *RelStr,
done:
dStr_free(Path, TRUE);
a_Url_free(RelUrl);
- if (BaseUrl != BaseUrlPar)
- a_Url_free(BaseUrl);
+ a_Url_free(BaseUrl);
return SolvedUrl;
}
@@ -350,7 +359,6 @@ done:
* port = 8080
* flags = URL_Get
* data = Dstr * ("")
- * alt = NULL
* ismap_url_len = 0
* }
*
@@ -379,10 +387,10 @@ DilloUrl* a_Url_new(const char *url_str, const char *base_url)
for (i = 0; url_str[i]; ++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;
@@ -400,7 +408,7 @@ DilloUrl* a_Url_new(const char *url_str, const char *base_url)
}
/* Resolve the URL */
- SolvedUrl = Url_resolve_relative(urlstr, NULL, base_url);
+ SolvedUrl = Url_resolve_relative(urlstr, base_url);
_MSG("SolvedUrl = %s\n", SolvedUrl->str);
/* Fill url data */
@@ -412,6 +420,33 @@ DilloUrl* a_Url_new(const char *url_str, const char *base_url)
dFree(str1);
dFree(str2);
+
+ /*
+ * A site's HTTP Strict Transport Security policy may direct us to transform
+ * URLs like "http://en.wikipedia.org:80" to "https://en.wikipedia.org:443".
+ */
+ if (prefs.http_strict_transport_security &&
+ url->scheme && !dStrAsciiCasecmp(url->scheme, "http") &&
+ a_Hsts_require_https(a_Url_hostname(url))) {
+ const char *const scheme = "https";
+
+ MSG("url: HSTS transformation for %s.\n", url->url_string->str);
+ url->scheme = scheme;
+ if (url->port == URL_HTTP_PORT)
+ url->port = URL_HTTPS_PORT;
+
+ if (url->authority) {
+ int len = strlen(url->authority);
+
+ if (len >= 3 && !strcmp(url->authority + len-3, ":80")) {
+ strcpy((char *)url->authority + len-2, "443");
+ }
+ }
+
+ dStr_free(url->url_string, TRUE);
+ url->url_string = NULL;
+ }
+
return url;
}
@@ -429,7 +464,6 @@ DilloUrl* a_Url_dup(const DilloUrl *ori)
url->url_string = dStr_new(URL_STR(ori));
url->port = ori->port;
url->flags = ori->flags;
- url->alt = dStrdup(ori->alt);
url->ismap_url_len = ori->ismap_url_len;
url->illegal_chars = ori->illegal_chars;
url->illegal_chars_spc = ori->illegal_chars_spc;
@@ -489,17 +523,6 @@ void a_Url_set_data(DilloUrl *u, Dstr **data)
}
/*
- * Set DilloUrl alt (alternate text to the URL. Used by image maps)
- */
-void a_Url_set_alt(DilloUrl *u, const char *alt)
-{
- if (u) {
- dFree((char *)u->alt);
- u->alt = dStrdup(alt);
- }
-}
-
-/*
* Set DilloUrl ismap coordinates
* (this is optimized for not hogging the CPU)
*/
@@ -509,8 +532,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;
- a_Url_set_flags(u, URL_FLAGS(u) | URL_Ismap);
+ u->ismap_url_len = URL_STR_(u) ? u->url_string->len : 0;
}
if (u->url_string) {
dStr_truncate(u->url_string, u->ismap_url_len);
@@ -638,7 +660,7 @@ char *a_Url_string_strip_delimiters(const char *str)
/*
* Is the provided hostname an IP address?
*/
-static bool_t Url_host_is_ip(const char *host)
+bool_t a_Url_host_is_ip(const char *host)
{
uint_t len;
@@ -724,7 +746,7 @@ static const char *Url_host_find_public_suffix(const char *host)
const char *s;
uint_t dots;
- if (!host || !*host || Url_host_is_ip(host))
+ if (!host || !*host || a_Url_host_is_ip(host))
return host;
s = host;
diff --git a/src/url.h b/src/url.h
index bb20d789..93d198f8 100644
--- a/src/url.h
+++ b/src/url.h
@@ -13,14 +13,8 @@
#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
-
+#define URL_HTTP_PORT 80
+#define URL_HTTPS_PORT 443
/*
* Values for DilloUrl->flags.
@@ -28,12 +22,8 @@
*/
#define URL_Get (1 << 0)
#define URL_Post (1 << 1)
-#define URL_ISindex (1 << 2)
-#define URL_Ismap (1 << 3)
-#define URL_RealmAccess (1 << 4)
#define URL_E2EQuery (1 << 5)
-#define URL_ReloadImages (1 << 6)
#define URL_ReloadPage (1 << 7)
#define URL_ReloadFromCache (1 << 8)
@@ -53,7 +43,6 @@
#define URL_QUERY_(u) (u)->query
#define URL_FRAGMENT_(u) (u)->fragment
#define URL_HOST_(u) a_Url_hostname(u)
-#define URL_ALT_(u) (u)->alt
#define URL_STR_(u) a_Url_str(u)
/* this returns a Dstr* */
#define URL_DATA_(u) (u)->data
@@ -75,9 +64,8 @@
#define URL_QUERY(u) NPTR2STR(URL_QUERY_(u))
#define URL_FRAGMENT(u) NPTR2STR(URL_FRAGMENT_(u))
#define URL_HOST(u) NPTR2STR(URL_HOST_(u))
-#define URL_DATA(u) URL_DATA_(u)
-#define URL_ALT(u) NPTR2STR(URL_ALT_(u))
#define URL_STR(u) NPTR2STR(URL_STR_(u))
+#define URL_DATA(u) URL_DATA_(u)
#define URL_PORT(u) URL_PORT_(u)
#define URL_FLAGS(u) URL_FLAGS_(u)
#define URL_ILLEGAL_CHARS(u) URL_ILLEGAL_CHARS_(u)
@@ -100,7 +88,6 @@ typedef struct {
int port;
int flags;
Dstr *data; /* POST */
- const char *alt; /* "alt" text (used by image maps) */
int ismap_url_len; /* Used by server side image maps */
int illegal_chars; /* number of illegal chars */
int illegal_chars_spc; /* number of illegal space chars */
@@ -115,11 +102,11 @@ DilloUrl* a_Url_dup(const DilloUrl *u);
int a_Url_cmp(const DilloUrl *A, const DilloUrl *B);
void a_Url_set_flags(DilloUrl *u, int flags);
void a_Url_set_data(DilloUrl *u, Dstr **data);
-void a_Url_set_alt(DilloUrl *u, const char *alt);
void a_Url_set_ismap_coords(DilloUrl *u, char *coord_str);
char *a_Url_decode_hex_str(const char *str);
char *a_Url_encode_hex_str(const char *str);
char *a_Url_string_strip_delimiters(const char *str);
+bool_t a_Url_host_is_ip(const char *host);
bool_t a_Url_same_organization(const DilloUrl *u1, const DilloUrl *u2);
#ifdef __cplusplus
}
diff --git a/src/web.cc b/src/web.cc
index b835610c..a175ddb9 100644
--- a/src/web.cc
+++ b/src/web.cc
@@ -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 2110b5bc..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 \
@@ -52,6 +56,14 @@ dw_find_test_LDADD = \
$(top_builddir)/lout/liblout.a \
@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 = \
$(top_builddir)/dw/libDw-widgets.a \
@@ -108,6 +120,17 @@ dw_lists_LDADD = \
$(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
dw_table_aligned_LDADD = \
$(top_builddir)/dw/libDw-widgets.a \
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 ff744c97..85031043 100644
--- a/test/cookies.c
+++ b/test/cookies.c
@@ -539,14 +539,33 @@ static void maxage()
a_Cookies_set("name=val; max-age=0", "maxage0.com", "/", NULL);
expect(__LINE__, "", "http", "maxage0.com", "/");
+ a_Cookies_set("name=val; max-age=-0", "maxage-0.com", "/", NULL);
+ expect(__LINE__, "", "http", "maxage-0.com", "/");
+
a_Cookies_set("name=val; max-age=100", "maxage100.com", "/", NULL);
expect(__LINE__, "Cookie: name=val\r\n", "http", "maxage100.com", "/");
a_Cookies_set("name=val; max-age=-100", "maxage-100.com", "/", NULL);
expect(__LINE__, "", "http", "maxage-100.com", "/");
- a_Cookies_set("name=val; max-age=2000000000", "maxage-huge.com", "/", NULL);
- expect(__LINE__, "Cookie: name=val\r\n", "http", "maxage-huge.com", "/");
+ a_Cookies_set("name=val; max-age=2000000000", "maxage2bil.com", "/", NULL);
+ expect(__LINE__, "Cookie: name=val\r\n", "http", "maxage2bil.com", "/");
+
+ a_Cookies_set("name=val; max-age=3000000000", "maxage3bil.com", "/", NULL);
+ expect(__LINE__, "Cookie: name=val\r\n", "http", "maxage3bil.com", "/");
+
+ a_Cookies_set("name=val; max-age=7000000000", "maxage7bil.com", "/", NULL);
+ expect(__LINE__, "Cookie: name=val\r\n", "http", "maxage7bil.com", "/");
+
+ a_Cookies_set("name=val; max-age=-2000000000", "maxage-2bil.com", "/",NULL);
+ expect(__LINE__, "", "http", "maxage-2bil.com", "/");
+
+ a_Cookies_set("name=val; max-age=-3000000000", "maxage-3bil.com", "/",NULL);
+ expect(__LINE__, "", "http", "maxage-3bil.com", "/");
+
+ a_Cookies_set("name=val; max-age=-7000000000", "maxage-7bil.com", "/",NULL);
+ expect(__LINE__, "", "http", "maxage-7bil.com", "/");
+
/* just having a server date shouldn't matter */
a_Cookies_set("name=val; max-age=0", "maxage0s.com", "/", server_date);
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..ed7849dc
--- /dev/null
+++ b/test/dw_simple_container.cc
@@ -0,0 +1,244 @@
+/*
+ * 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.minWidthIntrinsic = childExtr.maxWidth =
+ childExtr.maxWidthIntrinsic = extremes->adjustmentWidth = 0;
+
+ extremes->minWidth = childExtr.minWidth + boxDiffWidth ();
+ extremes->minWidthIntrinsic = childExtr.minWidthIntrinsic + boxDiffWidth ();
+ extremes->maxWidth = childExtr.maxWidth + boxDiffWidth ();
+ extremes->maxWidthIntrinsic = childExtr.maxWidthIntrinsic + boxDiffWidth ();
+ extremes->adjustmentWidth = childExtr.adjustmentWidth + boxDiffWidth ();
+
+ correctExtremes (extremes, true);
+}
+
+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?