diff options
-rw-r--r-- | .hgignore | 2 | ||||
-rw-r--r-- | AUTHORS | 2 | ||||
-rw-r--r-- | ChangeLog | 32 | ||||
-rw-r--r-- | Doxyfile | 2661 | ||||
-rw-r--r-- | README | 4 | ||||
-rw-r--r-- | configure.ac | 25 | ||||
-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/README | 51 | ||||
-rw-r--r-- | devdoc/dw-changes.doc (renamed from doc/dw-changes.doc) | 0 | ||||
-rw-r--r-- | devdoc/dw-example-screenshot.png | bin | 0 -> 2264 bytes | |||
-rw-r--r-- | devdoc/dw-floats-01.png | bin | 0 -> 3410 bytes | |||
-rw-r--r-- | devdoc/dw-grows.doc | 202 | ||||
-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.doc | 69 | ||||
-rw-r--r-- | devdoc/dw-out-of-flow-floats.doc | 121 | ||||
-rw-r--r-- | devdoc/dw-out-of-flow.doc | 214 | ||||
-rw-r--r-- | devdoc/dw-overview.doc (renamed from doc/dw-overview.doc) | 0 | ||||
-rw-r--r-- | devdoc/dw-size-of-widget.png | bin | 0 -> 1749 bytes | |||
-rw-r--r-- | devdoc/dw-style-box-model.png | bin | 0 -> 3889 bytes | |||
-rw-r--r-- | devdoc/dw-style-length-absolute.png | bin | 0 -> 575 bytes | |||
-rw-r--r-- | devdoc/dw-style-length-percentage.png | bin | 0 -> 890 bytes | |||
-rw-r--r-- | devdoc/dw-style-length-relative.png | bin | 0 -> 868 bytes | |||
-rw-r--r-- | devdoc/dw-textblock-collapsing-spaces-1-1.png (renamed from doc/dw-textblock-collapsing-spaces-1-1.png) | bin | 641 -> 641 bytes | |||
-rw-r--r-- | devdoc/dw-textblock-collapsing-spaces-1-2.png (renamed from doc/dw-textblock-collapsing-spaces-1-2.png) | bin | 521 -> 521 bytes | |||
-rw-r--r-- | devdoc/dw-textblock-collapsing-spaces-2-1.png (renamed from doc/dw-textblock-collapsing-spaces-2-1.png) | bin | 802 -> 802 bytes | |||
-rw-r--r-- | devdoc/dw-textblock-collapsing-spaces-2-2.png (renamed from doc/dw-textblock-collapsing-spaces-2-2.png) | bin | 586 -> 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) | bin | 755 -> 755 bytes | |||
-rw-r--r-- | devdoc/dw-viewport-without-scrollbar.png (renamed from doc/dw-viewport-without-scrollbar.png) | bin | 542 -> 542 bytes | |||
-rw-r--r-- | devdoc/dw-widget-sizes.doc | 277 | ||||
-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.png | bin | 0 -> 19319 bytes | |||
-rw-r--r-- | devdoc/not-so-simple-container.svg | 785 | ||||
-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-- | dillorc | 25 | ||||
-rw-r--r-- | doc/Dw.txt | 11 | ||||
-rw-r--r-- | doc/Imgbuf.txt | 177 | ||||
-rw-r--r-- | doc/Makefile.am | 43 | ||||
-rw-r--r-- | doc/README | 54 | ||||
-rw-r--r-- | doc/Selection.txt | 149 | ||||
-rw-r--r-- | doc/dillo.1.in | 6 | ||||
-rw-r--r-- | doc/dw-example-screenshot.png | bin | 3808 -> 0 bytes | |||
-rw-r--r-- | doc/dw-size-of-widget.png | bin | 2825 -> 0 bytes | |||
-rw-r--r-- | doc/dw-style-box-model.png | bin | 5116 -> 0 bytes | |||
-rw-r--r-- | doc/dw-style-length-absolute.png | bin | 756 -> 0 bytes | |||
-rw-r--r-- | doc/dw-style-length-percentage.png | bin | 1105 -> 0 bytes | |||
-rw-r--r-- | doc/dw-style-length-relative.png | bin | 1083 -> 0 bytes | |||
-rw-r--r-- | doc/dw-widget-sizes.doc | 186 | ||||
-rw-r--r-- | doc/not-so-simple-container.png | bin | 13812 -> 0 bytes | |||
-rw-r--r-- | dpi/cookies.c | 38 | ||||
-rw-r--r-- | dpi/downloads.cc | 2 | ||||
-rw-r--r-- | dpid/dpid_common.h | 1 | ||||
-rw-r--r-- | dw/Makefile.am | 13 | ||||
-rw-r--r-- | dw/alignedtablecell.cc | 205 | ||||
-rw-r--r-- | dw/alignedtablecell.hh | 47 | ||||
-rw-r--r-- | dw/bullet.cc | 20 | ||||
-rw-r--r-- | dw/bullet.hh | 3 | ||||
-rw-r--r-- | dw/findtext.cc | 6 | ||||
-rw-r--r-- | dw/fltkimgbuf.cc | 38 | ||||
-rw-r--r-- | dw/fltkplatform.hh | 2 | ||||
-rw-r--r-- | dw/fltkui.cc | 76 | ||||
-rw-r--r-- | dw/fltkui.hh | 4 | ||||
-rw-r--r-- | dw/hyphenator.cc | 2 | ||||
-rw-r--r-- | dw/image.cc | 171 | ||||
-rw-r--r-- | dw/image.hh | 7 | ||||
-rw-r--r-- | dw/imgrenderer.cc | 19 | ||||
-rw-r--r-- | dw/iterator.cc | 146 | ||||
-rw-r--r-- | dw/iterator.hh | 15 | ||||
-rw-r--r-- | dw/layout.cc | 200 | ||||
-rw-r--r-- | dw/layout.hh | 43 | ||||
-rw-r--r-- | dw/listitem.cc | 7 | ||||
-rw-r--r-- | dw/listitem.hh | 2 | ||||
-rw-r--r-- | dw/outofflowmgr.cc | 2293 | ||||
-rw-r--r-- | dw/outofflowmgr.hh | 435 | ||||
-rw-r--r-- | dw/regardingborder.cc | 39 | ||||
-rw-r--r-- | dw/regardingborder.hh | 27 | ||||
-rw-r--r-- | dw/ruler.cc | 41 | ||||
-rw-r--r-- | dw/ruler.hh | 21 | ||||
-rw-r--r-- | dw/selection.hh | 2 | ||||
-rw-r--r-- | dw/simpletablecell.cc | 129 | ||||
-rw-r--r-- | dw/simpletablecell.hh | 38 | ||||
-rw-r--r-- | dw/style.cc | 77 | ||||
-rw-r--r-- | dw/style.hh | 64 | ||||
-rw-r--r-- | dw/table.cc | 1653 | ||||
-rw-r--r-- | dw/table.hh | 140 | ||||
-rw-r--r-- | dw/table_iterator.cc | 134 | ||||
-rw-r--r-- | dw/tablecell.cc | 146 | ||||
-rw-r--r-- | dw/tablecell.hh | 37 | ||||
-rw-r--r-- | dw/textblock.cc | 1528 | ||||
-rw-r--r-- | dw/textblock.hh | 429 | ||||
-rw-r--r-- | dw/textblock_iterator.cc | 360 | ||||
-rw-r--r-- | dw/textblock_linebreaking.cc | 1750 | ||||
-rw-r--r-- | dw/types.cc | 85 | ||||
-rw-r--r-- | dw/types.hh | 32 | ||||
-rw-r--r-- | dw/ui.cc | 195 | ||||
-rw-r--r-- | dw/ui.hh | 62 | ||||
-rw-r--r-- | dw/widget.cc | 1202 | ||||
-rw-r--r-- | dw/widget.hh | 252 | ||||
-rw-r--r-- | lout/Makefile.am | 3 | ||||
-rw-r--r-- | lout/container.cc | 156 | ||||
-rw-r--r-- | lout/container.hh | 70 | ||||
-rw-r--r-- | lout/debug.hh | 249 | ||||
-rw-r--r-- | lout/identity.cc | 23 | ||||
-rw-r--r-- | lout/identity.hh | 6 | ||||
-rw-r--r-- | lout/misc.cc | 6 | ||||
-rw-r--r-- | lout/misc.hh | 67 | ||||
-rw-r--r-- | lout/msg.h | 1 | ||||
-rw-r--r-- | lout/object.cc | 61 | ||||
-rw-r--r-- | lout/object.hh | 57 | ||||
-rw-r--r-- | lout/signal.hh | 6 | ||||
-rw-r--r-- | lout/unicode.cc | 20 | ||||
-rw-r--r-- | src/IO/IO.c | 34 | ||||
-rw-r--r-- | src/IO/Makefile.am | 7 | ||||
-rw-r--r-- | src/IO/Url.h | 5 | ||||
-rw-r--r-- | src/IO/about.c | 2 | ||||
-rw-r--r-- | src/IO/http.c | 670 | ||||
-rw-r--r-- | src/IO/tls.c | 1258 | ||||
-rw-r--r-- | src/IO/tls.h | 49 | ||||
-rw-r--r-- | src/Makefile.am | 9 | ||||
-rw-r--r-- | src/cache.c | 140 | ||||
-rw-r--r-- | src/cache.h | 3 | ||||
-rw-r--r-- | src/capi.c | 225 | ||||
-rw-r--r-- | src/colors.c | 2 | ||||
-rw-r--r-- | src/cookies.h | 3 | ||||
-rw-r--r-- | src/css.cc | 2 | ||||
-rw-r--r-- | src/css.hh | 2 | ||||
-rw-r--r-- | src/cssparser.cc | 68 | ||||
-rw-r--r-- | src/decode.c | 26 | ||||
-rw-r--r-- | src/decode.h | 16 | ||||
-rw-r--r-- | src/dialog.cc | 31 | ||||
-rw-r--r-- | src/dillo.cc | 26 | ||||
-rw-r--r-- | src/form.cc | 4 | ||||
-rw-r--r-- | src/gif.c | 4 | ||||
-rw-r--r-- | src/hsts.c | 364 | ||||
-rw-r--r-- | src/hsts.h | 19 | ||||
-rwxr-xr-x | src/hsts_preload | 2037 | ||||
-rw-r--r-- | src/html.cc | 529 | ||||
-rw-r--r-- | src/html_charrefs.h | 2138 | ||||
-rw-r--r-- | src/klist.c | 2 | ||||
-rw-r--r-- | src/menu.cc | 35 | ||||
-rw-r--r-- | src/nav.c | 1 | ||||
-rw-r--r-- | src/paths.hh | 1 | ||||
-rw-r--r-- | src/png.c | 2 | ||||
-rw-r--r-- | src/prefs.c | 4 | ||||
-rw-r--r-- | src/prefs.h | 4 | ||||
-rw-r--r-- | src/prefsparser.cc | 5 | ||||
-rw-r--r-- | src/styleengine.cc | 46 | ||||
-rw-r--r-- | src/styleengine.hh | 1 | ||||
-rw-r--r-- | src/table.cc | 5 | ||||
-rw-r--r-- | src/tipwin.cc | 2 | ||||
-rw-r--r-- | src/uicmd.cc | 2 | ||||
-rw-r--r-- | src/url.c | 84 | ||||
-rw-r--r-- | src/url.h | 21 | ||||
-rw-r--r-- | src/web.cc | 2 | ||||
-rw-r--r-- | test/Makefile.am | 25 | ||||
-rw-r--r-- | test/containers.cc | 51 | ||||
-rw-r--r-- | test/cookies.c | 23 | ||||
-rw-r--r-- | test/dw_float_test.cc | 145 | ||||
-rw-r--r-- | test/dw_simple_container.cc | 244 | ||||
-rw-r--r-- | test/dw_simple_container.hh | 56 | ||||
-rw-r--r-- | test/dw_simple_container_test.cc | 114 | ||||
-rw-r--r-- | test/dw_table.cc | 1 | ||||
-rw-r--r-- | test/dw_table_aligned.cc | 6 | ||||
-rw-r--r-- | test/floats-and-absolute.html | 51 | ||||
-rw-r--r-- | test/floats-and-margins.html | 40 | ||||
-rw-r--r-- | test/floats-table.html | 24 | ||||
-rw-r--r-- | test/floats-worm.html | 42 | ||||
-rw-r--r-- | test/floats1.html | 46 | ||||
-rw-r--r-- | test/floats2.html | 17 | ||||
-rw-r--r-- | test/floats3.html | 16 | ||||
-rw-r--r-- | test/floats4.html | 63 | ||||
-rw-r--r-- | test/floats5.html | 16 |
186 files changed, 22496 insertions, 4440 deletions
@@ -27,6 +27,7 @@ ^test/dw-border-test$ ^test/dw-example$ ^test/dw-find-test$ +^test/dw-float-test$ ^test/dw-image-background$ ^test/dw-images-scaled$ ^test/dw-images-scaled2$ @@ -36,6 +37,7 @@ ^test/dw-links2$ ^test/dw-lists$ ^test/dw-resource-test$ +^test/dw-simple-container-test$ ^test/dw-table$ ^test/dw-table-aligned$ ^test/dw-ui-test$ @@ -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. @@ -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. @@ -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 @@ -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 Binary files differnew file mode 100644 index 00000000..94f272ab --- /dev/null +++ b/devdoc/dw-example-screenshot.png diff --git a/devdoc/dw-floats-01.png b/devdoc/dw-floats-01.png Binary files differnew file mode 100644 index 00000000..116d36b3 --- /dev/null +++ b/devdoc/dw-floats-01.png 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*; + - <button>. + + +A short sketch +============== + +**dw::core::Widget::sizeRequest and dw::core::Widget::getExtremes will +return final results.** The caller does not have to correct the size, +e. g. when percentages are defined. As an example, +dw::Textblock::calcWidgetSize has already become much simpler. + +**A new hierarchy, *container*:** Aside from dw::core::Widget::parent +and dw::core::Widget::generator, there is a third hierarchy +dw::core::Widget::container, which is (unlike *generator*) always a +direct ancestor, and represents what in CSS is called *containing +block*. Containers are important to define the "context size", which +is (not solely) used for percentage sizes. + +(There is another "containing block", dw::Textblock::containingBlock; +these may be consolidated some day.) + +**The process of size calculation is split between the widget itself +and its container:** + +- The container provides some abstract methods: + dw::core::Widget::getAvailWidthOfChild, + dw::core::Widget::getAvailHeightOfChild, + dw::core::Widget::correctRequisitionOfChild, and + dw::core::Widget::correctExtremesOfChild, which can be used in the + actual implementation of dw::core::Widget::sizeRequestImpl; + different containers with different ways how to arrange their + children will implement these methods in a different way. (Simple + example: the *available width* for children within a textblock is + the *available width* for the textblock itself, minus + margin/border/padding; on the other hand, it is completely different + for children of tables, for which a complex column width calculation + is used.) + +- The actual size calculation is, however, controlled by the widget + itself, which only *uses* these methods above. + +<div style="border: 2px solid #ffff00; margin-top: 0.5em; + margin-bottom: 0.5em; padding: 0.5em 1em; background-color: #ffffe0"> + <b>Update:</b> This is not fully correct; the parents are also involved + for calculating available widths and heights, at least when CSS 'width' + and 'height' are not set.</div> + +**Size hints are removed.** Instead, the container methods in the +previous paragraph are used. Changes of container sizes (especially +viewport the size) are handled in a different way. + +**Extremes are extended by intrinsic values.** In some cases (see +dw::Table::forceCalcCellSizes, case *minWidth* > *totalWidth*, for an +example) it is useful to know about minimal and maximal width of a +widget independent of CSS attributes. For this, dw::core::Extremes is +extended by: + +- dw::core::Extremes::minWidthIntrinsic and +- dw::core::Extremes::maxWidthIntrinsic. + +The rules for the calculation: + +1. If a widget has no children, it calculates *minWidthIntrinsic* and + *maxWidthIntrinsic* as those values not affected by CSS hints. + (dw::core::Widget::correctExtremes will not change these values.) +2. A widget must calculate *minWidthIntrinsic* and *maxWidthIntrinsic* + from *minWidthIntrinsic* and *maxWidthIntrinsic* of its children, + and *minWidth* and *maxWidth* from *minWidth* and *maxWidth* of its + children. +3. At the end, *minWidth* and *maxWidth* of a widget are corrected by + CSS attributes. (dw::core::Widget::correctExtremes will do this.) + +<div style="border: 2px solid #ffff00; margin-top: 0.5em; + margin-bottom: 0.5em; padding: 0.5em 1em; background-color: #ffffe0"> + <b>Notice:</b> Currently, dw::core::Widget::getExtremesImpl must + set all four members in dw::core::Extremes; this may change.</div> + +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. 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. 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 em (ibid., see also p. 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 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 Binary files differnew file mode 100644 index 00000000..dbdbe0c4 --- /dev/null +++ b/devdoc/dw-size-of-widget.png diff --git a/devdoc/dw-style-box-model.png b/devdoc/dw-style-box-model.png Binary files differnew file mode 100644 index 00000000..bf2fb1f1 --- /dev/null +++ b/devdoc/dw-style-box-model.png diff --git a/devdoc/dw-style-length-absolute.png b/devdoc/dw-style-length-absolute.png Binary files differnew file mode 100644 index 00000000..9ea28cad --- /dev/null +++ b/devdoc/dw-style-length-absolute.png diff --git a/devdoc/dw-style-length-percentage.png b/devdoc/dw-style-length-percentage.png Binary files differnew file mode 100644 index 00000000..b1ad79c9 --- /dev/null +++ b/devdoc/dw-style-length-percentage.png diff --git a/devdoc/dw-style-length-relative.png b/devdoc/dw-style-length-relative.png Binary files differnew file mode 100644 index 00000000..ee79b1a9 --- /dev/null +++ b/devdoc/dw-style-length-relative.png diff --git a/doc/dw-textblock-collapsing-spaces-1-1.png b/devdoc/dw-textblock-collapsing-spaces-1-1.png Binary files differindex d528dfb2..d528dfb2 100644 --- a/doc/dw-textblock-collapsing-spaces-1-1.png +++ b/devdoc/dw-textblock-collapsing-spaces-1-1.png diff --git a/doc/dw-textblock-collapsing-spaces-1-2.png b/devdoc/dw-textblock-collapsing-spaces-1-2.png Binary files differindex 483e79d1..483e79d1 100644 --- a/doc/dw-textblock-collapsing-spaces-1-2.png +++ b/devdoc/dw-textblock-collapsing-spaces-1-2.png diff --git a/doc/dw-textblock-collapsing-spaces-2-1.png b/devdoc/dw-textblock-collapsing-spaces-2-1.png Binary files differindex 0a03ea80..0a03ea80 100644 --- a/doc/dw-textblock-collapsing-spaces-2-1.png +++ b/devdoc/dw-textblock-collapsing-spaces-2-1.png diff --git a/doc/dw-textblock-collapsing-spaces-2-2.png b/devdoc/dw-textblock-collapsing-spaces-2-2.png Binary files differindex b89c6254..b89c6254 100644 --- a/doc/dw-textblock-collapsing-spaces-2-2.png +++ b/devdoc/dw-textblock-collapsing-spaces-2-2.png 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 Binary files differindex 7ac62de3..7ac62de3 100644 --- a/doc/dw-viewport-with-scrollbar.png +++ b/devdoc/dw-viewport-with-scrollbar.png diff --git a/doc/dw-viewport-without-scrollbar.png b/devdoc/dw-viewport-without-scrollbar.png Binary files differindex 8aa20fec..8aa20fec 100644 --- a/doc/dw-viewport-without-scrollbar.png +++ b/devdoc/dw-viewport-without-scrollbar.png 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. 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 Binary files differnew file mode 100644 index 00000000..f3e2c039 --- /dev/null +++ b/devdoc/not-so-simple-container.png 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 @@ -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 @@ -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 Binary files differdeleted file mode 100644 index a4d37903..00000000 --- a/doc/dw-example-screenshot.png +++ /dev/null diff --git a/doc/dw-size-of-widget.png b/doc/dw-size-of-widget.png Binary files differdeleted file mode 100644 index eda93ee1..00000000 --- a/doc/dw-size-of-widget.png +++ /dev/null diff --git a/doc/dw-style-box-model.png b/doc/dw-style-box-model.png Binary files differdeleted file mode 100644 index aa65ecb7..00000000 --- a/doc/dw-style-box-model.png +++ /dev/null diff --git a/doc/dw-style-length-absolute.png b/doc/dw-style-length-absolute.png Binary files differdeleted file mode 100644 index 6b4d8389..00000000 --- a/doc/dw-style-length-absolute.png +++ /dev/null diff --git a/doc/dw-style-length-percentage.png b/doc/dw-style-length-percentage.png Binary files differdeleted file mode 100644 index 15b36958..00000000 --- a/doc/dw-style-length-percentage.png +++ /dev/null diff --git a/doc/dw-style-length-relative.png b/doc/dw-style-length-relative.png Binary files differdeleted file mode 100644 index d54d99b5..00000000 --- a/doc/dw-style-length-relative.png +++ /dev/null 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 Binary files differdeleted file mode 100644 index 886b72a8..00000000 --- a/doc/not-so-simple-container.png +++ /dev/null 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. 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 @@ -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) @@ -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; } @@ -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); @@ -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 @@ -544,7 +544,7 @@ void CssContext::addRule (CssSelector *sel, CssPropertyList *props, if (order == CSS_PRIMARY_USER_AGENT) { userAgentSheet.addRule (rule); - } else { + } else { sheet[order].addRule (rule); } } @@ -133,7 +133,7 @@ inline float CSS_LENGTH_VALUE (CssLength l) { case CSS_LENGTH_TYPE_EX: case CSS_LENGTH_TYPE_PERCENTAGE: case CSS_LENGTH_TYPE_RELATIVE: - return ((float)(l & ~7)) / (1 << 15); + return ((float)(l & ~7)) / (1 << 15); case CSS_LENGTH_TYPE_AUTO: return 0.0; default: diff --git a/src/cssparser.cc b/src/cssparser.cc index 369dd67f..1487a605 100644 --- a/src/cssparser.cc +++ b/src/cssparser.cc @@ -72,6 +72,10 @@ static const char *const Css_border_width_enum_vals[] = { "thin", "medium", "thick", NULL }; +static const char *const Css_clear_enum_vals[] = { + "left", "right", "both", "none", NULL +}; + static const char *const Css_cursor_enum_vals[] = { "crosshair", "default", "pointer", "move", "e-resize", "ne-resize", "nw-resize", "n-resize", "se-resize", "sw-resize", "s-resize", @@ -84,6 +88,10 @@ static const char *const Css_display_enum_vals[] = { "table-cell", NULL }; +static const char *const Css_float_enum_vals[] = { + "none", "left", "right", NULL +}; + static const char *const Css_font_size_enum_vals[] = { "large", "larger", "medium", "small", "smaller", "xx-large", "xx-small", "x-large", "x-small", NULL @@ -121,6 +129,14 @@ static const char *const Css_list_style_type_enum_vals[] = { "katakana-iroha", "none", NULL }; +static const char *const Css_overflow_enum_vals[] = { + "visible", "hidden", "scroll", "auto", NULL +}; + +static const char *const Css_position_enum_vals[] = { + "static", "relative", "absolute", "fixed", NULL +}; + static const char *const Css_text_align_enum_vals[] = { "left", "right", "center", "justify", "string", NULL }; @@ -182,9 +198,9 @@ const CssPropertyInfo Css_property_info[CSS_PROPERTY_LAST] = { Css_border_style_enum_vals}, {"border-top-width", {CSS_TYPE_ENUM, CSS_TYPE_LENGTH, CSS_TYPE_UNUSED}, Css_border_width_enum_vals}, - {"bottom", {CSS_TYPE_UNUSED}, NULL}, + {"bottom", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_UNUSED}, NULL}, {"caption-side", {CSS_TYPE_UNUSED}, NULL}, - {"clear", {CSS_TYPE_UNUSED}, NULL}, + {"clear", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_clear_enum_vals}, {"clip", {CSS_TYPE_UNUSED}, NULL}, {"color", {CSS_TYPE_COLOR, CSS_TYPE_UNUSED}, NULL}, {"content", {CSS_TYPE_STRING, CSS_TYPE_UNUSED}, NULL}, @@ -194,7 +210,7 @@ const CssPropertyInfo Css_property_info[CSS_PROPERTY_LAST] = { {"direction", {CSS_TYPE_UNUSED}, NULL}, {"display", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_display_enum_vals}, {"empty-cells", {CSS_TYPE_UNUSED}, NULL}, - {"float", {CSS_TYPE_UNUSED}, NULL}, + {"float", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_float_enum_vals}, {"font-family", {CSS_TYPE_SYMBOL, CSS_TYPE_UNUSED}, NULL}, {"font-size", {CSS_TYPE_ENUM, CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_UNUSED}, Css_font_size_enum_vals}, @@ -227,21 +243,25 @@ const CssPropertyInfo Css_property_info[CSS_PROPERTY_LAST] = { {CSS_TYPE_SIGNED_LENGTH, CSS_TYPE_AUTO, CSS_TYPE_UNUSED}, NULL}, {"marker-offset", {CSS_TYPE_UNUSED}, NULL}, {"marks", {CSS_TYPE_UNUSED}, NULL}, - {"max-height", {CSS_TYPE_UNUSED}, NULL}, - {"max-width", {CSS_TYPE_UNUSED}, NULL}, - {"min-height", {CSS_TYPE_UNUSED}, NULL}, - {"min-width", {CSS_TYPE_UNUSED}, NULL}, + {"max-height", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_AUTO, CSS_TYPE_UNUSED}, + NULL}, + {"max-width", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_AUTO, CSS_TYPE_UNUSED}, + NULL}, + {"min-height", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_AUTO, CSS_TYPE_UNUSED}, + NULL}, + {"min-width", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_AUTO, CSS_TYPE_UNUSED}, + NULL}, {"outline-color", {CSS_TYPE_UNUSED}, NULL}, {"outline-style", {CSS_TYPE_UNUSED}, NULL}, {"outline-width", {CSS_TYPE_UNUSED}, NULL}, - {"overflow", {CSS_TYPE_UNUSED}, NULL}, + {"overflow", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_overflow_enum_vals}, {"padding-bottom", {CSS_TYPE_LENGTH, CSS_TYPE_UNUSED}, NULL}, {"padding-left", {CSS_TYPE_LENGTH, CSS_TYPE_UNUSED}, NULL}, {"padding-right", {CSS_TYPE_LENGTH, CSS_TYPE_UNUSED}, NULL}, {"padding-top", {CSS_TYPE_LENGTH, CSS_TYPE_UNUSED}, NULL}, - {"position", {CSS_TYPE_UNUSED}, NULL}, + {"position", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_position_enum_vals}, {"quotes", {CSS_TYPE_UNUSED}, NULL}, - {"right", {CSS_TYPE_UNUSED}, NULL}, + {"right", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_UNUSED}, NULL}, {"text-align", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_text_align_enum_vals}, {"text-decoration", {CSS_TYPE_MULTI_ENUM, CSS_TYPE_UNUSED}, Css_text_decoration_enum_vals}, @@ -249,7 +269,7 @@ const CssPropertyInfo Css_property_info[CSS_PROPERTY_LAST] = { {"text-shadow", {CSS_TYPE_UNUSED}, NULL}, {"text-transform", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_text_transform_enum_vals}, - {"top", {CSS_TYPE_UNUSED}, NULL}, + {"top", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_UNUSED}, NULL}, {"unicode-bidi", {CSS_TYPE_UNUSED}, NULL}, {"vertical-align",{CSS_TYPE_ENUM, CSS_TYPE_UNUSED},Css_vertical_align_vals}, {"visibility", {CSS_TYPE_UNUSED}, NULL}, @@ -277,7 +297,7 @@ typedef struct { } type; const CssPropertyName *properties; /* CSS_SHORTHAND_MULTIPLE: * must be terminated by - * CSS_PROPERTY_END + * CSS_PROPERTY_END * CSS_SHORTHAND_DIRECTIONS: * must have length 4 * CSS_SHORTHAND_BORDERS: @@ -720,7 +740,9 @@ bool CssParser::tokenMatchesProperty(CssPropertyName prop, CssValueType *type) dStrAsciiCasecmp(tval, "top") == 0 || dStrAsciiCasecmp(tval, "bottom") == 0)) return true; - // Fall Through (lenght and percentage) + if (ttype == CSS_TK_DECINT || ttype == CSS_TK_FLOAT) + return true; + break; case CSS_TYPE_LENGTH_PERCENTAGE: case CSS_TYPE_LENGTH_PERCENTAGE_NUMBER: case CSS_TYPE_LENGTH: @@ -766,7 +788,8 @@ bool CssParser::tokenMatchesProperty(CssPropertyName prop, CssValueType *type) case CSS_TYPE_URI: if (ttype == CSS_TK_SYMBOL && - dStrAsciiCasecmp(tval, "url") == 0) + (dStrAsciiCasecmp(tval, "url") == 0 || + dStrAsciiCasecmp(tval, "none") == 0)) return true; break; @@ -1044,12 +1067,16 @@ bool CssParser::parseValue(CssPropertyName prop, break; case CSS_TYPE_URI: - if (ttype == CSS_TK_SYMBOL && - dStrAsciiCasecmp(tval, "url") == 0) { - val->strVal = parseUrl(); - nextToken(); - if (val->strVal) + if (ttype == CSS_TK_SYMBOL) { + if (dStrAsciiCasecmp(tval, "url") == 0) { + val->strVal = parseUrl(); + if (val->strVal) + ret = true; + } else if (dStrAsciiCasecmp(tval, "none") == 0) { + val->strVal = NULL; ret = true; + } + nextToken(); } break; @@ -1106,6 +1133,9 @@ bool CssParser::parseValue(CssPropertyName prop, if (parseValue(prop, CSS_TYPE_LENGTH_PERCENTAGE, &valTmp)) { pos[i] = valTmp.intVal; ret = true; + } else if (parseValue(prop, CSS_TYPE_SIGNED_LENGTH, &valTmp)) { + pos[i] = valTmp.intVal; + ret = true; } else // ... but something may still fail. h[i] = v[i] = false; diff --git a/src/decode.c b/src/decode.c index 53a0d621..6d838d41 100644 --- a/src/decode.c +++ b/src/decode.c @@ -21,9 +21,10 @@ static const int bufsize = 8*1024; /* - * Decode chunked data + * Decode 'Transfer-Encoding: chunked' data */ -static Dstr *Decode_chunked(Decode *dc, const char *instr, int inlen) +Dstr *a_Decode_transfer_process(DecodeTransfer *dc, const char *instr, + int inlen) { char *inputPtr, *eol; int inputRemaining; @@ -66,6 +67,7 @@ static Dstr *Decode_chunked(Decode *dc, const char *instr, int inlen) } if (!(chunkRemaining = strtol(inputPtr, NULL, 0x10))) { + dc->finished = TRUE; break; /* A chunk length of 0 means we're done! */ } inputRemaining -= (eol - inputPtr) + 1; @@ -80,10 +82,16 @@ static Dstr *Decode_chunked(Decode *dc, const char *instr, int inlen) return output; } -static void Decode_chunked_free(Decode *dc) +bool_t a_Decode_transfer_finished(DecodeTransfer *dc) +{ + return dc->finished; +} + +void a_Decode_transfer_free(DecodeTransfer *dc) { dFree(dc->state); dStr_free(dc->leftover, 1); + dFree(dc); } static void Decode_compression_free(Decode *dc) @@ -208,7 +216,7 @@ static Dstr *Decode_deflate(Decode *dc, const char *instr, int inlen) dStr_free(output, 1); (void)inflateEnd(zs); dFree(dc->state); - dc->state = zs = dNew(z_stream, 1);; + dc->state = zs = dNew(z_stream, 1); zs->zalloc = NULL; zs->zfree = NULL; zs->next_in = NULL; @@ -280,19 +288,17 @@ static void Decode_charset_free(Decode *dc) /* * Initialize transfer decoder. Currently handles "chunked". */ -Decode *a_Decode_transfer_init(const char *format) +DecodeTransfer *a_Decode_transfer_init(const char *format) { - Decode *dc = NULL; + DecodeTransfer *dc = NULL; if (format && !dStrAsciiCasecmp(format, "chunked")) { int *chunk_remaining = dNew(int, 1); *chunk_remaining = 0; - dc = dNew(Decode, 1); + dc = dNew(DecodeTransfer, 1); dc->leftover = dStr_new(""); dc->state = chunk_remaining; - dc->decode = Decode_chunked; - dc->free = Decode_chunked_free; - dc->buffer = NULL; /* not used */ + dc->finished = FALSE; _MSG("chunked!\n"); } return dc; diff --git a/src/decode.h b/src/decode.h index 279807a6..06c987f6 100644 --- a/src/decode.h +++ b/src/decode.h @@ -15,7 +15,21 @@ typedef struct Decode { void (*free) (struct Decode *dc); } Decode; -Decode *a_Decode_transfer_init(const char *format); +/* I'm not going to shoehorn the decoders into the same form anymore. They + * can evolve independently. + */ +typedef struct DecodeTransfer { + Dstr *leftover; + void *state; + bool_t finished; /* has the terminating chunk been seen? */ +} DecodeTransfer; + +DecodeTransfer *a_Decode_transfer_init(const char *format); +Dstr *a_Decode_transfer_process(DecodeTransfer *dc, const char *instr, + int inlen); +bool_t a_Decode_transfer_finished(DecodeTransfer *dc); +void a_Decode_transfer_free(DecodeTransfer *dc); + Decode *a_Decode_content_init(const char *format); Decode *a_Decode_charset_init(const char *format); Dstr *a_Decode_process(Decode *dc, const char *instr, int inlen); diff --git a/src/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; @@ -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., "/" or "/"). + * The "&#" has already been consumed. */ -static int Html_parse_entity(DilloHtml *html, const char *token, - int toksize, int *entsize) +static const char *Html_parse_numeric_charref(DilloHtml *html, char *tok, + bool_t is_attr, int *entsize) { - int isocode, i; - char *tok, *s, c; + static char buf[5]; + char *s = tok; + int n, codepoint = -1; - token++; - tok = s = toksize ? dStrndup(token, (uint_t)toksize) : dStrdup(token); - - isocode = -1; - - if (*s == '#') { - /* numeric character reference */ - errno = 0; - if (*++s == 'x' || *s == 'X') { - if (isxdigit(*++s)) { - /* strtol with base 16 accepts leading "0x" - we don't */ - if (*s == '0' && s[1] == 'x') { - s++; - isocode = 0; - } else { - isocode = strtol(s, &s, 16); - } + errno = 0; + + if (*s == 'x' || *s == 'X') { + if (isxdigit(*++s)) { + /* strtol with base 16 accepts leading "0x" - we don't */ + if (*s == '0' && s[1] == 'x') { + s++; + codepoint = 0; + } else { + codepoint = strtol(s, &s, 16); } - } else if (isdigit(*s)) { - isocode = strtol(s, &s, 10); } + } else if (isdigit(*s)) { + codepoint = strtol(s, &s, 10); + } + if (errno) + codepoint = -1; - if (!isocode || errno || isocode > 0xffff) { - /* this catches null bytes, errors and codes >= 0xFFFF */ - BUG_MSG("Numeric character reference \"%s\" out of range.", 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 "©=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., "&" or "…"). + * The "&" has already been consumed. + */ +static const char *Html_parse_named_charref(DilloHtml *html, char *tok, + bool_t is_attr, int *entsize) +{ + Charref_t *p; + char c; + char *s = tok; + const char *ret = NULL; + + while (*++s && (isalnum(*s) || strchr(":_.-", *s))) ; + c = *s; + *s = '\0'; + if (c != ';') { + if (prefs.show_extra_warnings && (html->DocType == DT_XHTML || + (html->DocType == DT_HTML && html->DocTypeVersion <= 4.01f))) + BUG_MSG("Character reference '&%s' lacks ';'.", tok); + + /* Don't require ';' for old HTML, except that our current heuristic + * is to require it in attributes to avoid cases like "©=1" found + * in URLs. + */ + if (is_attr || html->DocType == DT_XHTML || + (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)) { + return ret; } - if (c == ';') - s++; - else if (prefs.show_extra_warnings) - BUG_MSG("Character entity reference without trailing ';'."); } + 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"); } /* @@ -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: @@ -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); @@ -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; @@ -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 } @@ -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? |